-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #122 from MisatoTremor/orm_multi_filtration
Add Doctrine ORM and multicolumn filtration
- Loading branch information
Showing
10 changed files
with
1,159 additions
and
32 deletions.
There are no files selected for viewing
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
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
229 changes: 229 additions & 0 deletions
229
src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/Query/WhereWalker.php
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
<?php | ||
|
||
namespace Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM\Query; | ||
|
||
use Doctrine\ORM\Query\TreeWalkerAdapter; | ||
use Doctrine\ORM\Query\AST\Node; | ||
use Doctrine\ORM\Query\AST\SelectStatement; | ||
use Doctrine\ORM\Query\AST\WhereClause; | ||
use Doctrine\ORM\Query\AST\PathExpression; | ||
use Doctrine\ORM\Query\AST\LikeExpression; | ||
use Doctrine\ORM\Query\AST\ComparisonExpression; | ||
use Doctrine\ORM\Query\AST\Literal; | ||
use Doctrine\ORM\Query\AST\ConditionalExpression; | ||
use Doctrine\ORM\Query\AST\ConditionalFactor; | ||
use Doctrine\ORM\Query\AST\ConditionalPrimary; | ||
use Doctrine\ORM\Query\AST\ConditionalTerm; | ||
|
||
/** | ||
* Where Query TreeWalker for Filtration functionality | ||
* in doctrine paginator | ||
*/ | ||
class WhereWalker extends TreeWalkerAdapter | ||
{ | ||
/** | ||
* Filter key columns hint name | ||
*/ | ||
const HINT_PAGINATOR_FILTER_COLUMNS = 'knp_paginator.filter.columns'; | ||
|
||
/** | ||
* Filter value hint name | ||
*/ | ||
const HINT_PAGINATOR_FILTER_VALUE = 'knp_paginator.filter.value'; | ||
|
||
/** | ||
* Walks down a SelectStatement AST node, modifying it to | ||
* filter the query like requested by url | ||
* | ||
* @param SelectStatement $AST | ||
* @return void | ||
*/ | ||
public function walkSelectStatement(SelectStatement $AST) | ||
{ | ||
$query = $this->_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; | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/QuerySubscriber.php
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
|
||
namespace Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM; | ||
|
||
use Doctrine\ORM\Query; | ||
use Knp\Component\Pager\Event\ItemsEvent; | ||
use Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM\Query\WhereWalker; | ||
use Knp\Component\Pager\Event\Subscriber\Paginate\Doctrine\ORM\Query\Helper as QueryHelper; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
|
||
class QuerySubscriber implements EventSubscriberInterface | ||
{ | ||
public function items(ItemsEvent $event) | ||
{ | ||
if ($event->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), | ||
); | ||
} | ||
} |
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
Oops, something went wrong.