From 07f8dfb1c5cab9e9a78c91580eb7666a972656df Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Thu, 5 Jul 2018 12:27:28 +0300 Subject: [PATCH] Made `ObjectCollection::matching()` criteria expressions to behave more like in Twig --- CHANGELOG.md | 10 +- .../Collection/ObjectExpressionVisitor.php | 152 ++++++++++++++++++ .../Framework/Object/ObjectCollection.php | 35 ++++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d9cf2affbb..3b13ac1bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ -# v1.5.0-beta.1 +# v1.5.0-beta.2 ## mm/dd/2018 +1. [](#improved) + * Made `ObjectCollection::matching()` criteria expressions to behave more like in Twig +1. [](#bugfix) + * Fixed regression in 1.5.0-beta.1 blueprint extend and embed + +# v1.5.0-beta.1 +## 06/19/2018 + 1. [](#new) * Set minimum requirements to [PHP 5.6.4](https://getgrav.org/blog/raising-php-requirements-2018) * Updated Doctrine Collections to 1.4 diff --git a/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php new file mode 100644 index 0000000000..40b1ffa171 --- /dev/null +++ b/system/src/Grav/Framework/Object/Collection/ObjectExpressionVisitor.php @@ -0,0 +1,152 @@ +{$accessor}(); + } + + return null; + } + + /** + * Helper for sorting arrays of objects based on multiple fields + orientations. + * + * @param string $name + * @param int $orientation + * @param \Closure $next + * + * @return \Closure + */ + public static function sortByField($name, $orientation = 1, \Closure $next = null) + { + if (!$next) { + $next = function() { + return 0; + }; + } + + return function ($a, $b) use ($name, $next, $orientation) { + $aValue = static::getObjectFieldValue($a, $name); + $bValue = static::getObjectFieldValue($b, $name); + + if ($aValue === $bValue) { + return $next($a, $b); + } + + return (($aValue > $bValue) ? 1 : -1) * $orientation; + }; + } + + /** + * {@inheritDoc} + */ + public function walkComparison(Comparison $comparison) + { + $field = $comparison->getField(); + $value = $comparison->getValue()->getValue(); // shortcut for walkValue() + + switch ($comparison->getOperator()) { + case Comparison::EQ: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) === $value; + }; + + case Comparison::NEQ: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) !== $value; + }; + + case Comparison::LT: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) < $value; + }; + + case Comparison::LTE: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) <= $value; + }; + + case Comparison::GT: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) > $value; + }; + + case Comparison::GTE: + return function ($object) use ($field, $value) { + return static::getObjectFieldValue($object, $field) >= $value; + }; + + case Comparison::IN: + return function ($object) use ($field, $value) { + return \in_array(static::getObjectFieldValue($object, $field), $value, true); + }; + + case Comparison::NIN: + return function ($object) use ($field, $value) { + return !\in_array(static::getObjectFieldValue($object, $field), $value, true); + }; + + case Comparison::CONTAINS: + return function ($object) use ($field, $value) { + return false !== strpos(static::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::MEMBER_OF: + return function ($object) use ($field, $value) { + $fieldValues = static::getObjectFieldValue($object, $field); + if (!is_array($fieldValues)) { + $fieldValues = iterator_to_array($fieldValues); + } + return \in_array($value, $fieldValues, true); + }; + + case Comparison::STARTS_WITH: + return function ($object) use ($field, $value) { + return 0 === strpos(static::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::ENDS_WITH: + return function ($object) use ($field, $value) { + return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value)); + }; + + + default: + throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator()); + } + } +} diff --git a/system/src/Grav/Framework/Object/ObjectCollection.php b/system/src/Grav/Framework/Object/ObjectCollection.php index a637bf1e00..1d0ff30ce1 100644 --- a/system/src/Grav/Framework/Object/ObjectCollection.php +++ b/system/src/Grav/Framework/Object/ObjectCollection.php @@ -8,9 +8,11 @@ namespace Grav\Framework\Object; +use Doctrine\Common\Collections\Criteria; use Grav\Framework\Collection\ArrayCollection; use Grav\Framework\Object\Access\NestedPropertyCollectionTrait; use Grav\Framework\Object\Base\ObjectCollectionTrait; +use Grav\Framework\Object\Collection\ObjectExpressionVisitor; use Grav\Framework\Object\Interfaces\NestedObjectInterface; use Grav\Framework\Object\Interfaces\ObjectCollectionInterface; @@ -36,6 +38,39 @@ public function __construct(array $elements = [], $key = null) $this->setKey($key); } + /** + * {@inheritDoc} + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + $filtered = $this->getElements(); + + if ($expr) { + $visitor = new ObjectExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $filtered = array_filter($filtered, $filter); + } + + if ($orderings = $criteria->getOrderings()) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ObjectExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next); + } + + uasort($filtered, $next); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int)$offset, $length); + } + + return $this->createFrom($filtered); + } + protected function getElements() { return $this->toArray();