vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php line 67

Open in your IDE?
  1. <?php
  2. namespace Doctrine\Common\Collections\Expr;
  3. use ArrayAccess;
  4. use Closure;
  5. use RuntimeException;
  6. use function in_array;
  7. use function is_array;
  8. use function is_scalar;
  9. use function iterator_to_array;
  10. use function method_exists;
  11. use function preg_match;
  12. use function preg_replace_callback;
  13. use function strlen;
  14. use function strpos;
  15. use function strtoupper;
  16. use function substr;
  17. /**
  18.  * Walks an expression graph and turns it into a PHP closure.
  19.  *
  20.  * This closure can be used with {@Collection#filter()} and is used internally
  21.  * by {@ArrayCollection#select()}.
  22.  */
  23. class ClosureExpressionVisitor extends ExpressionVisitor
  24. {
  25.     /**
  26.      * Accesses the field of a given object. This field has to be public
  27.      * directly or indirectly (through an accessor get*, is*, or a magic
  28.      * method, __get, __call).
  29.      *
  30.      * @param object|mixed[] $object
  31.      * @param string         $field
  32.      *
  33.      * @return mixed
  34.      */
  35.     public static function getObjectFieldValue($object$field)
  36.     {
  37.         if (is_array($object)) {
  38.             return $object[$field];
  39.         }
  40.         $accessors = ['get''is'];
  41.         foreach ($accessors as $accessor) {
  42.             $accessor .= $field;
  43.             if (method_exists($object$accessor)) {
  44.                 return $object->$accessor();
  45.             }
  46.         }
  47.         if (preg_match('/^is[A-Z]+/'$field) === && method_exists($object$field)) {
  48.             return $object->$field();
  49.         }
  50.         // __call should be triggered for get.
  51.         $accessor $accessors[0] . $field;
  52.         if (method_exists($object'__call')) {
  53.             return $object->$accessor();
  54.         }
  55.         if ($object instanceof ArrayAccess) {
  56.             return $object[$field];
  57.         }
  58.         if (isset($object->$field)) {
  59.             return $object->$field;
  60.         }
  61.         // camelcase field name to support different variable naming conventions
  62.         $ccField preg_replace_callback('/_(.?)/', static function ($matches) {
  63.             return strtoupper($matches[1]);
  64.         }, $field);
  65.         foreach ($accessors as $accessor) {
  66.             $accessor .= $ccField;
  67.             if (method_exists($object$accessor)) {
  68.                 return $object->$accessor();
  69.             }
  70.         }
  71.         return $object->$field;
  72.     }
  73.     /**
  74.      * Helper for sorting arrays of objects based on multiple fields + orientations.
  75.      *
  76.      * @param string $name
  77.      * @param int    $orientation
  78.      *
  79.      * @return Closure
  80.      */
  81.     public static function sortByField($name$orientation 1, ?Closure $next null)
  82.     {
  83.         if (! $next) {
  84.             $next = static function (): int {
  85.                 return 0;
  86.             };
  87.         }
  88.         return static function ($a$b) use ($name$next$orientation): int {
  89.             $aValue ClosureExpressionVisitor::getObjectFieldValue($a$name);
  90.             $bValue ClosureExpressionVisitor::getObjectFieldValue($b$name);
  91.             if ($aValue === $bValue) {
  92.                 return $next($a$b);
  93.             }
  94.             return ($aValue $bValue : -1) * $orientation;
  95.         };
  96.     }
  97.     /**
  98.      * {@inheritDoc}
  99.      */
  100.     public function walkComparison(Comparison $comparison)
  101.     {
  102.         $field $comparison->getField();
  103.         $value $comparison->getValue()->getValue(); // shortcut for walkValue()
  104.         switch ($comparison->getOperator()) {
  105.             case Comparison::EQ:
  106.                 return static function ($object) use ($field$value): bool {
  107.                     return ClosureExpressionVisitor::getObjectFieldValue($object$field) === $value;
  108.                 };
  109.             case Comparison::NEQ:
  110.                 return static function ($object) use ($field$value): bool {
  111.                     return ClosureExpressionVisitor::getObjectFieldValue($object$field) !== $value;
  112.                 };
  113.             case Comparison::LT:
  114.                 return static function ($object) use ($field$value): bool {
  115.                     return ClosureExpressionVisitor::getObjectFieldValue($object$field) < $value;
  116.                 };
  117.             case Comparison::LTE:
  118.                 return static function ($object) use ($field$value): bool {
  119.                     return ClosureExpressionVisitor::getObjectFieldValue($object$field) <= $value;
  120.                 };
  121.             case Comparison::GT:
  122.                 return static function ($object) use ($field$value): bool {
  123.                     return ClosureExpressionVisitor::getObjectFieldValue($object$field) > $value;
  124.                 };
  125.             case Comparison::GTE:
  126.                 return static function ($object) use ($field$value): bool {
  127.                     return ClosureExpressionVisitor::getObjectFieldValue($object$field) >= $value;
  128.                 };
  129.             case Comparison::IN:
  130.                 return static function ($object) use ($field$value): bool {
  131.                     $fieldValue ClosureExpressionVisitor::getObjectFieldValue($object$field);
  132.                     return in_array($fieldValue$valueis_scalar($fieldValue));
  133.                 };
  134.             case Comparison::NIN:
  135.                 return static function ($object) use ($field$value): bool {
  136.                     $fieldValue ClosureExpressionVisitor::getObjectFieldValue($object$field);
  137.                     return ! in_array($fieldValue$valueis_scalar($fieldValue));
  138.                 };
  139.             case Comparison::CONTAINS:
  140.                 return static function ($object) use ($field$value) {
  141.                     return strpos(ClosureExpressionVisitor::getObjectFieldValue($object$field), $value) !== false;
  142.                 };
  143.             case Comparison::MEMBER_OF:
  144.                 return static function ($object) use ($field$value): bool {
  145.                     $fieldValues ClosureExpressionVisitor::getObjectFieldValue($object$field);
  146.                     if (! is_array($fieldValues)) {
  147.                         $fieldValues iterator_to_array($fieldValues);
  148.                     }
  149.                     return in_array($value$fieldValuestrue);
  150.                 };
  151.             case Comparison::STARTS_WITH:
  152.                 return static function ($object) use ($field$value): bool {
  153.                     return strpos(ClosureExpressionVisitor::getObjectFieldValue($object$field), $value) === 0;
  154.                 };
  155.             case Comparison::ENDS_WITH:
  156.                 return static function ($object) use ($field$value): bool {
  157.                     return $value === substr(ClosureExpressionVisitor::getObjectFieldValue($object$field), -strlen($value));
  158.                 };
  159.             default:
  160.                 throw new RuntimeException('Unknown comparison operator: ' $comparison->getOperator());
  161.         }
  162.     }
  163.     /**
  164.      * {@inheritDoc}
  165.      */
  166.     public function walkValue(Value $value)
  167.     {
  168.         return $value->getValue();
  169.     }
  170.     /**
  171.      * {@inheritDoc}
  172.      */
  173.     public function walkCompositeExpression(CompositeExpression $expr)
  174.     {
  175.         $expressionList = [];
  176.         foreach ($expr->getExpressionList() as $child) {
  177.             $expressionList[] = $this->dispatch($child);
  178.         }
  179.         switch ($expr->getType()) {
  180.             case CompositeExpression::TYPE_AND:
  181.                 return $this->andExpressions($expressionList);
  182.             case CompositeExpression::TYPE_OR:
  183.                 return $this->orExpressions($expressionList);
  184.             default:
  185.                 throw new RuntimeException('Unknown composite ' $expr->getType());
  186.         }
  187.     }
  188.     /**
  189.      * @param callable[] $expressions
  190.      */
  191.     private function andExpressions(array $expressions): callable
  192.     {
  193.         return static function ($object) use ($expressions): bool {
  194.             foreach ($expressions as $expression) {
  195.                 if (! $expression($object)) {
  196.                     return false;
  197.                 }
  198.             }
  199.             return true;
  200.         };
  201.     }
  202.     /**
  203.      * @param callable[] $expressions
  204.      */
  205.     private function orExpressions(array $expressions): callable
  206.     {
  207.         return static function ($object) use ($expressions): bool {
  208.             foreach ($expressions as $expression) {
  209.                 if ($expression($object)) {
  210.                     return true;
  211.                 }
  212.             }
  213.             return false;
  214.         };
  215.     }
  216. }