Last active
November 18, 2015 02:01
-
-
Save merk/578bb4a7e7051c85193c to your computer and use it in GitHub Desktop.
Example Specification pattern implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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