Skip to content

Commit

Permalink
Merge pull request #196 from nextras/or
Browse files Browse the repository at this point in the history
Or [closes #105]
  • Loading branch information
hrach committed Jan 21, 2017
2 parents 79c9a8a + 494ff93 commit 84eb2af
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 93 deletions.
46 changes: 44 additions & 2 deletions doc/collection.texy
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ $orm->books
Filtering
=========

Each collection can be filtered by array of conditions. These are passed as the first parameter of `findBy()` method. Array consists of entity property names and values. Keys can contain optional operator. Default operator is equality. Let's see the example.
Each collection can be filtered by array of conditions. These are passed as a parameter of `findBy()` method. Array consists of entity property names and values. Keys can contain optional operator. Default operator is equality. Let's see the example.

/--php
$books = $orm->books->findBy([
Expand All @@ -58,8 +58,50 @@ $orm->books->findBy(['this->author->name' => 'Jon Snow']);
$orm->books->findBy(['this->translator->name!=' => 'Jon Snow']);
\--

The described syntax may be expanded to support `OR` logical conjunction. Wrap the conditions in another array and put the operator `ICollection::OR` as a first value.

/--php
// finds all books which were authored od translated by one specific person
$books = $orm->books->findBy([ICollection::OR, [
'author' => $person->id,
'translator' => $person->id,
]]);
\--

You may combine the query array structure, use the same syntax repeatedly.

/--php
// find all man older than 10 years and woman younger than 10 years
$books = $orm->author->findBy([ICollection::OR, [
[ICollection::AND, [
'age>=' => 10,
'sex' => 'male',
]],
[ICollection::AND, [
'age<=' => 10,
'sex' => 'female',
]],
]]);
\--

The previous example can be shortened because the `AND` is the default logical conjunction.

/--php
// find all man older than 10 years and woman younger than 12 years
$books = $orm->author->findBy([ICollection::OR, [
[
'age>=' => 10,
'gender' => 'male',
],
[
'age<=' => 12,
'gender' => 'female',
],
]]);
\--

There are few restrictions:
- Filtering does not implement any support for aggregation. If you need some more complex queries, proxy your methods to mapper layer. See more in [Repository chapter | repository].
- Filtering does not support for any kind of aggregation. If you need some more complex queries, proxy your methods to mapper layer. See more in [Repository chapter | repository].
- Traversing is currently supported only over the persisted properties. Your own virtual properties have not been implemented yet.

--------------
Expand Down
4 changes: 1 addition & 3 deletions src/Collection/ArrayCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,7 @@ public function getBy(array $where)
public function findBy(array $where): ICollection
{
$collection = clone $this;
foreach ($where as $column => $value) {
$collection->collectionFilter[] = $this->getHelper()->createFilter($column, $value);
}
$collection->collectionFilter[] = $this->getHelper()->createFilter($where);
return $collection;
}

Expand Down
74 changes: 61 additions & 13 deletions src/Collection/Helpers/ArrayCollectionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Nextras\Orm\Mapper\IMapper;
use Nextras\Orm\Model\IModel;
use Nextras\Orm\Model\MetadataStorage;
use Nextras\Orm\NotSupportedException;
use Nextras\Orm\Relationships\IRelationshipCollection;


Expand All @@ -43,10 +44,52 @@ public function __construct(IModel $model, IMapper $mapper)
}


public function createFilter(array $conditions): Closure
{
if (!isset($conditions[0])) {
$operator = ICollection::AND;
} else {
$operator = $conditions[0];
$conditions = $conditions[1];
}

$callbacks = [];
foreach ($conditions as $expression => $value) {
if (is_int($expression)) {
$callbacks[] = $this->createFilter($value);
} else {
$callbacks[] = $this->createExpressionFilter($expression, $value);
}
}

if ($operator === ICollection::AND) {
return function ($value) use ($callbacks) {
foreach ($callbacks as $callback) {
if (!$callback($value)) {
return false;
}
}
return true;
};
} elseif ($operator === ICollection::OR) {
return function ($value) use ($callbacks) {
foreach ($callbacks as $callback) {
if ($callback($value)) {
return true;
}
}
return false;
};
} else {
throw new NotSupportedException("Operator $operator is not supported");
}
}


/**
* @param mixed $value
*/
public function createFilter(string $condition, $value): Closure
public function createExpressionFilter(string $condition, $value): Closure
{
list($chain, $operator, $sourceEntity) = ConditionParserHelper::parseCondition($condition);
$sourceEntityMeta = $this->metadataStorage->get($sourceEntity ?: $this->mapper->getRepository()->getEntityClassNames()[0]);
Expand All @@ -55,47 +98,52 @@ public function createFilter(string $condition, $value): Closure
$value = $value->getValue('id');
}

$comparator = $this->createComparator($operator, is_array($value));
return $this->createFilterEvaluator($chain, $comparator, $sourceEntityMeta, $value);
}


private function createComparator(string $operator, bool $isArray): Closure
{
if ($operator === ConditionParserHelper::OPERATOR_EQUAL) {
if (is_array($value)) {
$predicate = function ($property, $value) {
if ($isArray) {
return function ($property, $value) {
return in_array($property, $value, true);
};
} else {
$predicate = function ($property, $value) {
return function ($property, $value) {
return $property === $value;
};
}
} elseif ($operator === ConditionParserHelper::OPERATOR_NOT_EQUAL) {
if (is_array($value)) {
$predicate = function ($property, $value) {
if ($isArray) {
return function ($property, $value) {
return !in_array($property, $value, true);
};
} else {
$predicate = function ($property, $value) {
return function ($property, $value) {
return $property !== $value;
};
}
} elseif ($operator === ConditionParserHelper::OPERATOR_GREATER) {
$predicate = function ($property, $value) {
return function ($property, $value) {
return $property > $value;
};
} elseif ($operator === ConditionParserHelper::OPERATOR_EQUAL_OR_GREATER) {
$predicate = function ($property, $value) {
return function ($property, $value) {
return $property >= $value;
};
} elseif ($operator === ConditionParserHelper::OPERATOR_SMALLER) {
$predicate = function ($property, $value) {
return function ($property, $value) {
return $property < $value;
};
} elseif ($operator === ConditionParserHelper::OPERATOR_EQUAL_OR_SMALLER) {
$predicate = function ($property, $value) {
return function ($property, $value) {
return $property <= $value;
};
} else {
throw new InvalidArgumentException();
}

return $this->createFilterEvaluator($chain, $predicate, $sourceEntityMeta, $value);
}


Expand Down
6 changes: 6 additions & 0 deletions src/Collection/ICollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ interface ICollection extends IteratorAggregate, Countable
/** @const desc order */
const DESC = 'DESC';

/** @const and logic conjuction */
const AND = 'AND';

/** @const or logic conjunction */
const OR = 'OR';


/**
* Returns IEntity filtered by conditions.
Expand Down
7 changes: 1 addition & 6 deletions src/Mapper/Dbal/DbalCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,7 @@ public function getBy(array $where)
public function findBy(array $where): ICollection
{
$collection = clone $this;
$parser = $collection->getParser();

foreach ($where as $column => $value) {
$parser->processWhereExpression($column, $value, $collection->queryBuilder, $collection->distinct);
}

$collection->getParser()->processWhereExpressions($where, $collection->queryBuilder, $collection->distinct);
return $collection;
}

Expand Down

0 comments on commit 84eb2af

Please sign in to comment.