Skip to content

Commit

Permalink
Merge pull request #500 from teohhanhui/search-property-multi-values
Browse files Browse the repository at this point in the history
Allow multiple values for a property in search filter when using "exact" strategy
  • Loading branch information
dunglas committed Apr 21, 2016
2 parents 8b8fccf + 697439e commit 7833be3
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 26 deletions.
14 changes: 13 additions & 1 deletion features/crud.feature
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Feature: Create-Retrieve-Update-Delete
"hydra:totalItems": 1,
"hydra:search": {
"@type": "hydra:IriTemplate",
"hydra:template": "/dummies{?id,name,alias,description,relatedDummy.name,relatedDummies,relatedDummies[],order[id],order[name],order[relatedDummy.symfony],dummyDate[before],dummyDate[after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[after],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte]}",
"hydra:template": "/dummies{?id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],order[id],order[name],order[relatedDummy.symfony],dummyDate[before],dummyDate[after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[after],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte]}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
Expand All @@ -122,6 +122,12 @@ Feature: Create-Retrieve-Update-Delete
"property": "id",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "id[]",
"property": "id",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "name",
Expand All @@ -146,6 +152,12 @@ Feature: Create-Retrieve-Update-Delete
"property": "relatedDummy.name",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummy.name[]",
"property": "relatedDummy.name",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummies",
Expand Down
84 changes: 59 additions & 25 deletions src/Bridge/Doctrine/Orm/Filter/SearchFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,37 @@ public function apply(QueryBuilder $queryBuilder, string $resourceClass, string
}

if ($metadata->hasField($field)) {
if (!is_string($value)) {
$values = (array) $value;
foreach ($values as $k => $v) {
if (!is_int($k) || !is_string($v)) {
unset($values[$k]);
}
}
$values = array_values($values);

if (empty($values)) {
continue;
}

if ('id' === $field) {
$value = $this->getFilterValueFromUrl($value);
$values = array_map([$this, 'getFilterValueFromUrl'], $values);
}

$this->addWhereByStrategy($this->properties[$property] ?? self::STRATEGY_EXACT, $queryBuilder, $alias, $field, $value);
$strategy = $this->properties[$property] ?? self::STRATEGY_EXACT;

if (1 === count($values)) {
$this->addWhereByStrategy($strategy, $queryBuilder, $alias, $field, $values[0]);
} else {
if (self::STRATEGY_EXACT !== $strategy) {
continue;
}

$valueParameter = QueryNameGenerator::generateParameterName($field);

$queryBuilder
->andWhere(sprintf('%s.%s IN (:%s)', $alias, $field, $valueParameter))
->setParameter($valueParameter, $values);
}
} elseif ($metadata->hasAssociation($field)) {
$values = (array) $value;
foreach ($values as $k => $v) {
Expand Down Expand Up @@ -167,43 +189,47 @@ public function apply(QueryBuilder $queryBuilder, string $resourceClass, string
* @param string $value
*
* @throws InvalidArgumentException If strategy does not exist
*
* @return string
*/
private function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, string $alias, string $field, string $value) : string
private function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, string $alias, string $field, string $value)
{
$valueParameter = QueryNameGenerator::generateParameterName($field);

switch ($strategy) {
case null:
case self::STRATEGY_EXACT:
return $queryBuilder
$queryBuilder
->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter))
->setParameter($valueParameter, $value);
break;

case self::STRATEGY_PARTIAL:
return $queryBuilder
$queryBuilder
->andWhere(sprintf('%s.%s LIKE :%s', $alias, $field, $valueParameter))
->setParameter($valueParameter, sprintf('%%%s%%', $value));
break;

case self::STRATEGY_START:
return $queryBuilder
$queryBuilder
->andWhere(sprintf('%s.%s LIKE :%s', $alias, $field, $valueParameter))
->setParameter($valueParameter, sprintf('%s%%', $value));
break;

case self::STRATEGY_END:
return $queryBuilder
$queryBuilder
->andWhere(sprintf('%s.%s LIKE :%s', $alias, $field, $valueParameter))
->setParameter($valueParameter, sprintf('%%%s', $value));
break;

case self::STRATEGY_WORD_START:
return $queryBuilder
$queryBuilder
->andWhere(sprintf('%1$s.%2$s LIKE :%3$s_1 OR %1$s.%2$s LIKE :%3$s_2', $alias, $field, $valueParameter))
->setParameter(sprintf('%s_1', $valueParameter), sprintf('%s%%', $value))
->setParameter(sprintf('%s_2', $valueParameter), sprintf('%% %s%%', $value));
}
break;

throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy));
default:
throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy));
}
}

