Skip to content

Commit

Permalink
Handle binary UUID in SearchFilter
Browse files Browse the repository at this point in the history
Most difficult work was to rewrite IN queries.
  • Loading branch information
odoucet committed Nov 10, 2020
1 parent 1c7c26d commit bf34b36
Showing 1 changed file with 40 additions and 10 deletions.
50 changes: 40 additions & 10 deletions src/Bridge/Doctrine/Orm/Filter/SearchFilter.php
Expand Up @@ -21,8 +21,10 @@
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\PropertyAccess\PropertyAccess;
Expand Down Expand Up @@ -114,7 +116,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
}

if (1 === \count($values)) {
$this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $values[0], $caseSensitive);
$this->addWhereByStrategy($strategy, $queryBuilder, $queryNameGenerator, $alias, $field, $values[0], $caseSensitive, $metadata);

return;
}
Expand All @@ -128,11 +130,32 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
}

$wrapCase = $this->createWrapCase($caseSensitive);
$valueParameter = $queryNameGenerator->generateParameterName($field);

/*
* If field type is string/float, Doctrine does not call convertToDatabaseValueSQL() because it
* does not know it needs conversion.
* This would lead to incorrect values for Ramsey\Uuid\Doctrine\UuidBinaryType for example.
* The only fix is to provide field type to doctrine ...
* BUT it is not possible with setParameter(), as third arg can only be
* \Doctrine\DBAL\Connection::PARAM_STR_ARRAY
* The only way to do this will be to rewrite the IN() statement to multiple values,
* each provided as a single setParameter()
*/
$inQuery = [];
$args = [];
$type = $metadata->getTypeOfField($field);
$nbValues = \count($values);

for ($i = 0; $i < $nbValues; ++$i) {
$valueParameter = $queryNameGenerator->generateParameterName($field);
$inQuery[] = ':'.$valueParameter;
$args[] = new Parameter($valueParameter, $caseSensitive ? $values[$i] : strtolower($values[$i]), $type);
}

$queryBuilder
->andWhere(sprintf($wrapCase('%s.%s').' IN (:%s)', $alias, $field, $valueParameter))
->setParameter($valueParameter, $caseSensitive ? $values : array_map('strtolower', $values));
->andWhere(sprintf($wrapCase('%s.%s').' IN (%s)', $alias, $field, implode(',', $inQuery)))
->setParameters($args);

}

// metadata doesn't have the field, nor an association on the field
Expand Down Expand Up @@ -184,8 +207,15 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
*
* @throws InvalidArgumentException If strategy does not exist
*/
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $value, bool $caseSensitive)
protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, $value, bool $caseSensitive, ClassMetadata $metadata = null)
{
// check if we have metadata
if ($metadata instanceof ClassMetadata) {
$type = $metadata->getTypeOfField($field);
} else {
@trigger_error('addWhereByStrategy() will require argument ClassMetadata in 3.0.', E_USER_DEPRECATED);
$type = null; // default setParameter() value
}
$wrapCase = $this->createWrapCase($caseSensitive);
$valueParameter = $queryNameGenerator->generateParameterName($field);

Expand All @@ -194,27 +224,27 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild
case self::STRATEGY_EXACT:
$queryBuilder
->andWhere(sprintf($wrapCase('%s.%s').' = '.$wrapCase(':%s'), $alias, $field, $valueParameter))
->setParameter($valueParameter, $value);
->setParameter($valueParameter, $value, $type);
break;
case self::STRATEGY_PARTIAL:
$queryBuilder
->andWhere(sprintf($wrapCase('%s.%s').' LIKE '.$wrapCase('CONCAT(\'%%\', :%s, \'%%\')'), $alias, $field, $valueParameter))
->setParameter($valueParameter, $value);
->setParameter($valueParameter, $value, $type);
break;
case self::STRATEGY_START:
$queryBuilder
->andWhere(sprintf($wrapCase('%s.%s').' LIKE '.$wrapCase('CONCAT(:%s, \'%%\')'), $alias, $field, $valueParameter))
->setParameter($valueParameter, $value);
->setParameter($valueParameter, $value, $type);
break;
case self::STRATEGY_END:
$queryBuilder
->andWhere(sprintf($wrapCase('%s.%s').' LIKE '.$wrapCase('CONCAT(\'%%\', :%s)'), $alias, $field, $valueParameter))
->setParameter($valueParameter, $value);
->setParameter($valueParameter, $value, $type);
break;
case self::STRATEGY_WORD_START:
$queryBuilder
->andWhere(sprintf($wrapCase('%1$s.%2$s').' LIKE '.$wrapCase('CONCAT(:%3$s, \'%%\')').' OR '.$wrapCase('%1$s.%2$s').' LIKE '.$wrapCase('CONCAT(\'%% \', :%3$s, \'%%\')'), $alias, $field, $valueParameter))
->setParameter($valueParameter, $value);
->setParameter($valueParameter, $value, $type);
break;
default:
throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy));
Expand Down

0 comments on commit bf34b36

Please sign in to comment.