rikbruil/doctrine-specification

Doctrine Specification 模式,用于动态构建查询并使用可复用的类进行组合。

1.2.0 2017-03-17 18:30 UTC

README

Build Status Coverage Status Latest Stable Version License Scrutinizer Code Quality SensioLabsInsight

用于动态构建查询以及使用可复用类进行组合的 Doctrine Specification 模式。

这个库最初是 Benjamin Eberlei 的 博客文章 的改编。我也受到了 Happyr Doctrine-Specification 代码的启发,然而这个库有一些小的不同。主要的不同是 SpecificationRepository->match() 不直接返回结果,而是返回查询对象。

由于我喜欢 Doctrine 的分页对象,我希望能够将其与 Specification 模式结合使用。

注意:在 1.2 版本之前,需要扩展 SpecificationRepository 类。现在不再需要这样做,因为我们提供了一个 SpecificationRepositoryTrait,您可以用它来代替。这个类仍然提供,是为了向后兼容。还有一个 SpecificationAwareInterface,您如果需要的话也可以使用。

使用方法

使用 composer require rikbruil/doctrine-specification 安装最新版本

// Not using the lib
// Note: Advertisement repository is an instance of the Doctrine default repository class
$qb = $this->em->getRepository('Advertisement')
    ->createQueryBuilder('r');

return $qb->where('r.ended = 0')
    ->andWhere(
        $qb->expr()->orX(
            'r.endDate < :now',
            $qb->expr()->andX(
                'r.endDate IS NULL',
                'r.startDate < :timeLimit'
            )
        )
    )
    ->setParameter('now', new \DateTime())
    ->setParameter('timeLimit', new \DateTime('-4weeks'))
    ->getQuery()
    ->getResult();
use Rb\Specification\Doctrine\Condition\Equals;
use Rb\Specification\Doctrine\Condition\IsNull;
use Rb\Specification\Doctrine\Condition\LessThan;
use Rb\Specification\Doctrine\Logic\AndX;
use Rb\Specification\Doctrine\Logic\OrX;
use Rb\Specification\Doctrine\Specification;

// Using the lib
$spec = new Specification([
    new Equals('ended', 0),
    new OrX(
        new LessThan('endDate', new \DateTime()),
        new AndX(
            new IsNull('endDate'),
            new LessThan('startDate', new \DateTime('-4weeks'))
        )
    )
]);

// Note: Advertisement repository is an instance that uses the SpecificationRepositoryTrait
return $this->em->getRepository('Advertisement')->match($spec)->execute();

组合

这个模式的一个优点是组合,这使得规范非常可重用

use Entity\Advertisement;

class ExpiredAds extends Specification
{
    public function __construct()
    {
        $specs = [
            new Equals('ended', 0),
            new OrX(
                new LessThan('endDate', new \DateTime()),
                new AndX(
                    new IsNull('endDate'),
                    new LessThan('startDate', new \DateTime('-4weeks'))
                )
            )
        ];
        parent::__construct($specs);
    }
    
    public function isSatisfiedBy($value)
    {
        return $value === Advertisement::class;
    }
}

use Entity\User;

class AdsByUser extends Specification
{
    public function __construct(User $user)
    {
        $specs = [
            new Select('u'),
            new Join('user', 'u'),
            new Equals('id', $user->getId(), 'u'),
        ];
        
        parent::__construct($specs);
    }

    public function isSatisfiedBy($value)
    {
        return $value == Advertisement::class && parent::isSatisfiedBy($value);
    }
}

class SomeService
{
    /**
     * Fetch Adverts that we should close but only for a specific company
     */
    public function myQuery(User $user)
    {
        $spec = new Specification([
            new ExpiredAds(),
            new AdsByUser($user),
        ]);

        return $this->em->getRepository('Advertisement')->match($spec)->execute();
    }
    
    /**
     * Fetch adverts paginated by Doctrine Paginator with joins intact.
     * A paginator can be iterated over like a normal array or Doctrine Collection
     */
    public function myPaginatedQuery(User $user, $page = 1, $size = 10)
    {
        $spec = new Specification([
            new ExpiredAds(),
            new AdsByUser($user),
        ]);
        
        $query = $this->em->getRepository('Advertisement')->match($spec);
        $query->setFirstResult(($page - 1) * $size))
            ->setMaxResults($size);
        return new Paginator($query);
    }
}

需求

Doctrine-Specification 需要

  • PHP 5.5+
  • Doctrine 2.2

许可证

Doctrine-Specification 在 MIT 许可证下发布 - 详细信息请参阅 LICENSE 文件

致谢

这个库深受 Benjamin Eberlei 的 博客文章Happyr 的 Doctrine-Specification 库 的启发。