Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

[DDC-1637] Collection Filtering API #117

Merged
merged 9 commits into from

4 participants

...trine/Common/Collections/Expr/CompositeExpression.php
((21 lines not shown))
+
+class CompositeExpression implements Expression
+{
+ const TYPE_AND = 'AND';
+ const TYPE_OR = 'OR';
+
+ private $type;
+ private $expressions = array();
+
+ public function __construct($type, array $expressions)
+ {
+ $this->type = $type;
+ foreach ($expressions as $expr) {
+ if ($expr instanceof Value) {
+ throw new \RuntimeException("Values are not supported expressions as children of and/or expressions.");
+ } else if (!($expr instanceof Expression)) {
@stof Collaborator
stof added a note

should be simply if, and the braces are useless around instanceof

@beberlei Owner

i never know the operator precedence of instanceof, so its save to brace it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...octrine/Common/Collections/Expr/ExpressionVisitor.php
((25 lines not shown))
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+abstract class ExpressionVisitor
+{
+ abstract public function walkComparison(Comparison $comparison);
+
+ abstract public function walkValue(Value $value);
+
+ abstract public function walkCompositeExpression(CompositeExpression $expr);
+
+ public function dispatch(Expression $expr)
+ {
+ if ($expr instanceof Comparison) {
+ return $this->walkComparison($expr);
+ } else if ($expr instanceof Value) {
@stof Collaborator
stof added a note

should be if

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...octrine/Common/Collections/Expr/ExpressionVisitor.php
((21 lines not shown))
+
+/**
+ * An Expression visitor walks a graph of expressions and turns them into a
+ * query for the underlying implementation.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+abstract class ExpressionVisitor
+{
+ abstract public function walkComparison(Comparison $comparison);
+
+ abstract public function walkValue(Value $value);
+
+ abstract public function walkCompositeExpression(CompositeExpression $expr);
+
+ public function dispatch(Expression $expr)
@stof Collaborator
stof added a note

missing phpdoc for all methods

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
lib/Doctrine/Common/Collections/Selectable.php
((40 lines not shown))
+{
+ /**
+ * Select all elements from a selectable that match the expression and
+ * return a new collection containing these elements.
+ *
+ * @param Expression $expr
+ * @return Collection
+ */
+ public function select(Expression $expr);
+
+ /**
+ * Return the expression builder.
+ *
+ * @return ExpressionBuilder
+ */
+ public function expr();
@stof Collaborator
stof added a note

should the ArrayCollection implement the interface ?

@beberlei Owner

its WIP ;-) Step by step

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
lib/Doctrine/Common/Collections/Selectable.php
((24 lines not shown))
+/**
+ * Interface for collections that allow efficient filtering with an expression API.
+ *
+ * Goal of this interface is a backend independent method to fetch elements
+ * from a collections. {@link Expression} is crafted in a way that you can
+ * implement queries from both in-memory and database-backed collections.
+ *
+ * For database backed collections this allows very efficient access by
+ * utilizing the query APIs, for example SQL in the ORM. Applications using
+ * this API can implement efficient database access without having to ask the
+ * EntityManager or Repositories.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+interface Selectable
@stof Collaborator
stof added a note

should this interface extend Collection to allow typehinting on it and still exxpecting the Collection interface ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/Common/Collections/Expr/Comparison.php
@@ -0,0 +1,66 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYvalue HOLDERS AND CONTRIBUTORS
@schmittjoh Collaborator

search/replace detected :)

@stof Collaborator
stof added a note

oh, nice one :)

@beberlei Owner

good catch :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.../Common/Collections/Expr/ClosureExpressionVisitor.php
((23 lines not shown))
+ * Walks an expression graph and turns it into a PHP closure.
+ *
+ * This closure can be used with {@Collection#filter()} and is used internally
+ * by {@ArrayCollection#select()}.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+class ClosureExpressionVisitor extends ExpressionVisitor
+{
+ static public function getObjectFieldValue($object, $field)
+ {
+ $accessor = "get" . $field;
+ if (method_exists($object, $accessor)) {
+ return $object->$accessor();
+ } else if ($object instanceof \ArrayAccess) {
@stof Collaborator
stof added a note

simply if :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.../Common/Collections/Expr/ClosureExpressionVisitor.php
((37 lines not shown))
+ return $object->$accessor();
+ } else if ($object instanceof \ArrayAccess) {
+ return $object[$field];
+ }
+ return $object->$field;
+ }
+
+ public function walkComparison(Comparison $comparison)
+ {
+ $field = $comparison->getField();
+ $value = $comparison->getValue()->getValue(); // shortcut for walkValue()
+
+ switch ($comparison->getOperator()) {
+ case Comparison::EQ:
+ case Comparison::IS:
+ return function ($object) use($field, $value) {
@stof Collaborator
stof added a note

IIRC, Sf2 uses a space after use for closures. Not sure about Doctrine.

@guilhermeblanco Owner

Doctrine also uses space after use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.../Common/Collections/Expr/ClosureExpressionVisitor.php
((21 lines not shown))
+
+/**
+ * Walks an expression graph and turns it into a PHP closure.
+ *
+ * This closure can be used with {@Collection#filter()} and is used internally
+ * by {@ArrayCollection#select()}.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+class ClosureExpressionVisitor extends ExpressionVisitor
+{
+ static public function getObjectFieldValue($object, $field)
+ {
+ $accessor = "get" . $field;
+ if (method_exists($object, $accessor)) {
@stof Collaborator
stof added a note

what about isFoo ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.../Common/Collections/Expr/ClosureExpressionVisitor.php
((62 lines not shown))
+ };
+ case Comparison::LTE:
+ return function ($object) use($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value;
+ };
+ case Comparison::GT:
+ return function ($object) use($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value;
+ };
+ case Comparison::GTE:
+ return function ($object) use($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value;
+ };
+ case Comparison::IN:
+ return function ($object) use($field, $value) {
+ return in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
@stof Collaborator
stof added a note

this will break if the field value is a collection instead of an array

@stof Collaborator
stof added a note

ok, I just saw that the value can only be an array

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof stof commented on the diff
lib/Doctrine/Common/Collections/Expr/Comparison.php
((23 lines not shown))
+ * Comparison of a field with a value by the given operator.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+class Comparison implements Expression
+{
+ const EQ = '=';
+ const NEQ = '<>';
+ const LT = '<';
+ const LTE = '<=';
+ const GT = '>';
+ const GTE = '>=';
+ const IS = 'IS';
+ const IN = 'IN';
+ const NIN = 'NIN';
@stof Collaborator
stof added a note

what about CONTAINS to check that the field contains a given value (for fields holding an array of a collection obviously)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@stof
Collaborator

@beberlei any progress here ?

@stof
Collaborator

@beberlei what is the status here ?

@beberlei
Owner

@stof continueing now :)

@stof
Collaborator

@beberlei great news

@beberlei beberlei merged commit 4ac23ae into master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
29 lib/Doctrine/Common/Collections/ArrayCollection.php
@@ -20,6 +20,8 @@
namespace Doctrine\Common\Collections;
use Closure, ArrayIterator;
+use Doctrine\Common\Collections\Expr\Expression;
+use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
/**
* An ArrayCollection is a Collection implementation that wraps a regular PHP array.
@@ -29,7 +31,7 @@
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
-class ArrayCollection implements Collection
+class ArrayCollection implements Collection, Selectable
{
/**
* An array containing the entries of this collection.
@@ -457,4 +459,29 @@ public function slice($offset, $length = null)
{
return array_slice($this->_elements, $offset, $length, true);
}
+
+ /**
+ * Select all elements from a selectable that match the expression and
+ * return a new collection containing these elements.
+ *
+ * @param Expression $expr
+ * @return Collection
+ */
+ public function select(Expression $expr)
+ {
+ $visitor = new ClosureExpressionVisitor();
+
+ return $this->filter($visitor->dispatch($expr));
+ }
+
+ /**
+ * Return the expression builder.
+ *
+ * @return ExpressionBuilder
+ */
+ public function expr()
+ {
+ return new ExpressionBuilder();
+ }
}
+
View
166 lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php
@@ -0,0 +1,166 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections\Expr;
+
+/**
+ * Walks an expression graph and turns it into a PHP closure.
+ *
+ * This closure can be used with {@Collection#filter()} and is used internally
+ * by {@ArrayCollection#select()}.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+class ClosureExpressionVisitor extends ExpressionVisitor
+{
+ /**
+ * Access the field of a given object. This field has to be public directly
+ * or indirectly (through an accessor get* or a magic method, __get, __call).
+ *
+ * is*() is not supported.
+ *
+ * @return mixed
+ */
+ static public function getObjectFieldValue($object, $field)
+ {
+ $accessor = "get" . $field;
+
+ if (method_exists($object, $accessor) || method_exists($object, '__call')) {
+ return $object->$accessor();
+ }
+
+ if ($object instanceof \ArrayAccess) {
+ return $object[$field];
+ }
+
+ return $object->$field;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function walkComparison(Comparison $comparison)
+ {
+ $field = $comparison->getField();
+ $value = $comparison->getValue()->getValue(); // shortcut for walkValue()
+
+ switch ($comparison->getOperator()) {
+ case Comparison::EQ:
+ case Comparison::IS:
+ return function ($object) use ($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value;
+ };
+
+ case Comparison::NEQ:
+ return function ($object) use ($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value;
+ };
+
+ case Comparison::LT:
+ return function ($object) use ($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value;
+ };
+
+ case Comparison::LTE:
+ return function ($object) use ($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value;
+ };
+
+ case Comparison::GT:
+ return function ($object) use ($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value;
+ };
+
+ case Comparison::GTE:
+ return function ($object) use ($field, $value) {
+ return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value;
+ };
+
+ case Comparison::IN:
+ return function ($object) use ($field, $value) {
+ return in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
+ };
+
+ case Comparison::NIN:
+ return function ($object) use ($field, $value) {
+ return ! in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value);
+ };
+
+ default:
+ throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function walkValue(Value $value)
+ {
+ return $value->getValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function walkCompositeExpression(CompositeExpression $expr)
+ {
+ $expressionList = array();
+
+ foreach ($expr->getExpressionList() as $child) {
+ $expressionList[] = $this->dispatch($child);
+ }
+
+ switch($expr->getType()) {
+ case CompositeExpression::TYPE_AND:
+ return $this->andExpressions($expressionList);
+
+ case CompositeExpression::TYPE_OR:
+ return $this->orExpressions($expressionList);
+
+ default:
+ throw new \RuntimeException("Unknown composite " . $expr->getType());
+ }
+ }
+
+ private function andExpressions($expressions)
+ {
+ return function ($object) use ($expressions) {
+ foreach ($expressions as $expression) {
+ if ( ! $expression($object)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ private function orExpressions($expressions)
+ {
+ return function ($object) use ($expressions) {
+ foreach ($expressions as $expression) {
+ if ($expression($object)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+}
+
View
71 lib/Doctrine/Common/Collections/Expr/Comparison.php
@@ -0,0 +1,71 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections\Expr;
+
+/**
+ * Comparison of a field with a value by the given operator.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+class Comparison implements Expression
+{
+ const EQ = '=';
+ const NEQ = '<>';
+ const LT = '<';
+ const LTE = '<=';
+ const GT = '>';
+ const GTE = '>=';
+ const IS = 'IS';
+ const IN = 'IN';
+ const NIN = 'NIN';
@stof Collaborator
stof added a note

what about CONTAINS to check that the field contains a given value (for fields holding an array of a collection obviously)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ private $field;
+ private $op;
+ private $value;
+
+ public function __construct($field, $operator, Value $value)
+ {
+ $this->field = $field;
+ $this->op = $operator;
+ $this->value = $value;
+ }
+
+ public function getField()
+ {
+ return $this->field;
+ }
+
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ public function getOperator()
+ {
+ return $this->op;
+ }
+
+ public function visit(ExpressionVisitor $visitor)
+ {
+ return $visitor->walkComparison($this);
+ }
+}
+
View
72 lib/Doctrine/Common/Collections/Expr/CompositeExpression.php
@@ -0,0 +1,72 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections\Expr;
+
+/**
+ * Expression of Expressions combined by AND or OR operation.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+class CompositeExpression implements Expression
+{
+ const TYPE_AND = 'AND';
+ const TYPE_OR = 'OR';
+
+ private $type;
+ private $expressions = array();
+
+ public function __construct($type, array $expressions)
+ {
+ $this->type = $type;
+
+ foreach ($expressions as $expr) {
+ if ($expr instanceof Value) {
+ throw new \RuntimeException("Values are not supported expressions as children of and/or expressions.");
+ }
+ if ( ! ($expr instanceof Expression)) {
+ throw new \RuntimeException("No expression given to CompositeExpression.");
+ }
+
+ $this->expressions[] = $expr;
+ }
+ }
+
+ /**
+ * Return the list of expressions nested in this composite.
+ *
+ * @return Expression[]
+ */
+ public function getExpressionList()
+ {
+ return $this->expressions;
+ }
+
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function visit(ExpressionVisitor $visitor)
+ {
+ return $visitor->walkCompositeExpression($this);
+ }
+}
+
View
31 lib/Doctrine/Common/Collections/Expr/Expression.php
@@ -0,0 +1,31 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections\Expr;
+
+/**
+ * Expression for the {@link Selectable} interface.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+interface Expression
+{
+ public function visit(ExpressionVisitor $visitor);
+}
+
View
81 lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php
@@ -0,0 +1,81 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections\Expr;
+
+/**
+ * An Expression visitor walks a graph of expressions and turns them into a
+ * query for the underlying implementation.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+abstract class ExpressionVisitor
+{
+ /**
+ * Convert a comparison expression into the target query language output
+ *
+ * @param Comparison $comparison
+ *
+ * @return mixed
+ */
+ abstract public function walkComparison(Comparison $comparison);
+
+ /**
+ * Convert a value expression into the target query language part.
+ *
+ * @param Value $value
+ *
+ * @return mixed
+ */
+ abstract public function walkValue(Value $value);
+
+ /**
+ * Convert a composite expression into the target query language output
+ *
+ * @param CompositeExpression $expr
+ *
+ * @return mixed
+ */
+ abstract public function walkCompositeExpression(CompositeExpression $expr);
+
+ /**
+ * Dispatch walking an expression to the appropriate handler.
+ *
+ * @param Expression
+ *
+ * @return mixed
+ */
+ public function dispatch(Expression $expr)
+ {
+ switch (true) {
+ case ($expr instanceof Comparison):
+ return $this->walkComparison($expr);
+
+ case ($expr instanceof Value):
+ return $this->walkValue($expr);
+
+ case ($expr instanceof CompositeExpression):
+ return $this->walkCompositeExpression($expr);
+
+ default:
+ throw new \RuntimeException("Unknown Expression " . get_class($expr));
+ }
+ }
+}
+
View
41 lib/Doctrine/Common/Collections/Expr/Value.php
@@ -0,0 +1,41 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections\Expr;
+
+class Value implements Expression
+{
+ private $value;
+
+ public function __construct($value)
+ {
+ $this->value = $value;
+ }
+
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ public function visit(ExpressionVisitor $visitor)
+ {
+ return $visitor->walkValue($this);
+ }
+}
+
View
149 lib/Doctrine/Common/Collections/ExpressionBuilder.php
@@ -0,0 +1,149 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYvalue HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYvalue
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections;
+
+use Doctrine\Common\Collections\Expr\Comparison;
+use Doctrine\Common\Collections\Expr\CompositeExpression;
+use Doctrine\Common\Collections\Expr\Value;
+
+/**
+ * Builder for Expressions in the {@link Selectable} interface.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+class ExpressionBuilder
+{
+ /**
+ * @return CompositeExpression
+ */
+ public function andX($x = null)
+ {
+ return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
+ }
+
+ /**
+ * @return CompositeExpression
+ */
+ public function orX($x = null)
+ {
+ return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args());
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function eq($field, $value)
+ {
+ return new Comparison($field, Comparison::EQ, new Value($value));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function gt($field, $value)
+ {
+ return new Comparison($field, Comparison::GT, new Value($value));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function lt($field, $value)
+ {
+ return new Comparison($field, Comparison::LT, new Value($value));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function gte($field, $value)
+ {
+ return new Comparison($field, Comparison::GTE, new Value($value));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function lte($field, $value)
+ {
+ return new Comparison($field, Comparison::LTE, new Value($value));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function neq($field, $value)
+ {
+ return new Comparison($field, Comparison::NEQ, new Value($value));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function isNull($field)
+ {
+ return new Comparison($field, Comparison::IS, new Value(null));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function in($field, array $values)
+ {
+ return new Comparison($field, Comparison::IN, new Value($values));
+ }
+
+ /**
+ * @param string $field
+ * @param mixed $value
+ *
+ * @return Comparison
+ */
+ public function notIn($field, array $values)
+ {
+ return new Comparison($field, Comparison::NIN, new Value($values));
+ }
+}
+
View
57 lib/Doctrine/Common/Collections/Selectable.php
@@ -0,0 +1,57 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\Collections;
+
+use Doctrine\Common\Collections\Expr\Expression;
+
+/**
+ * Interface for collections that allow efficient filtering with an expression API.
+ *
+ * Goal of this interface is a backend independent method to fetch elements
+ * from a collections. {@link Expression} is crafted in a way that you can
+ * implement queries from both in-memory and database-backed collections.
+ *
+ * For database backed collections this allows very efficient access by
+ * utilizing the query APIs, for example SQL in the ORM. Applications using
+ * this API can implement efficient database access without having to ask the
+ * EntityManager or Repositories.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ * @since 2.3
+ */
+interface Selectable
@stof Collaborator
stof added a note

should this interface extend Collection to allow typehinting on it and still exxpecting the Collection interface ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+{
+ /**
+ * Select all elements from a selectable that match the expression and
+ * return a new collection containing these elements.
+ *
+ * @param Expression $expr
+ * @return Collection
+ */
+ public function select(Expression $expr);
+
+ /**
+ * Return the expression builder.
+ *
+ * @return ExpressionBuilder
+ */
+ public function expr();
@stof Collaborator
stof added a note

should the ArrayCollection implement the interface ?

@beberlei Owner

its WIP ;-) Step by step

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+}
+
View
161 tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php
@@ -0,0 +1,161 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Tests\Common\Collections;
+
+use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
+use Doctrine\Common\Collections\ExpressionBuilder;
+
+/**
+ * @group DDC-1637
+ */
+class ClosureExpressionVisitorTest extends \PHPUnit_Framework_TestCase
+{
+ private $visitor;
+ private $builder;
+
+ public function setUp()
+ {
+ $this->visitor = new ClosureExpressionVisitor();
+ $this->builder = new ExpressionBuilder();
+ }
+
+ public function testWalkEqualsComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->eq("foo", 1));
+
+ $this->assertTrue($closure(new TestObject(1)));
+ $this->assertFalse($closure(new TestObject(2)));
+ }
+
+ public function testWalkNotEqualsComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->neq("foo", 1));
+
+ $this->assertFalse($closure(new TestObject(1)));
+ $this->assertTrue($closure(new TestObject(2)));
+ }
+
+ public function testWalkLessThanComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->lt("foo", 1));
+
+ $this->assertFalse($closure(new TestObject(1)));
+ $this->assertTrue($closure(new TestObject(0)));
+ }
+
+ public function testWalkLessThanEqualsComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->lte("foo", 1));
+
+ $this->assertFalse($closure(new TestObject(2)));
+ $this->assertTrue($closure(new TestObject(1)));
+ $this->assertTrue($closure(new TestObject(0)));
+ }
+
+ public function testWalkGreaterThanEqualsComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->gte("foo", 1));
+
+ $this->assertTrue($closure(new TestObject(2)));
+ $this->assertTrue($closure(new TestObject(1)));
+ $this->assertFalse($closure(new TestObject(0)));
+ }
+
+ public function testWalkGreaterThanComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->gt("foo", 1));
+
+ $this->assertTrue($closure(new TestObject(2)));
+ $this->assertFalse($closure(new TestObject(1)));
+ $this->assertFalse($closure(new TestObject(0)));
+ }
+
+ public function testWalkInComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->in("foo", array(1, 2, 3)));
+
+ $this->assertTrue($closure(new TestObject(2)));
+ $this->assertTrue($closure(new TestObject(1)));
+ $this->assertFalse($closure(new TestObject(0)));
+ }
+
+ public function testWalkNotInComparison()
+ {
+ $closure = $this->visitor->walkComparison($this->builder->notIn("foo", array(1, 2, 3)));
+
+ $this->assertFalse($closure(new TestObject(1)));
+ $this->assertFalse($closure(new TestObject(2)));
+ $this->assertTrue($closure(new TestObject(0)));
+ $this->assertTrue($closure(new TestObject(4)));
+ }
+
+ public function testWalkAndCompositeExpression()
+ {
+ $closure = $this->visitor->walkCompositeExpression(
+ $this->builder->andX(
+ $this->builder->eq("foo", 1),
+ $this->builder->eq("bar", 1)
+ )
+ );
+
+ $this->assertTrue($closure(new TestObject(1, 1)));
+ $this->assertFalse($closure(new TestObject(1, 0)));
+ $this->assertFalse($closure(new TestObject(0, 1)));
+ $this->assertFalse($closure(new TestObject(0, 0)));
+ }
+
+ public function testWalkOrCompositeExpression()
+ {
+ $closure = $this->visitor->walkCompositeExpression(
+ $this->builder->orX(
+ $this->builder->eq("foo", 1),
+ $this->builder->eq("bar", 1)
+ )
+ );
+
+ $this->assertTrue($closure(new TestObject(1, 1)));
+ $this->assertTrue($closure(new TestObject(1, 0)));
+ $this->assertTrue($closure(new TestObject(0, 1)));
+ $this->assertFalse($closure(new TestObject(0, 0)));
+ }
+}
+
+class TestObject
+{
+ private $foo;
+ private $bar;
+
+ public function __construct($foo = null, $bar = null)
+ {
+ $this->foo = $foo;
+ $this->bar = $bar;
+ }
+
+ public function getFoo()
+ {
+ return $this->foo;
+ }
+
+ public function getBar()
+ {
+ return $this->bar;
+ }
+}
+
View
21 tests/Doctrine/Tests/Common/Collections/CollectionTest.php
@@ -192,4 +192,23 @@ public function testSlice()
$slice = $this->_coll->slice(1, 1);
$this->assertEquals(array(1 => 'two'), $slice);
}
-}
+
+ /**
+ * @group DDC-1637
+ */
+ public function testSelect()
+ {
+ $std1 = new \stdClass();
+ $std1->foo = "bar";
+ $this->_coll[] = $std1;
+
+ $std2 = new \stdClass();
+ $std2->foo = "baz";
+ $this->_coll[] = $std2;
+
+ $col = $this->_coll->select($this->_coll->expr()->eq("foo", "bar"));
+ $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $col);
+ $this->assertNotSame($col, $this->_coll);
+ $this->assertEquals(1, count($col));
+ }
+}
View
114 tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php
@@ -0,0 +1,114 @@
+<?php
+namespace Doctrine\Tests\Common\Collections;
+
+use Doctrine\Common\Collections\ExpressionBuilder;
+use Doctrine\Common\Collections\Expr\Comparison;
+use Doctrine\Common\Collections\Expr\CompositeExpression;
+
+/**
+ * @group DDC-1637
+ */
+class ExpressionBuilderTest extends \PHPUnit_Framework_TestCase
+{
+ private $builder;
+
+ public function setUp()
+ {
+ $this->builder = new ExpressionBuilder();
+ }
+
+ public function testAndX()
+ {
+ $expr = $this->builder->andX($this->builder->eq("a", "b"));
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\CompositeExpression", $expr);
+ $this->assertEquals(CompositeExpression::TYPE_AND, $expr->getType());
+ }
+
+ public function testOrX()
+ {
+ $expr = $this->builder->orX($this->builder->eq("a", "b"));
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\CompositeExpression", $expr);
+ $this->assertEquals(CompositeExpression::TYPE_OR, $expr->getType());
+ }
+
+ public function testInvalidAndXArgument()
+ {
+ $this->setExpectedException("RuntimeException");
+ $this->builder->andX("foo");
+ }
+
+ public function testEq()
+ {
+ $expr = $this->builder->eq("a", "b");
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::EQ, $expr->getOperator());
+ }
+
+ public function testNeq()
+ {
+ $expr = $this->builder->neq("a", "b");
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::NEQ, $expr->getOperator());
+ }
+
+ public function testLt()
+ {
+ $expr = $this->builder->lt("a", "b");
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::LT, $expr->getOperator());
+ }
+
+ public function testGt()
+ {
+ $expr = $this->builder->gt("a", "b");
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::GT, $expr->getOperator());
+ }
+
+ public function testGte()
+ {
+ $expr = $this->builder->gte("a", "b");
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::GTE, $expr->getOperator());
+ }
+
+ public function testLte()
+ {
+ $expr = $this->builder->lte("a", "b");
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::LTE, $expr->getOperator());
+ }
+
+ public function testIn()
+ {
+ $expr = $this->builder->in("a", array("b"));
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::IN, $expr->getOperator());
+ }
+
+ public function testNotIn()
+ {
+ $expr = $this->builder->notIn("a", array("b"));
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::NIN, $expr->getOperator());
+ }
+
+ public function testIsNull()
+ {
+ $expr = $this->builder->isNull("a");
+
+ $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr);
+ $this->assertEquals(Comparison::IS, $expr->getOperator());
+ }
+}
+
Something went wrong with that request. Please try again.