Skip to content

Instantly share code, notes, and snippets.

@merk
Last active November 18, 2015 02:01
Show Gist options
  • Save merk/578bb4a7e7051c85193c to your computer and use it in GitHub Desktop.
Save merk/578bb4a7e7051c85193c to your computer and use it in GitHub Desktop.
Example Specification pattern implementation
<?php
/**
* This file is part of the Infinite SSO project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Entity\Specification\User;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Entity\Specification\Specification;
class Enabled implements Specification
{
private $state;
public function __construct($state)
{
$this->state = $state;
}
/**
* Adds conditions to the query builder related to the specification. The
* specification should add parameters as required and return the expression to be
* added to the QueryBuilder.
*
* @param \Doctrine\ORM\QueryBuilder $qb
* @param string $dqlAlias
* @return \Doctrine\ORM\Query\Expr
*/
public function match(QueryBuilder $qb, $dqlAlias)
{
$qb->setParameter('user_enabled', $this->state);
return $qb->expr()->eq($dqlAlias.'.enabled', ':user_enabled');
}
public function modifyQuery(Query $query)
{
}
/**
* Supports a given class name.
*
* @param string $className
* @return bool
*/
public function supports($className)
{
return $className === 'Entity\\User';
}
}
<?php
/**
* This file is part of the Infinite SSO project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Entity\Repository;
use Doctrine\ORM\EntityRepository as BaseEntityRepository;
use Entity\Specification\Specification;
class EntityRepository extends BaseEntityRepository
{
/**
* Returns objects matching a specification.
*
* @param Specification $specification
* @return array
* @throws \InvalidArgumentException
*/
public function match(Specification $specification)
{
if (!$specification->supports($this->getEntityName())) {
throw new \InvalidArgumentException("Specification does not support this repository");
}
$qb = $this->createQueryBuilder('e');
if ($expr = $specification->match($qb, 'e')) {
$qb->where($expr);
}
$query = $qb->getQuery();
$specification->modifyQuery($query);
return $query->execute();
}
}
<?php
/**
* This file is part of the Infinite SSO project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Entity\Specification\User;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Entity\Group as UserGroup;
use Entity\Specification\Specification;
class Group implements Specification
{
private $group;
public function __construct(UserGroup $group)
{
$this->group = $group;
}
/**
* Adds conditions to the query builder related to the specification. The
* specification should add parameters as required and return the expression to be
* added to the QueryBuilder.
*
* @param \Doctrine\ORM\QueryBuilder $qb
* @param string $dqlAlias
* @return \Doctrine\ORM\Query\Expr|null
*/
public function match(QueryBuilder $qb, $dqlAlias)
{
$qb->leftJoin($dqlAlias.'.groups', 'g');
$qb->setParameter('user_group', $this->group);
return sprintf(':user_group MEMBER OF %s.groups', $dqlAlias);
}
/**
* Modifies the query once it has been generated.
*
* @param \Doctrine\ORM\Query $query
*/
public function modifyQuery(Query $query)
{
}
/**
* Supports a given class name.
*
* @param string $className
* @return bool
*/
public function supports($className)
{
return $className === 'Entity\\User';
}
}
<?php
/**
* This file is part of the Infinite SSO project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Entity\Specification;
use Doctrine\ORM\Query;
class Limit extends AbstractWrap
{
/**
* @var int
*/
private $maxResults;
/**
* @var int
*/
private $firstResult;
/**
* @param integer $maxResults
* @param integer $firstResult
* @param Specification $specification
*/
public function __construct($maxResults, $firstResult = 0, Specification $specification)
{
$this->maxResults = $maxResults;
$this->firstResult = $firstResult;
parent::__construct($specification);
}
public function modifyQuery(Query $query)
{
parent::modifyQuery($query);
$query->setMaxResults($this->maxResults);
$query->setFirstResult($this->firstResult);
}
}
<?php
/**
* This file is part of the Infinite SSO project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Entity\Specification;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
class Sort extends AbstractWrap
{
private $sortFields = array();
public function __construct(array $sortFields, Specification $specification)
{
parent::__construct($specification);
$this->sortFields = $sortFields;
}
/**
* Adds conditions to the query builder related to the specification. The
* specification should add parameters as required and return the expression to be
* added to the QueryBuilder.
*
* @param \Doctrine\ORM\QueryBuilder $qb
* @param string $dqlAlias
* @return \Doctrine\ORM\Query\Expr
*/
public function match(QueryBuilder $qb, $dqlAlias)
{
foreach ($this->sortFields as $field => $direction) {
$qb->addOrderBy(sprintf('%s.%s', $dqlAlias, $field), $direction);
}
return $this->specification->match($qb, $dqlAlias);
}
}
<?php
/**
* This file is part of the Infinite SSO project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Entity\Specification;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
interface Specification
{
/**
* Adds conditions to the query builder related to the specification. The
* specification should add parameters as required and return the expression to be
* added to the QueryBuilder.
*
* @param \Doctrine\ORM\QueryBuilder $qb
* @param string $dqlAlias
* @return \Doctrine\ORM\Query\Expr|null
*/
public function match(QueryBuilder $qb, $dqlAlias);
/**
* Modifies the query once it has been generated.
*
* @param \Doctrine\ORM\Query $query
*/
public function modifyQuery(Query $query);
/**
* Supports a given class name.
*
* @param string $className
* @return bool
*/
public function supports($className);
}
<?php
/**
* This file is part of the Infinite SSO project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ApiBundle\Filter;
use Doctrine\Common\Persistence\ManagerRegistry;
use Entity\Specification as Spec;
use JMS\DiExtraBundle\Annotation as DI;
/**
* @DI\Service("api.filter.user")
*/
class UserFilter
{
/**
* @var ManagerRegistry
*/
private $registry;
/**
* @DI\InjectParams({
* "registry" = @DI\Inject("doctrine")
* })
* @param ManagerRegistry $registry
*/
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
}
/**
* Returns the count of users matching the filter.
*
* @param UserFilterModel $filter
* @return int
*/
public function countUsers(UserFilterModel $filter)
{
$specification = $this->buildSpecification($filter);
$specification = new Spec\SingleScalar(new Spec\Count($specification));
return (int) $this->getRepository()->match($specification);
}
/**
* Returns users matching the filter supplied.
*
* @param UserFilterModel $filter
* @return \Entity\User[]
*/
public function getUsers(UserFilterModel $filter)
{
$specification = $this->buildSpecification($filter);
return $this->getRepository()->match($specification);
}
/**
* @param UserFilterModel $filter
* @return Spec\Specification
*/
private function buildSpecification(UserFilterModel $filter)
{
$specifications = array();
if ($filter->text) {
$specifications[] = new Spec\User\Text($filter->text);
}
if ($filter->enabled !== $filter::ENABLED_BOTH) {
$specifications[] = new Spec\User\Enabled($filter->enabled === $filter::ENABLED_ENABLED);
}
if ($filter->memberOf) {
$specifications[] = new Spec\User\Group($filter->memberOf);
}
if ($filter->loggedInDays) {
$date = new \DateTime;
$date->modify(sprintf('-%d days', $filter->loggedInDays));
$specifications[] = new Spec\User\LoggedIn($date, $filter->loggedInBefore);
}
$specification = new Spec\Sort($filter->getNormalSortFields(),
new Spec\Limit($filter->limit, $filter->offset, new Spec\AndX($specifications))
);
return $specification;
}
/**
* @return \Entity\Repository\EntityRepository
*/
private function getRepository()
{
return $this->registry->getRepository('Entity\\User');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment