Skip to content

Commit cce360c

Browse files
odoucetalanpoulain
authored andcommitted
fix searchFilter with binary types (like UUID)
1 parent 0e3f820 commit cce360c

File tree

2 files changed

+67
-24
lines changed

2 files changed

+67
-24
lines changed

src/Bridge/Doctrine/Orm/Filter/SearchFilter.php

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
2222
use ApiPlatform\Core\Exception\InvalidArgumentException;
2323
use Doctrine\DBAL\Types\Type as DBALType;
24+
use Doctrine\ORM\Query\Parameter;
2425
use Doctrine\ORM\QueryBuilder;
2526
use Doctrine\Persistence\ManagerRegistry;
27+
use Doctrine\Persistence\Mapping\ClassMetadata;
2628
use Psr\Log\LoggerInterface;
2729
use Symfony\Component\HttpFoundation\RequestStack;
2830
use Symfony\Component\PropertyAccess\PropertyAccess;
@@ -113,7 +115,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
113115
$caseSensitive = false;
114116
}
115117

116-
$this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $values, $caseSensitive);
118+
$this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $values, $caseSensitive, $metadata);
117119

118120
return;
119121
}
@@ -149,14 +151,39 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
149151
$associationField = $associationFieldIdentifier;
150152
}
151153

152-
if (1 === \count($values)) {
154+
/*
155+
* If field type is string/float, Doctrine does not call convertToDatabaseValueSQL() because it
156+
* does not know it needs conversion.
157+
* This would lead to incorrect values for Ramsey\Uuid\Doctrine\UuidBinaryType for example.
158+
* The only fix is to provide field type to doctrine ...
159+
* It's easy if setParameter() sets only one value BUT impossible if multiple values are provided.
160+
* The only way to do this is to rewrite the IN() statement to multiple values
161+
* and map a global setParameters()
162+
*/
163+
$type = $metadata->getTypeOfField($associationField);
164+
$nbValues = \count($values);
165+
166+
if (1 === $nbValues) {
153167
$queryBuilder
154168
->andWhere($queryBuilder->expr()->eq($associationAlias.'.'.$associationField, ':'.$valueParameter))
155-
->setParameter($valueParameter, $values[0]);
169+
->setParameter($valueParameter, $values[0], $type);
156170
} else {
171+
// get current parameters, because QueryBuilder->setParameters() erase previous parameters set
172+
$parameters = $queryBuilder->getParameters();
173+
$inQuery = [];
174+
175+
// convertToDatabaseValue() can only convert one value at a time ... We can no longer pass an array of values, we should use multiple values
176+
177+
for ($i = 0; $i < $nbValues; ++$i) {
178+
$inQuery[] = ':'.$valueParameter;
179+
$parameters->add(new Parameter($valueParameter, $values[$i], $type));
180+
$valueParameter = $queryNameGenerator->generateParameterName($associationField);
181+
}
182+
183+
// we cannot use expr->in() here because it considers $inQuery parameters as strings.
157184
$queryBuilder
158-
->andWhere($queryBuilder->expr()->in($associationAlias.'.'.$associationField, ':'.$valueParameter))
159-
->setParameter($valueParameter, $values);
185+
->andWhere($associationAlias.'.'.$associationField.' IN ('.implode(', ', $inQuery).')')
186+
->setParameters($parameters);
160187
}
161188
}
162189

@@ -165,28 +192,47 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
165192
*
166193
* @throws InvalidArgumentException If strategy does not exist
167194
*/
168-
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $values, bool $caseSensitive)
195+
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $values, bool $caseSensitive/*, ClassMetadata $metadata = null*/)
169196
{
197+
// check if we have metadata
198+
if (\func_num_args() > 7 && ($metadata = func_get_arg(7)) instanceof ClassMetadata) {
199+
$type = $metadata->getTypeOfField($field);
200+
} else {
201+
@trigger_error(sprintf('Method %s() will have a 8th argument `$metadata` in version API Platform 3.0.', __FUNCTION__), E_USER_DEPRECATED);
202+
$type = null; // default setParameter() value
203+
}
204+
170205
if (!\is_array($values)) {
171206
$values = [$values];
172207
}
173208

209+
$nbValues = \count($values);
174210
$wrapCase = $this->createWrapCase($caseSensitive);
175211
$valueParameter = ':'.$queryNameGenerator->generateParameterName($field);
176212
$aliasedField = sprintf('%s.%s', $alias, $field);
177213

178214
if (null == $strategy || self::STRATEGY_EXACT == $strategy) {
179-
if (1 == \count($values)) {
215+
if (1 == $nbValues) {
180216
$queryBuilder
181217
->andWhere($queryBuilder->expr()->eq($wrapCase($aliasedField), $wrapCase($valueParameter)))
182-
->setParameter($valueParameter, $values[0]);
218+
->setParameter($valueParameter, $values[0], $type);
183219

184220
return;
185221
}
186222

223+
// get current parameters, because QueryBuilder->setParameters() erase previous parameters set
224+
$parameters = $queryBuilder->getParameters();
225+
$inQuery = [];
226+
for ($i = 0; $i < $nbValues; ++$i) {
227+
$inQuery[] = $valueParameter;
228+
$parameters->add(new Parameter($valueParameter, $caseSensitive ? $values[$i] : strtolower($values[$i]), $type));
229+
$valueParameter = ':'.$queryNameGenerator->generateParameterName($field);
230+
}
231+
232+
// we cannot use expr->in() here because it considers $inQuery parameters as strings.
187233
$queryBuilder
188-
->andWhere($queryBuilder->expr()->in($wrapCase($aliasedField), $valueParameter))
189-
->setParameter($valueParameter, $caseSensitive ? $values : array_map('strtolower', $values));
234+
->andWhere($wrapCase($aliasedField).' IN ('.implode(', ', $inQuery).')')
235+
->setParameters($parameters);
190236

191237
return;
192238
}
@@ -228,7 +274,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild
228274
}
229275

230276
$queryBuilder->andWhere($queryBuilder->expr()->orX(...$ors));
231-
array_walk($parameters, [$queryBuilder, 'setParameter']);
277+
array_walk($parameters, [$queryBuilder, 'setParameter'], $type);
232278
}
233279

234280
/**

tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -390,22 +390,18 @@ public function provideApplyTestData(): array
390390
$filterFactory,
391391
],
392392
'exact (multiple values)' => [
393-
sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN(:name_p1)', $this->alias, Dummy::class),
393+
sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN (:name_p1, :name_p2)', $this->alias, Dummy::class),
394394
[
395-
'name_p1' => [
396-
'CaSE',
397-
'SENSitive',
398-
],
395+
'name_p1' => 'CaSE',
396+
'name_p2' => 'SENSitive',
399397
],
400398
$filterFactory,
401399
],
402400
'exact (multiple values; case insensitive)' => [
403-
sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN(:name_p1)', $this->alias, Dummy::class),
401+
sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN (:name_p1, :name_p2)', $this->alias, Dummy::class),
404402
[
405-
'name_p1' => [
406-
'case',
407-
'insensitive',
408-
],
403+
'name_p1' => 'case',
404+
'name_p2' => 'insensitive',
409405
],
410406
$filterFactory,
411407
],
@@ -547,10 +543,11 @@ public function provideApplyTestData(): array
547543
$filterFactory,
548544
],
549545
'mixed IRI and entity ID values for relations' => [
550-
sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN(:relatedDummy_p1) AND relatedDummies_a1.id = :relatedDummies_p2', $this->alias, Dummy::class),
546+
sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN (:relatedDummy_p1, :relatedDummy_p2) AND relatedDummies_a1.id = :relatedDummies_p4', $this->alias, Dummy::class),
551547
[
552-
'relatedDummy_p1' => [1, 2],
553-
'relatedDummies_p2' => 1,
548+
'relatedDummy_p1' => 1,
549+
'relatedDummy_p2' => 2,
550+
'relatedDummies_p4' => 1,
554551
],
555552
$filterFactory,
556553
],

0 commit comments

Comments
 (0)