digipolisgent / doctrine-extra
Doctrine 的扩展
Requires
- php: >=5.5.9
- doctrine/orm: ^2.5
Requires (Dev)
- phpunit/phpunit: ^5.3.2
This package is auto-updated.
Last update: 2024-09-12 23:36:29 UTC
README
1. 示例
本包包含一个使用 Assert 和 Resolvable 特性的 BaseManager。如果我们使用这个 Manager,可以执行以下代码示例;
分页选择所有新订单和正在处理的订单
$paginator = $manager->paginate()->filter(
new AggregateFilter([new StatusFilter('new'),new StatusFilter('processing')]
));
以迭代器形式选择所有 '新' 和 '正在处理' 的订单
$iterator = $manager->iterate()->filter(
new AggregateFilter([new StatusFilter('new'),new StatusFilter('finished')]
));
统计所有 '新' 和 '正在处理' 的订单数量
$count = $manager->count()->filter(
new AggregateFilter([new StatusFilter('new'),new StatusFilter('finished')]
));
向现有查询添加额外的参数
$queryBuilder = $this->repository->builder()->filter(
new AggregateFilter([new StatusFilter('new'),new StatusFilter('finished')]
));
$queryBuilder->andWhere('category = :category');
$queryBuilder->setParameter('category', 'books');
$orders = $queryBuilder->getResult();
更多示例在文档中提供。
2. 过滤器
创建 Filter 对象以轻松过滤您的存储库。本包包含两个默认的 Filter 对象供使用;'AggregateFilter' 和 'PropertyFilter'。两者都是多功能的,但建议扩展 AbstractFilter 并创建自己的。
过滤本身可以通过任何使用 Resolver 特性的类来完成,并使用 Doctrine QueryBuilder 对象作为起点。
让我们看以下示例;
class OrderRepository extends \Doctrine\ORM\EntityRepository
{
/** Resolves filters **/
use Resolver;
public function filter($filters)
{
$builder = $this->createQueryBuilder('root');
return $this->resolve($filters, $builder)->getQuery()->getResult();
}
}
我们有一个实现 Resolver 特性的 OrderRepository,它有一个名为 "filter" 的方法,该方法接受一个 Doctrine Filters 数组或一个 DoctrineFilter。
使用 PropertyFilter
属性过滤器可以用于过滤任何实体的属性。假设订单有一个 "category" 属性,我们想要过滤。
/**
* @var OrderRepository
*/
private $repository;
public function categoryAction($category)
{
$orders = $this->repository->filter(new PropertyFilter($category, 'category'));
}
到现在为止,我们已经根据某个类别过滤了订单。我们还可以将多个过滤器作为数组传递给 filter() 方法作为参数,它们将全部在 andX 表达式中组合。
使用 AggregateFilter
如果我们想在 orX 表达式中使用多个过滤器,我们可以使用 AggregateFilter。在以下示例中,我们将过滤所有 '新' 或 '完成' 的订单;
$orders = $this->repository->filter(
new AggregateFilter([new StatusFilter('new'),new StatusFilter('finished')]
));
我们可以组合多个 AggregateFilters 来创建更复杂的表达式。在以下示例中,我们将过滤所有 '新' 或 '完成' 的订单,它们有 'multimedia' 或 'books' 类别。
$orders = $this->repository->filter(
new AggregateFilter([new StatusFilter('new'), new StatusFilter('finished')]),
new AggregateFilter([new CategoryFilter('multimedia'), new CategoryFilter('books')])
);
创建自定义 DoctrineFilter
简单的属性过滤器
如果我们想要实现 CategoryFilter,并且有更多实体具有类别属性并且经常使用它,我们可以这样做;
如果我们想创建自己的 CategoryFilter,这是最基本的形式,通过过滤单个直接属性进行过滤,我们可以这样做;
class CategoryFilter extends AbstractFilter
{
public function createExpression($root)
{
return $this->expr()->eq(
sprintf('%s.category', $root),
$this->expr()->literal($this->parameter)
);
}
}
我们只需要实现 CreateExpression() 方法,并创建一个简单的 expression,它将被用于 QueryBuilder 的 Where 子句。现在这个过滤器可以用于所有具有 "category" 属性的实体。
有时我们需要更复杂的过滤器。假设我们的 Order 实体与 User 有关系,User 有一个用户名。我们想要根据用户的用户名过滤所有订单。我们可以创建以下过滤器;
更高级的示例
class UserEmailFilter extends AbstractFilter
{
public function createExpression($root)
{
return $this->expr()->eq('user.email', $this->expr()->literal($this->parameter));
}
public function getAlias()
{
return 'user';
}
public function addAlias(QueryBuilder $builder, $root)
{
$builder->leftJoin(sprintf('%s.user', $root), 'user');
}
}
这个过滤器稍微复杂一些。由于我们不是过滤 Order 的直接属性,而是过滤与我们的 Order 对象有关联的 User 的属性,我们需要实现两个额外的方法。
函数 getAlias() 将返回一个字符串,别名定义了我们正在过滤属性的对象别名。在本例中是 'user',因为我们将在 user.email 上进行过滤。
添加别名(AddAlias())方法将在解析器检查QueryBuilder中是否存在'user'别名时被触发,一个别名存在是指它被选择或者连接。在这种情况下,我们将User对象左连接到我们的'root'对象上,在这个例子中是'Order'。
现在我们已经完成了UserEmailFilter,我们可以在任何具有与User关系的对象上使用此过滤器。例如,我们可以有一个预订实体和票务实体,它们都与User有关联。
所以到目前为止,我们可以做以下操作;
$reservations = $this->reservationRepository->filter(new UserEmailFilter('example@doctrine.com'));
$orders = $this->orderRepository->filter(new UserEmailFilter('example@doctrine.com'));
$tickets = $this->ticketRepository->filter(new UserEmailFilter('example@doctrine.com'));
##3. 断言结果 此包包含Assertable特性,它允许任何类传递一个QueryBuilder对象并从该QueryBuilder对象检索不同的结果。
例如,我们有一个使用Assertable特性的存储库,我们可以编写以下代码;
class OrderRepository extends EntityRepository
{
use Assertable;
public function getOrdersByCategory($category)
{
$builder = $this->createQueryBuilder('root');
$builder->andWhere('root.category = :category');
$builder->setParameter('category', $category);
return $this->assertResult($builder);
}
}
由于我们使用了assertable特性,并通过assertResult()方法传递了QueryBuilder对象,我们可以操作该函数将返回哪种结果。默认情况下,此函数将按预期运行并返回一个包含特定类别下所有Order实体的数组。
$orders = $this->repository->getOrdersByCategory('some_category');
如果我们想检索分页的订单,我们可以在调用getOrdersByCategory之前简单地调用builder()方法,Assert特性将断言作为结果传递的将是Paginator对象;
$paginator = $this->repository->builder()->getOrdersByCategory('some_category');
要查看assertResult()方法的所有可能结果,请参考以下代码;
protected function assertResult(QueryBuilder $builder, $hydration = Query::HYDRATE_OBJECT)
{
switch($this->assert) {
case Result::ARRAY:
return $builder->getQuery()->getResult($hydration);
case Result::PAGINATE:
return new Paginator($builder);
case Result::SINGLE:
return $builder->getQuery()->getOneOrNullResult($hydration);
case Result::FIRST:
$result = $builder->setMaxResults(1)->getQuery()->getResult();
return count($result) > 0 ? $result[0] : null;
case Result::ITERATE:
return $builder->getQuery()->iterate();
case Result::COUNT:
$paginator = new Paginator($builder);
return $paginator->count();
case Result::QUERY:
return $builder->getQuery();
case Result::BUILDER:
return $builder;
default:
throw new AssertResultException(sprintf('Unknown result assertion "%s"', $this->assert));
}
}
上述代码位于Assert特性中。