diff --git a/doc/pager/config.md b/doc/pager/config.md index 22f67a22..cbb4d036 100644 --- a/doc/pager/config.md +++ b/doc/pager/config.md @@ -19,6 +19,8 @@ The list of existing options are: | sortFieldWhitelist | array | [] | SortableSubscriber | | sortFieldParameterName | string | sort | SortableSubscriber | | sortDirectionParameterName | string | sort | SortableSubscriber | +| defaultFilterFields | string\|array* | | FiltrationSubscriber | +| filterFieldWhitelist | array | | FiltrationSubscriber | | filterFieldParameterName | string | filterParam | FiltrationSubscriber | | filterValueParameterName | string | filterValue | FiltrationSubscriber | @@ -45,3 +47,8 @@ you have to set `wrap-queries` to `true`. Otherwise you will get an exception wi Used as default field name for the sorting. It can take an array for sorting by multiple fields. \* **Attention**: Arrays are only supported for *Doctrine's ORM*. + + +### `defaultFilterFields` + +Used as default field names for the filtration. It can take an array for filtering by multiple fields. diff --git a/doc/pager/usage.md b/doc/pager/usage.md index b3409cc1..1456d826 100644 --- a/doc/pager/usage.md +++ b/doc/pager/usage.md @@ -95,3 +95,19 @@ $pagination = $paginator->paginate($query, 1/*page number*/, 20/*limit per page* The Paginator will add an `ORDER BY` automatically for each attribute for the `defaultSortFieldName` option. + +## Filtering database query results by multiple columns (only Doctrine ORM and Propel) + +You can also filter the result of a database query by multiple columns. +For example users should be filtered by lastname or by firstname: + +```php +$query = $entityManager->createQuery('SELECT u FROM User'); + +$pagination = $paginator->paginate($query, 1/*page number*/, 20/*limit per page*/, array( + 'defaultFilterFields' => array('u.lastname', 'u.firstname'), +)); +``` + +If the `filterValue` parameter is set, the Paginator will add an `WHERE` condition automatically +for each attribute for the `defaultFilterFields` option. The conditions are `OR`-linked. diff --git a/src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/Query/WhereWalker.php b/src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/Query/WhereWalker.php new file mode 100644 index 00000000..c0a9c9a0 --- /dev/null +++ b/src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/Query/WhereWalker.php @@ -0,0 +1,229 @@ +_getQuery(); + $queriedValue = $query->getHint(self::HINT_PAGINATOR_FILTER_VALUE); + $columns = $query->getHint(self::HINT_PAGINATOR_FILTER_COLUMNS); + $components = $this->_getQueryComponents(); + $filterExpressions = array(); + $expressions = array(); + foreach ($columns as $column) { + $alias = false; + $parts = explode('.', $column); + $field = end($parts); + if (2 <= count($parts)) { + $alias = reset($parts); + if (!array_key_exists($alias, $components)) { + throw new \UnexpectedValueException("There is no component aliased by [{$alias}] in the given Query"); + } + $meta = $components[$alias]; + if (!$meta['metadata']->hasField($field)) { + throw new \UnexpectedValueException("There is no such field [{$field}] in the given Query component, aliased by [$alias]"); + } + $pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD, $alias, $field); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + } else { + if (!array_key_exists($field, $components)) { + throw new \UnexpectedValueException("There is no component field [{$field}] in the given Query"); + } + $pathExpression = $components[$field]['resultVariable']; + } + $expression = new ConditionalPrimary(); + if (isset($meta) && $meta['metadata']->getTypeOfField($field) === 'boolean') { + if (in_array(strtolower($queriedValue), array('true', 'false'))) { + $expression->simpleConditionalExpression = new ComparisonExpression($pathExpression, '=', new Literal(Literal::BOOLEAN, $queriedValue)); + } elseif (is_numeric($queriedValue)) { + $expression->simpleConditionalExpression = new ComparisonExpression($pathExpression, '=', new Literal(Literal::BOOLEAN, $queriedValue == '1' ? 'true' : 'false')); + } else { + continue; + } + unset($meta); + } elseif (is_numeric($queriedValue)) { + $expression->simpleConditionalExpression = new ComparisonExpression($pathExpression, '=', new Literal(Literal::NUMERIC, $queriedValue)); + } else { + $expression->simpleConditionalExpression = new LikeExpression($pathExpression, new Literal(Literal::STRING, $queriedValue)); + } + $filterExpressions[] = $expression->simpleConditionalExpression; + $expressions[] = $expression; + } + if (count($expressions) > 1) { + $conditionalPrimary = new ConditionalExpression($expressions); + } elseif (count($expressions) > 0) { + $conditionalPrimary = reset($expressions); + } else { + return; + } + if ($AST->whereClause) { + if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) { + if (!$this->termContainsFilter($AST->whereClause->conditionalExpression, $filterExpressions)) { + array_unshift( + $AST->whereClause->conditionalExpression->conditionalFactors, + $this->createPrimaryFromNode($conditionalPrimary) + ); + } + } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) { + if (!$this->primaryContainsFilter($AST->whereClause->conditionalExpression, $filterExpressions)) { + $AST->whereClause->conditionalExpression = new ConditionalTerm(array( + $this->createPrimaryFromNode($conditionalPrimary), + $AST->whereClause->conditionalExpression, + )); + } + } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression) { + if (!$this->expressionContainsFilter($AST->whereClause->conditionalExpression, $filterExpressions)) { + $previousPrimary = new ConditionalPrimary(); + $previousPrimary->conditionalExpression = $AST->whereClause->conditionalExpression; + $AST->whereClause->conditionalExpression = new ConditionalTerm(array( + $this->createPrimaryFromNode($conditionalPrimary), + $previousPrimary, + )); + } + } + } else { + $AST->whereClause = new WhereClause( + $conditionalPrimary + ); + } + } + + /** + * @param ConditionalExpression $node + * @param Node[] $filterExpressions + * @return bool + */ + private function expressionContainsFilter(ConditionalExpression $node, $filterExpressions) + { + foreach ($node->conditionalTerms as $conditionalTerm) { + if ($conditionalTerm instanceof ConditionalTerm && $this->termContainsFilter($conditionalTerm, $filterExpressions)) { + return true; + } elseif ($conditionalTerm instanceof ConditionalPrimary && $this->primaryContainsFilter($conditionalTerm, $filterExpressions)) { + return true; + } + } + + return false; + } + + /** + * @param ConditionalTerm $node + * @param Node[] $filterExpressions + * @return bool|void + */ + private function termContainsFilter(ConditionalTerm $node, $filterExpressions) + { + foreach ($node->conditionalFactors as $conditionalFactor) { + if ($conditionalFactor instanceof ConditionalFactor) { + if ($this->factorContainsFilter($conditionalFactor, $filterExpressions)) { + return true; + } + } elseif ($conditionalFactor instanceof ConditionalPrimary) { + if ($this->primaryContainsFilter($conditionalFactor, $filterExpressions)) { + return true; + } + } + } + + return false; + } + + /** + * @param ConditionalFactor $node + * @param Node[] $filterExpressions + * @return bool + */ + private function factorContainsFilter(ConditionalFactor $node, $filterExpressions) + { + if ($node->conditionalPrimary instanceof ConditionalPrimary && $node->not === false) { + return $this->primaryContainsFilter($node->conditionalPrimary, $filterExpressions); + } + + return false; + } + + /** + * @param ConditionalPrimary $node + * @param Node[] $filterExpressions + * @return bool + */ + private function primaryContainsFilter(ConditionalPrimary $node, $filterExpressions) + { + if ($node->isSimpleConditionalExpression() && ($node->simpleConditionalExpression instanceof LikeExpression || $node->simpleConditionalExpression instanceof ComparisonExpression)) { + return $this->isExpressionInFilterExpressions($node->simpleConditionalExpression, $filterExpressions); + } + if ($node->isConditionalExpression()) { + return $this->expressionContainsFilter($node->conditionalExpression, $filterExpressions); + } + + return false; + } + + /** + * @param Node $node + * @param Node[] $filterExpressions + * @return bool + */ + private function isExpressionInFilterExpressions(Node $node, $filterExpressions) + { + foreach ($filterExpressions as $filterExpression) { + if ((string) $filterExpression === (string) $node) { + return true; + } + } + + return false; + } + + /** + * @param Node $node + * @return ConditionalPrimary + */ + private function createPrimaryFromNode($node) + { + if ($node instanceof ConditionalPrimary) { + $conditionalPrimary = $node; + } else { + $conditionalPrimary = new ConditionalPrimary(); + $conditionalPrimary->conditionalExpression = $node; + } + + return $conditionalPrimary; + } +} diff --git a/src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/QuerySubscriber.php b/src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/QuerySubscriber.php new file mode 100644 index 00000000..64bc88a7 --- /dev/null +++ b/src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/QuerySubscriber.php @@ -0,0 +1,54 @@ +target instanceof Query) { + if (!isset($_GET[$event->options['filterValueParameterName']]) || (empty($_GET[$event->options['filterValueParameterName']]) && $_GET[$event->options['filterValueParameterName']] !== "0")) { + return; + } + if (!empty($_GET[$event->options['filterFieldParameterName']])) { + $columns = $_GET[$event->options['filterFieldParameterName']]; + } elseif (!empty($event->options['defaultFilterFields'])) { + $columns = $event->options['defaultFilterFields']; + } else { + return; + } + $value = $_GET[$event->options['filterValueParameterName']]; + if (false !== strpos($value, '*')) { + $value = str_replace('*', '%', $value); + } + if (is_string($columns) && false !== strpos($columns, ',')) { + $columns = explode(',', $columns); + } + $columns = (array) $columns; + if (isset($event->options['filterFieldWhitelist'])) { + foreach ($columns as $column) { + if (!in_array($column, $event->options['filterFieldWhitelist'])) { + throw new \UnexpectedValueException("Cannot filter by: [{$column}] this field is not in whitelist"); + } + } + } + $event->target + ->setHint(WhereWalker::HINT_PAGINATOR_FILTER_VALUE, $value) + ->setHint(WhereWalker::HINT_PAGINATOR_FILTER_COLUMNS, $columns); + QueryHelper::addCustomTreeWalker($event->target, 'Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM\Query\WhereWalker'); + } + } + + public static function getSubscribedEvents() + { + return array( + 'knp_pager.items' => array('items', 0), + ); + } +} diff --git a/src/Knp/Component/Pager/Event/Subscriber/Filtration/FiltrationSubscriber.php b/src/Knp/Component/Pager/Event/Subscriber/Filtration/FiltrationSubscriber.php index c4308b27..3dcb4b9c 100644 --- a/src/Knp/Component/Pager/Event/Subscriber/Filtration/FiltrationSubscriber.php +++ b/src/Knp/Component/Pager/Event/Subscriber/Filtration/FiltrationSubscriber.php @@ -10,14 +10,15 @@ class FiltrationSubscriber implements EventSubscriberInterface public function before(BeforeEvent $event) { $disp = $event->getEventDispatcher(); - // hook all standard sortable subscribers + // hook all standard filtration subscribers + $disp->addSubscriber(new Doctrine\ORM\QuerySubscriber()); $disp->addSubscriber(new PropelQuerySubscriber()); } public static function getSubscribedEvents() { return array( - 'knp_pager.before' => array('before', 1) + 'knp_pager.before' => array('before', 1), ); } } diff --git a/src/Knp/Component/Pager/Event/Subscriber/Filtration/PropelQuerySubscriber.php b/src/Knp/Component/Pager/Event/Subscriber/Filtration/PropelQuerySubscriber.php index 9ceb43c2..3bd538b5 100644 --- a/src/Knp/Component/Pager/Event/Subscriber/Filtration/PropelQuerySubscriber.php +++ b/src/Knp/Component/Pager/Event/Subscriber/Filtration/PropelQuerySubscriber.php @@ -11,30 +11,38 @@ public function items(ItemsEvent $event) { $query = $event->target; if ($query instanceof \ModelCriteria) { - if (!empty($_GET[$event->options['filterFieldParameterName']]) - && !empty($_GET[$event->options['filterValueParameterName']])) { - - $value = $_GET[$event->options['filterValueParameterName']]; + if (empty($_GET[$event->options['filterValueParameterName']])) { + return; + } + if (!empty($_GET[$event->options['filterFieldParameterName']])) { $columns = $_GET[$event->options['filterFieldParameterName']]; - - if (isset($event->options['filterFieldWhitelist'])) { - if (!in_array($_GET[$event->options['filterFieldParameterName']], $event->options['filterFieldWhitelist'])) { - throw new \UnexpectedValueException("Cannot sort by: [{$_GET[$event->options['filterFieldParameterName']]}] this field is not in whitelist"); + } elseif (!empty($event->options['defaultFilterFields'])) { + $columns = $event->options['defaultFilterFields']; + } else { + return; + } + if (is_string($columns) && false !== strpos($columns, ',')) { + $columns = explode(',', $columns); + } + $columns = (array) $columns; + if (isset($event->options['filterFieldWhitelist'])) { + foreach ($columns as $column) { + if (!in_array($column, $event->options['filterFieldWhitelist'])) { + throw new \UnexpectedValueException("Cannot filter by: [{$column}] this field is not in whitelist"); } } - - $criteria = \Criteria::EQUAL; - if (false !== strpos($value, '*')) { - $value = str_replace('*', '%', $value); - $criteria = \Criteria::LIKE; - } - - foreach ((array) $columns as $column) { - if (false !== strpos($column, '.')) { - $query->where($column . $criteria . '?', $value); - } else { - $query->{'filterBy' . $column}($value, $criteria); - } + } + $value = $_GET[$event->options['filterValueParameterName']]; + $criteria = \Criteria::EQUAL; + if (false !== strpos($value, '*')) { + $value = str_replace('*', '%', $value); + $criteria = \Criteria::LIKE; + } + foreach ($columns as $column) { + if (false !== strpos($column, '.')) { + $query->where($column.$criteria.'?', $value); + } else { + $query->{'filterBy'.$column}($value, $criteria); } } } @@ -43,7 +51,7 @@ public function items(ItemsEvent $event) public static function getSubscribedEvents() { return array( - 'knp_pager.items' => array('items', 0) + 'knp_pager.items' => array('items', 0), ); } } diff --git a/tests/Test/Fixture/Entity/Article.php b/tests/Test/Fixture/Entity/Article.php index 5eee3939..509b2988 100644 --- a/tests/Test/Fixture/Entity/Article.php +++ b/tests/Test/Fixture/Entity/Article.php @@ -21,6 +21,11 @@ class Article */ private $title; + /** + * @ORM\Column(type="boolean") + */ + private $enabled = true; + public function getId() { return $this->id; @@ -35,4 +40,14 @@ public function getTitle() { return $this->title; } + + public function setEnabled($enabled) + { + $this->enabled = $enabled; + } + + public function isEnabled() + { + return $this->enabled; + } } diff --git a/tests/Test/Pager/Subscriber/Filtration/Doctrine/ORM/QueryTest.php b/tests/Test/Pager/Subscriber/Filtration/Doctrine/ORM/QueryTest.php new file mode 100644 index 00000000..90ce737b --- /dev/null +++ b/tests/Test/Pager/Subscriber/Filtration/Doctrine/ORM/QueryTest.php @@ -0,0 +1,705 @@ +markTestSkipped('APC extension is not loaded.'); + } + $config = new \Doctrine\ORM\Configuration(); + $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + $config->setProxyDir(__DIR__); + $config->setProxyNamespace('Gedmo\Mapping\Proxy'); + $config->getAutoGenerateProxyClasses(false); + $config->setMetadataDriverImpl($this->getMetadataDriverImplementation()); + + $conn = array( + 'driver' => 'pdo_sqlite', + 'memory' => true, + ); + + $em = \Doctrine\ORM\EntityManager::create($conn, $config); + $schema = array_map(function ($class) use ($em) { + return $em->getClassMetadata($class); + }, (array) $this->getUsedEntityFixtures()); + + $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($em); + $schemaTool->dropSchema(array()); + $schemaTool->createSchema($schema); + $this->populate($em); + + $_GET['filterField'] = 'a.title'; + $_GET['filterValue'] = 'summer'; + $query = $em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + + $p = new Paginator(); + $view = $p->paginate($query, 1, 10); + + $query = $em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $view = $p->paginate($query, 1, 10); + } + + /** + * @test + */ + public function shouldFilterSimpleDoctrineQuery() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = '*er'; + $this->startQueryLog(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + + $_GET['filterValue'] = 'summer'; + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + + $this->assertEquals(4, $this->queryAnalyzer->getNumExecutedQueries()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[3]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[3]); + } + } + + /** + * @test + */ + public function shouldFilterBooleanFilterValues() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $this->startQueryLog(); + + $_GET['filterParam'] = 'a.enabled'; + $_GET['filterValue'] = '1'; + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + + $_GET['filterValue'] = 'true'; + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + + $_GET['filterValue'] = '0'; + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('autumn', $items[0]->getTitle()); + $this->assertEquals('spring', $items[1]->getTitle()); + + $_GET['filterValue'] = 'false'; + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('autumn', $items[0]->getTitle()); + $this->assertEquals('spring', $items[1]->getTitle()); + + $this->assertEquals(8, $this->queryAnalyzer->getNumExecutedQueries()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.enabled = 1 LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.enabled = 1 LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.enabled = 0 LIMIT 10 OFFSET 0', $executed[5]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.enabled = 0 LIMIT 10 OFFSET 0', $executed[7]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.enabled = 1 LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.enabled = 1 LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.enabled = 0 LIMIT 10 OFFSET 0', $executed[5]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.enabled = 0 LIMIT 10 OFFSET 0', $executed[7]); + } + } + + /** + * @test + */ + public function shouldNotFilterInvalidBooleanFilterValues() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $this->startQueryLog(); + + $_GET['filterParam'] = 'a.enabled'; + $_GET['filterValue'] = 'invalid_boolean'; + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(4, count($items)); + + $this->assertEquals(2, $this->queryAnalyzer->getNumExecutedQueries()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[1]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[1]); + } + } + + /** + * @test + */ + public function shouldFilterNumericFilterValues() + { + $em = $this->getMockSqliteEntityManager(); + $this->populateNumeric($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $this->startQueryLog(); + + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = '0'; + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('0', $items[0]->getTitle()); + + $_GET['filterValue'] = '1'; + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('1', $items[0]->getTitle()); + + $this->assertEquals(4, $this->queryAnalyzer->getNumExecutedQueries()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.title = 0 LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.title = 1 LIMIT 10 OFFSET 0', $executed[3]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.title = 0 LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.title = 1 LIMIT 10 OFFSET 0', $executed[3]); + } + } + + /** + * @test + */ + public function shouldFilterComplexDoctrineQuery() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = '*er'; + $this->startQueryLog(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a WHERE a.title <> \'\' AND (a.title LIKE \'summer\' OR a.title LIKE \'spring\')'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $_GET['filterParam'] = 'a.id,a.title'; + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a WHERE a.title <> \'\' OR (a.title LIKE \'summer\' OR a.title LIKE \'spring\')'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a WHERE a.title <> \'\''); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + $_GET['filterParam'] = 'a.title'; + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.title LIKE \'%er\' AND a0_.title <> \'\' AND (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\') LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND a0_.title <> \'\' AND (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\') LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND (a0_.title <> \'\' OR (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\')) LIMIT 10 OFFSET 0', $executed[5]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND a0_.title <> \'\' LIMIT 10 OFFSET 0', $executed[7]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.title LIKE \'%er\' AND a0_.title <> \'\' LIMIT 10 OFFSET 0', $executed[9]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.title LIKE \'%er\' AND a0_.title <> \'\' AND (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\') LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND a0_.title <> \'\' AND (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\') LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND (a0_.title <> \'\' OR (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\')) LIMIT 10 OFFSET 0', $executed[5]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND a0_.title <> \'\' LIMIT 10 OFFSET 0', $executed[7]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.title LIKE \'%er\' AND a0_.title <> \'\' LIMIT 10 OFFSET 0', $executed[9]); + } + } + + /** + * @test + */ + public function shouldFilterSimpleDoctrineQueryWithMultipleProperties() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + + $_GET['filterParam'] = 'a.id,a.title'; + $_GET['filterValue'] = '*er'; + $this->startQueryLog(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + + $_GET['filterParam'] = array('a.id', 'a.title'); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $this->assertEquals('winter', $items[1]->getTitle()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[3]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[3]); + } + } + + /** + * @test + */ + public function shouldFilterComplexDoctrineQueryWithMultipleProperties() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + + $_GET['filterParam'] = 'a.id,a.title'; + $_GET['filterValue'] = '*er'; + $this->startQueryLog(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a WHERE a.title <> \'\' AND (a.title LIKE \'summer\' OR a.title LIKE \'spring\')'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND a0_.title <> \'\' AND (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\') LIMIT 10 OFFSET 0', $executed[1]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE (a0_.id LIKE \'%er\' OR a0_.title LIKE \'%er\') AND a0_.title <> \'\' AND (a0_.title LIKE \'summer\' OR a0_.title LIKE \'spring\') LIMIT 10 OFFSET 0', $executed[1]); + } + } + + /** + * @test + * @expectedException UnexpectedValueException + */ + public function shouldValidateFiltrationParameter() + { + $_GET['filterParam'] = '"a.title\''; + $_GET['filterValue'] = 'summer'; + $query = $this + ->getMockSqliteEntityManager() + ->createQuery('SELECT a FROM Test\Fixture\Entity\Article a') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + } + + /** + * @test + * @expectedException UnexpectedValueException + */ + public function shouldValidateFiltrationParameterWithoutAlias() + { + $_GET['filterParam'] = 'title'; + $_GET['filterValue'] = 'summer'; + $query = $this + ->getMockSqliteEntityManager() + ->createQuery('SELECT a FROM Test\Fixture\Entity\Article a') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + } + + /** + * @test + * @expectedException UnexpectedValueException + */ + public function shouldValidateFiltrationParameterExistance() + { + $_GET['filterParam'] = 'a.nonExistantField'; + $_GET['filterValue'] = 'summer'; + $query = $this + ->getMockSqliteEntityManager() + ->createQuery('SELECT a FROM Test\Fixture\Entity\Article a') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + } + + /** + * @test + */ + public function shouldFilterByAnyAvailableAlias() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $_GET['filterParam'] = 'test_alias'; + $_GET['filterValue'] = '*er'; + $dql = <<<___SQL + SELECT a, a.title AS test_alias + FROM Test\Fixture\Entity\Article a +___SQL; + $query = $this->em->createQuery($dql); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $this->startQueryLog(); + $view = $p->paginate($query, 1, 10, array('distinct' => false)); + $items = $view->getItems(); + $this->assertEquals(2, count($items)); + $this->assertEquals('summer', $items[0][0]->getTitle()); + $this->assertEquals('winter', $items[1][0]->getTitle()); + + $this->assertEquals(2, $this->queryAnalyzer->getNumExecutedQueries()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2, a0_.title AS title3 FROM Article a0_ WHERE a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[1]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2, a0_.title AS title_3 FROM Article a0_ WHERE a0_.title LIKE \'%er\' LIMIT 10 OFFSET 0', $executed[1]); + } + } + + /** + * @test + */ + public function shouldNotWorkWithInitialPaginatorEventDispatcher() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = 'summer'; + $query = $this + ->em + ->createQuery('SELECT a FROM Test\Fixture\Entity\Article a') + ; + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + + $p = new Paginator(); + $this->startQueryLog(); + $view = $p->paginate($query, 1, 10); + $this->assertTrue($view instanceof SlidingPagination); + + $this->assertEquals(2, $this->queryAnalyzer->getNumExecutedQueries()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[1]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[1]); + } + } + + /** + * @test + */ + public function shouldNotExecuteExtraQueriesWhenCountIsZero() + { + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = 'asc'; + $query = $this + ->getMockSqliteEntityManager() + ->createQuery('SELECT a FROM Test\Fixture\Entity\Article a') + ; + + $p = new Paginator(); + $this->startQueryLog(); + $view = $p->paginate($query, 1, 10); + $this->assertTrue($view instanceof SlidingPagination); + + $this->assertEquals(2, $this->queryAnalyzer->getNumExecutedQueries()); + } + + /** + * @test + */ + public function shouldFilterWithEmptyParametersAndDefaults() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + + $_GET['filterParam'] = ''; + $_GET['filterValue'] = 'summer'; + $this->startQueryLog(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $defaultFilterFields = 'a.title'; + $view = $p->paginate($query, 1, 10, compact('defaultFilterFields')); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $defaultFilterFields = 'a.id,a.title'; + $view = $p->paginate($query, 1, 10, compact('defaultFilterFields')); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $defaultFilterFields = array('a.id', 'a.title'); + $view = $p->paginate($query, 1, 10, compact('defaultFilterFields')); + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.id LIKE \'summer\' OR a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ WHERE a0_.id LIKE \'summer\' OR a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[5]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.id LIKE \'summer\' OR a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ WHERE a0_.id LIKE \'summer\' OR a0_.title LIKE \'summer\' LIMIT 10 OFFSET 0', $executed[5]); + } + } + + /** + * @test + */ + public function shouldNotFilterWithEmptyParametersAndDefaults() + { + $em = $this->getMockSqliteEntityManager(); + $this->populate($em); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = ''; + $this->startQueryLog(); + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + $query->setHint(UsesPaginator::HINT_FETCH_JOIN_COLLECTION, false); + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(4, count($items)); + $_GET['filterParam'] = ''; + $_GET['filterValue'] = 'summer'; + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(4, count($items)); + $_GET['filterParam'] = ''; + $_GET['filterValue'] = ''; + $view = $p->paginate($query, 1, 10); + $items = $view->getItems(); + $this->assertEquals(4, count($items)); + $executed = $this->queryAnalyzer->getExecutedQueries(); + + // Different aliases separators according to Doctrine version + if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[5]); + } else { + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ LIMIT 10 OFFSET 0', $executed[5]); + } + } + + protected function getUsedEntityFixtures() + { + return array('Test\Fixture\Entity\Article'); + } + + private function populate($em) + { + $summer = new Article(); + $summer->setTitle('summer'); + $summer->setEnabled(true); + + $winter = new Article(); + $winter->setTitle('winter'); + $winter->setEnabled(true); + + $autumn = new Article(); + $autumn->setTitle('autumn'); + $autumn->setEnabled(false); + + $spring = new Article(); + $spring->setTitle('spring'); + $spring->setEnabled(false); + + $em->persist($summer); + $em->persist($winter); + $em->persist($autumn); + $em->persist($spring); + $em->flush(); + } + + private function populateNumeric($em) + { + $zero = new Article(); + $zero->setTitle('0'); + $zero->setEnabled(true); + + $one = new Article(); + $one->setTitle('1'); + $one->setEnabled(true); + + $lower = new Article(); + $lower->setTitle('123'); + $lower->setEnabled(false); + + $upper = new Article(); + $upper->setTitle('234'); + $upper->setEnabled(false); + + $em->persist($zero); + $em->persist($one); + $em->persist($lower); + $em->persist($upper); + $em->flush(); + } + + private function getApcEntityManager() + { + $config = new \Doctrine\ORM\Configuration(); + $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + $config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + $config->setProxyDir(__DIR__); + $config->setProxyNamespace('Gedmo\Mapping\Proxy'); + $config->setAutoGenerateProxyClasses(false); + $config->setMetadataDriverImpl($this->getMetadataDriverImplementation()); + + $conn = array( + 'driver' => 'pdo_sqlite', + 'memory' => true, + ); + + $em = \Doctrine\ORM\EntityManager::create($conn, $config); + + return $em; + } +} diff --git a/tests/Test/Pager/Subscriber/Filtration/Doctrine/ORM/WhitelistTest.php b/tests/Test/Pager/Subscriber/Filtration/Doctrine/ORM/WhitelistTest.php new file mode 100644 index 00000000..1fc244bc --- /dev/null +++ b/tests/Test/Pager/Subscriber/Filtration/Doctrine/ORM/WhitelistTest.php @@ -0,0 +1,92 @@ +populate(); + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = 'summer'; + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $filterFieldWhitelist = array('a.title'); + $view = $p->paginate($query, 1, 10, compact('filterFieldWhitelist')); + + $items = $view->getItems(); + $this->assertEquals(1, count($items)); + $this->assertEquals('summer', $items[0]->getTitle()); + + $_GET['filterParam'] = 'a.id'; + $view = $p->paginate($query, 1, 10, compact('filterFieldWhitelist')); + } + + /** + * @test + */ + public function shouldFilterWithoutSpecificWhitelist() + { + $this->populate(); + $_GET['filterParam'] = 'a.title'; + $_GET['filterValue'] = 'autumn'; + $query = $this->em->createQuery('SELECT a FROM Test\Fixture\Entity\Article a'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new PaginationSubscriber()); + $dispatcher->addSubscriber(new Filtration()); + $p = new Paginator($dispatcher); + $view = $p->paginate($query, 1, 10); + + $items = $view->getItems(); + $this->assertEquals('autumn', $items[0]->getTitle()); + + $_GET['filterParam'] = 'a.id'; + $view = $p->paginate($query, 1, 10); + + $items = $view->getItems(); + $this->assertEquals(0, count($items)); + } + + protected function getUsedEntityFixtures() + { + return array('Test\Fixture\Entity\Article'); + } + + private function populate() + { + $em = $this->getMockSqliteEntityManager(); + $summer = new Article(); + $summer->setTitle('summer'); + + $winter = new Article(); + $winter->setTitle('winter'); + + $autumn = new Article(); + $autumn->setTitle('autumn'); + + $spring = new Article(); + $spring->setTitle('spring'); + + $em->persist($summer); + $em->persist($winter); + $em->persist($autumn); + $em->persist($spring); + $em->flush(); + } +} diff --git a/tests/Test/Pager/Subscriber/Sortable/Doctrine/ORM/QueryTest.php b/tests/Test/Pager/Subscriber/Sortable/Doctrine/ORM/QueryTest.php index 602ed035..04f6a410 100644 --- a/tests/Test/Pager/Subscriber/Sortable/Doctrine/ORM/QueryTest.php +++ b/tests/Test/Pager/Subscriber/Sortable/Doctrine/ORM/QueryTest.php @@ -98,11 +98,11 @@ function shouldSortSimpleDoctrineQuery() // Different aliases separators according to Doctrine version if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { - $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); - $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1 FROM Article a0_ ORDER BY a0_.title DESC LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ ORDER BY a0_.title DESC LIMIT 10 OFFSET 0', $executed[3]); } else { - $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); - $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1 FROM Article a0_ ORDER BY a0_.title DESC LIMIT 10 OFFSET 0', $executed[3]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ ORDER BY a0_.title DESC LIMIT 10 OFFSET 0', $executed[3]); } } @@ -149,9 +149,9 @@ function shouldSortByAnyAvailableAlias() // Different aliases separators according to Doctrine version if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { - $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, COUNT(a0_.id) AS sclr2 FROM Article a0_ ORDER BY sclr2 ASC LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2, COUNT(a0_.id) AS sclr3 FROM Article a0_ ORDER BY sclr3 ASC LIMIT 10 OFFSET 0', $executed[1]); } else { - $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, COUNT(a0_.id) AS sclr_2 FROM Article a0_ ORDER BY sclr_2 ASC LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2, COUNT(a0_.id) AS sclr_3 FROM Article a0_ ORDER BY sclr_3 ASC LIMIT 10 OFFSET 0', $executed[1]); } } @@ -180,9 +180,9 @@ function shouldWorkWithInitialPaginatorEventDispatcher() // Different aliases separators according to Doctrine version if (version_compare(\Doctrine\ORM\Version::VERSION, '2.5', '<')) { - $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id0, a0_.title AS title1, a0_.enabled AS enabled2 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); } else { - $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); + $this->assertEquals('SELECT a0_.id AS id_0, a0_.title AS title_1, a0_.enabled AS enabled_2 FROM Article a0_ ORDER BY a0_.title ASC LIMIT 10 OFFSET 0', $executed[1]); } }