Skip to content

Commit cf8b7aa

Browse files
committed
Merge pull request #289 from ahlee2326/search-filter-improvements
Add `start`, `end` and `word_start` matching startegy
2 parents 1d785bc + 30a7928 commit cf8b7aa

File tree

13 files changed

+1935
-1055
lines changed

13 files changed

+1935
-1055
lines changed

Doctrine/Orm/Filter/SearchFilter.php

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Doctrine\ORM\QueryBuilder;
1616
use Dunglas\ApiBundle\Api\IriConverterInterface;
1717
use Dunglas\ApiBundle\Api\ResourceInterface;
18+
use Dunglas\ApiBundle\Exception\InvalidArgumentException;
1819
use Symfony\Component\HttpFoundation\Request;
1920
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
2021

@@ -33,6 +34,18 @@ class SearchFilter extends AbstractFilter
3334
* @var string The value must be contained in the field.
3435
*/
3536
const STRATEGY_PARTIAL = 'partial';
37+
/**
38+
* @var string Finds fields that are starting with the value.
39+
*/
40+
const STRATEGY_START = 'start';
41+
/**
42+
* @var string Finds fields that are ending with the value.
43+
*/
44+
const STRATEGY_END = 'end';
45+
/**
46+
* @var string Finds fields that are starting with the word.
47+
*/
48+
const STRATEGY_WORD_START = 'word_start';
3649

3750
/**
3851
* @var IriConverterInterface
@@ -74,17 +87,14 @@ public function apply(ResourceInterface $resource, QueryBuilder $queryBuilder, R
7487
continue;
7588
}
7689

77-
$partial = null !== $this->properties && self::STRATEGY_PARTIAL === $this->properties[$property];
78-
7990
if (isset($fieldNames[$property])) {
8091
if ('id' === $property) {
8192
$value = $this->getFilterValueFromUrl($value);
8293
}
8394

84-
$queryBuilder
85-
->andWhere(sprintf('o.%1$s LIKE :%1$s', $property))
86-
->setParameter($property, $partial ? sprintf('%%%s%%', $value) : $value)
87-
;
95+
$strategy = null !== $this->properties ? $this->properties[$property] : self::STRATEGY_EXACT;
96+
97+
$this->addWhereByStrategy($strategy, $queryBuilder, $property, $value);
8898
} elseif ($metadata->isSingleValuedAssociation($property)
8999
|| $metadata->isCollectionValuedAssociation($property)
90100
) {
@@ -93,12 +103,62 @@ public function apply(ResourceInterface $resource, QueryBuilder $queryBuilder, R
93103
$queryBuilder
94104
->join(sprintf('o.%s', $property), $property)
95105
->andWhere(sprintf('%1$s.id = :%1$s', $property))
96-
->setParameter($property, $partial ? sprintf('%%%s%%', $value) : $value)
106+
->setParameter($property, $value)
97107
;
98108
}
99109
}
100110
}
101111

112+
/**
113+
* Adds where clause according to the strategy.
114+
*
115+
* @param string $strategy
116+
* @param QueryBuilder $queryBuilder
117+
* @param string $property
118+
* @param string $value
119+
*
120+
* @return string
121+
*
122+
* @throws InvalidArgumentException If strategy does not exist
123+
*/
124+
private function addWhereByStrategy($strategy, QueryBuilder $queryBuilder, $property, $value)
125+
{
126+
switch ($strategy) {
127+
case self::STRATEGY_EXACT:
128+
return $queryBuilder
129+
->andWhere(sprintf('o.%1$s = :%1$s', $property))
130+
->setParameter($property, $value)
131+
;
132+
133+
case self::STRATEGY_PARTIAL:
134+
return $queryBuilder
135+
->andWhere(sprintf('o.%1$s LIKE :%1$s', $property))
136+
->setParameter($property, sprintf('%%%s%%', $value))
137+
;
138+
139+
case self::STRATEGY_START:
140+
return $queryBuilder
141+
->andWhere(sprintf('o.%1$s LIKE :%1$s', $property))
142+
->setParameter($property, sprintf('%s%%', $value))
143+
;
144+
145+
case self::STRATEGY_END:
146+
return $queryBuilder
147+
->andWhere(sprintf('o.%1$s LIKE :%1$s', $property))
148+
->setParameter($property, sprintf('%%%s', $value))
149+
;
150+
151+
case self::STRATEGY_WORD_START:
152+
return $queryBuilder
153+
->andWhere(sprintf('o.%1$s LIKE :%1$s_1 OR o.%1$s LIKE :%1$s_2', $property))
154+
->setParameter(sprintf('%s_1', $property), sprintf('%s%%', $value))
155+
->setParameter(sprintf('%s_2', $property), sprintf('%% %s%%', $value))
156+
;
157+
}
158+
159+
throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy));
160+
}
161+
102162
/**
103163
* {@inheritdoc}
104164
*/

