/src/Knp/Component/Pager/Event/Subscriber/Filtration/Doctrine/ORM/Query/WhereWalker.php
https://github.com/KnpLabs/knp-components · PHP · 261 lines · 192 code · 20 blank · 49 comment · 37 complexity · 9a28518ac81f0884a721a74fce4f70a7 MD5 · raw file
- <?php
- namespace Knp\Component\Pager\Event\Subscriber\Filtration\Doctrine\ORM\Query;
- use Doctrine\DBAL\Types\Types as Type;
- use Doctrine\ORM\Query\AST\Functions\LowerFunction;
- use Doctrine\ORM\Query\AST\ComparisonExpression;
- use Doctrine\ORM\Query\AST\ConditionalExpression;
- use Doctrine\ORM\Query\AST\ConditionalFactor;
- use Doctrine\ORM\Query\AST\ConditionalPrimary;
- use Doctrine\ORM\Query\AST\ConditionalTerm;
- use Doctrine\ORM\Query\AST\LikeExpression;
- use Doctrine\ORM\Query\AST\Literal;
- use Doctrine\ORM\Query\AST\Node;
- use Doctrine\ORM\Query\AST\PathExpression;
- use Doctrine\ORM\Query\AST\SelectStatement;
- use Doctrine\ORM\Query\AST\WhereClause;
- use Doctrine\ORM\Query\TreeWalkerAdapter;
- /**
- * Where Query TreeWalker for Filtration functionality
- * in doctrine paginator
- */
- class WhereWalker extends TreeWalkerAdapter
- {
- /**
- * Filter key columns hint name
- */
- public const HINT_PAGINATOR_FILTER_COLUMNS = 'knp_paginator.filter.columns';
- /**
- * Filter value hint name
- */
- public const HINT_PAGINATOR_FILTER_VALUE = 'knp_paginator.filter.value';
- /**
- * Filter strings in a case insensitive way
- */
- const HINT_PAGINATOR_FILTER_CASE_INSENSITIVE = 'knp_paginator.filter.case_insensitive';
- /**
- * Walks down a SelectStatement AST node, modifying it to
- * filter the query like requested by url
- *
- * @param SelectStatement $AST
- * @return void|string
- */
- public function walkSelectStatement(SelectStatement $AST)
- {
- $query = $this->_getQuery();
- $queriedValue = $query->getHint(self::HINT_PAGINATOR_FILTER_VALUE);
- $columns = $query->getHint(self::HINT_PAGINATOR_FILTER_COLUMNS);
- $filterCaseInsensitive = $query->getHint(self::HINT_PAGINATOR_FILTER_CASE_INSENSITIVE);
- $components = $this->_getQueryComponents();
- $filterExpressions = [];
- $expressions = [];
- foreach ($columns as $column) {
- $alias = false;
- $parts = explode('.', $column, 2);
- $field = end($parts);
- if (2 <= count($parts)) {
- $alias = trim(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), ['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)
- && (
- !isset($meta)
- || in_array(
- $meta['metadata']->getTypeOfField($field),
- [
- Type::SMALLINT,
- Type::INTEGER,
- Type::BIGINT,
- Type::FLOAT,
- Type::DECIMAL,
- ]
- )
- )
- ) {
- $expression->simpleConditionalExpression = new ComparisonExpression($pathExpression, '=', new Literal(Literal::NUMERIC, $queriedValue));
- } else {
- $likePathExpression = $pathExpression;
- $likeQueriedValue = $queriedValue;
- if ($filterCaseInsensitive) {
- $lower = new LowerFunction('lower');
- $lower->stringPrimary = $pathExpression;
- $likePathExpression = $lower;
- $likeQueriedValue = strtolower($queriedValue);
- }
- $expression->simpleConditionalExpression = new LikeExpression($likePathExpression, new Literal(Literal::STRING, $likeQueriedValue));
- }
- $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([
- $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([
- $this->createPrimaryFromNode($conditionalPrimary),
- $previousPrimary,
- ]);
- }
- }
- } else {
- $AST->whereClause = new WhereClause(
- $conditionalPrimary
- );
- }
- }
- /**
- * @param ConditionalExpression $node
- * @param Node[] $filterExpressions
- * @return bool
- */
- private function expressionContainsFilter(ConditionalExpression $node, $filterExpressions): bool
- {
- 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
- */
- private function termContainsFilter(ConditionalTerm $node, $filterExpressions): bool
- {
- 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): bool
- {
- 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): bool
- {
- 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): bool
- {
- foreach ($filterExpressions as $filterExpression) {
- if ((string) $filterExpression === (string) $node) {
- return true;
- }
- }
- return false;
- }
- /**
- * @param ConditionalPrimary|ConditionalExpression $node
- * @return ConditionalPrimary
- */
- private function createPrimaryFromNode($node): ConditionalPrimary
- {
- if ($node instanceof ConditionalPrimary) {
- $conditionalPrimary = $node;
- } else {
- $conditionalPrimary = new ConditionalPrimary();
- $conditionalPrimary->conditionalExpression = $node;
- }
- return $conditionalPrimary;
- }
- }