/**
Expand Down Expand Up @@ -233,22 +259,30 @@ public function getDescription(string $resourceClass) : array
}

if ($metadata->hasField($field)) {
$description[$property] = [
'property' => $property,
'type' => $metadata->getTypeOfField($field),
'required' => false,
'strategy' => isset($this->properties[$property]) ? $this->properties[$property] : self::STRATEGY_EXACT,
$typeOfField = $metadata->getTypeOfField($field);
$strategy = $this->properties[$property] ?? self::STRATEGY_EXACT;
$filterParameterNames = [
$property,
];
if (self::STRATEGY_EXACT === $strategy) {
$filterParameterNames[] = $property.'[]';
}
foreach ($filterParameterNames as $filterParameterName) {
$description[$filterParameterName] = [
'property' => $property,
'type' => $typeOfField,
'required' => false,
'strategy' => $strategy,
];
}
} elseif ($metadata->hasAssociation($field)) {
$association = $field;
$description[$property] = [
'property' => $property,
'type' => 'iri',
'required' => false,
'strategy' => self::STRATEGY_EXACT,
$filterParameterNames = [
$property,
$property.'[]',
];
if ($metadata->hasAssociation($association)) {
$description[$property.'[]'] = [
foreach ($filterParameterNames as $filterParameterName) {
$description[$filterParameterName] = [
'property' => $property,
'type' => 'iri',
'required' => false,
Expand Down
102 changes: 102 additions & 0 deletions tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,54 +130,108 @@ public function testGetDescription()
'required' => false,
'strategy' => 'exact',
],
'id[]' => [
'property' => 'id',
'type' => 'integer',
'required' => false,
'strategy' => 'exact',
],
'name' => [
'property' => 'name',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'name[]' => [
'property' => 'name',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'alias' => [
'property' => 'alias',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'alias[]' => [
'property' => 'alias',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'description' => [
'property' => 'description',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'description[]' => [
'property' => 'description',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'dummy' => [
'property' => 'dummy',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'dummy[]' => [
'property' => 'dummy',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'dummyDate' => [
'property' => 'dummyDate',
'type' => 'datetime',
'required' => false,
'strategy' => 'exact',
],
'dummyDate[]' => [
'property' => 'dummyDate',
'type' => 'datetime',
'required' => false,
'strategy' => 'exact',
],
'dummyPrice' => [
'property' => 'dummyPrice',
'type' => 'decimal',
'required' => false,
'strategy' => 'exact',
],
'dummyPrice[]' => [
'property' => 'dummyPrice',
'type' => 'decimal',
'required' => false,
'strategy' => 'exact',
],
'jsonData' => [
'property' => 'jsonData',
'type' => 'json_array',
'required' => false,
'strategy' => 'exact',
],
'jsonData[]' => [
'property' => 'jsonData',
'type' => 'json_array',
'required' => false,
'strategy' => 'exact',
],
'nameConverted' => [
'property' => 'nameConverted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'nameConverted[]' => [
'property' => 'nameConverted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
], $filter->getDescription($this->resourceClass));

$filter = new SearchFilter(
Expand Down Expand Up @@ -205,48 +259,96 @@ public function testGetDescription()
'required' => false,
'strategy' => 'exact',
],
'id[]' => [
'property' => 'id',
'type' => 'integer',
'required' => false,
'strategy' => 'exact',
],
'name' => [
'property' => 'name',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'name[]' => [
'property' => 'name',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'alias' => [
'property' => 'alias',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'alias[]' => [
'property' => 'alias',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'dummy' => [
'property' => 'dummy',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'dummy[]' => [
'property' => 'dummy',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'dummyDate' => [
'property' => 'dummyDate',
'type' => 'datetime',
'required' => false,
'strategy' => 'exact',
],
'dummyDate[]' => [
'property' => 'dummyDate',
'type' => 'datetime',
'required' => false,
'strategy' => 'exact',
],
'jsonData' => [
'property' => 'jsonData',
'type' => 'json_array',
'required' => false,
'strategy' => 'exact',
],
'jsonData[]' => [
'property' => 'jsonData',
'type' => 'json_array',
'required' => false,
'strategy' => 'exact',
],
'nameConverted' => [
'property' => 'nameConverted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'nameConverted[]' => [
'property' => 'nameConverted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
],
'relatedDummies.dummyDate' => [
'property' => 'relatedDummies.dummyDate',
'type' => 'datetime',
'required' => false,
'strategy' => 'exact',
],
'relatedDummies.dummyDate[]' => [
'property' => 'relatedDummies.dummyDate',
'type' => 'datetime',
'required' => false,
'strategy' => 'exact',
],
'relatedDummy' => [
'property' => 'relatedDummy',
'type' => 'iri',
Expand Down

0 comments on commit 7833be3

Please sign in to comment.