Tests/Behat/TestBundle/Entity/Dummy.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ class Dummy
5252
* @var array foo
5353
*/
5454
private $foo;
55+
/**
56+
* @var string A short description of the item.
57+
*
58+
* @ORM\Column(nullable=true)
59+
* @Iri("https://schema.org/description")
60+
*/
61+
public $description;
5562
/**
5663
* @var string A dummy.
5764
*
@@ -121,6 +128,16 @@ public function getAlias()
121128
return $this->alias;
122129
}
123130

131+
public function setDescription($description)
132+
{
133+
$this->description = $description;
134+
}
135+
136+
public function getDescription()
137+
{
138+
return $this->description;
139+
}
140+
124141
public function hasRole($role)
125142
{
126143
}

features/bootstrap/FeatureContext.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,13 @@ public function dropDatabase()
6666
*/
6767
public function thereIsDummyObjects($nb)
6868
{
69+
$descriptions = ['Smart dummy.', 'Not so smart dummy.'];
70+
6971
for ($i = 1; $i <= $nb; ++$i) {
7072
$dummy = new Dummy();
7173
$dummy->setName('Dummy #'.$i);
7274
$dummy->setAlias('Alias #'.($nb - $i));
75+
$dummy->setDescription($i % 2 ? $descriptions[0] : $descriptions[1]);
7376

7477
$this->manager->persist($dummy);
7578
}
@@ -82,12 +85,16 @@ public function thereIsDummyObjects($nb)
8285
*/
8386
public function thereIsDummyObjectsWithDummyDate($nb)
8487
{
88+
$descriptions = ['Smart dummy.', 'Not so smart dummy.'];
89+
8590
for ($i = 1; $i <= $nb; ++$i) {
8691
$date = new \DateTime(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC'));
8792

8893
$dummy = new Dummy();
8994
$dummy->setName('Dummy #'.$i);
9095
$dummy->setAlias('Alias #'.($nb - $i));
96+
$dummy->setDescription($i % 2 ? $descriptions[0] : $descriptions[1]);
97+
9198
// Last Dummy has a null date
9299
if ($nb !== $i) {
93100
$dummy->setDummyDate($date);

features/crud.feature

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Feature: Create-Retrieve-Update-Delete
2929
"@type": "Dummy",
3030
"name": "My Dummy",
3131
"alias": null,
32+
"description": null,
3233
"dummyDate": "2015-03-01T10:00:00+00:00",
3334
"jsonData": {
3435
"key": [
@@ -56,6 +57,7 @@ Feature: Create-Retrieve-Update-Delete
5657
"@type": "Dummy",
5758
"name": "My Dummy",
5859
"alias": null,
60+
"description": null,
5961
"dummyDate": "2015-03-01T10:00:00+00:00",
6062
"jsonData": {
6163
"key": [
@@ -91,6 +93,7 @@ Feature: Create-Retrieve-Update-Delete
9193
"@type":"Dummy",
9294
"name":"My Dummy",
9395
"alias": null,
96+
"description": null,
9497
"dummyDate": "2015-03-01T10:00:00+00:00",
9598
"jsonData": {
9699
"key": [
@@ -106,7 +109,7 @@ Feature: Create-Retrieve-Update-Delete
106109
],
107110
"hydra:search": {
108111
"@type": "hydra:IriTemplate",
109-
"hydra:template": "\/dummies{?id,name,order[id],order[name],dummyDate[before],dummyDate[after]}",
112+
"hydra:template": "\/dummies{?id,name,alias,description,order[id],order[name],dummyDate[before],dummyDate[after]}",
110113
"hydra:variableRepresentation": "BasicRepresentation",
111114
"hydra:mapping": [
112115
{
@@ -121,6 +124,18 @@ Feature: Create-Retrieve-Update-Delete
121124
"property": "name",
122125
"required": false
123126
},
127+
{
128+
"@type": "IriTemplateMapping",
129+
"variable": "alias",
130+
"property": "alias",
131+
"required": false
132+
},
133+
{
134+
"@type": "IriTemplateMapping",
135+
"variable": "description",
136+
"property": "description",
137+
"required": false
138+
},
124139
{
125140
"@type": "IriTemplateMapping",
126141
"variable": "order[id]",
@@ -176,6 +191,7 @@ Feature: Create-Retrieve-Update-Delete
176191
"@type": "Dummy",
177192
"name": "A nice dummy",
178193
"alias": null,
194+
"description": null,
179195
"dummyDate": "2015-03-01T10:00:00+00:00",
180196
"jsonData": [{
181197
"key": "value1"

0 commit comments

Comments
 (0)