Skip to content

Commit

Permalink
feat(doctrine): searchfilter using id instead of full IRI when it's n…
Browse files Browse the repository at this point in the history
…ot an int
  • Loading branch information
mrossard committed Aug 24, 2023
1 parent a774f4c commit d050fa0
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 9 deletions.
12 changes: 11 additions & 1 deletion features/doctrine/search_filter.feature
Expand Up @@ -1045,10 +1045,20 @@ Feature: Search filter on collections

@!mongodb
@createSchema
Scenario: Filters can use UUIDs
Scenario: Filters can use UUID based IRIs
Given there is a group object with uuid "61817181-0ecc-42fb-a6e7-d97f2ddcb344" and 2 users
And there is a group object with uuid "32510d53-f737-4e70-8d9d-58e292c871f8" and 1 users
When I send a "GET" request to "/issue5735/issue5735_users?groups[]=/issue5735/groups/61817181-0ecc-42fb-a6e7-d97f2ddcb344&groups[]=/issue5735/groups/32510d53-f737-4e70-8d9d-58e292c871f8"
Then the response status code should be 200
And the response should be in JSON
And the JSON node "hydra:totalItems" should be equal to 3

@!mongodb
@createSchema
Scenario: Filters can use UUID directly
Given there is a group object with uuid "61817181-0ecc-42fb-a6e7-d97f2ddcb344" and 2 users
And there is a group object with uuid "32510d53-f737-4e70-8d9d-58e292c871f8" and 1 users
When I send a "GET" request to "/issue5735/issue5735_users?groups[]=61817181-0ecc-42fb-a6e7-d97f2ddcb344&groups[]=32510d53-f737-4e70-8d9d-58e292c871f8"
Then the response status code should be 200
And the response should be in JSON
And the JSON node "hydra:totalItems" should be equal to 3
48 changes: 40 additions & 8 deletions src/Doctrine/Orm/Filter/SearchFilter.php
Expand Up @@ -21,6 +21,9 @@
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\ResourceClassResolverInterface;
use ApiPlatform\Metadata\Util\ResourceClassInfoTrait;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
Expand Down Expand Up @@ -133,17 +136,29 @@
*/
final class SearchFilter extends AbstractFilter implements SearchFilterInterface
{
use ResourceClassInfoTrait;
use SearchFilterTrait;

public const DOCTRINE_INTEGER_TYPE = Types::INTEGER;

public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface $identifiersExtractor = null, NameConverterInterface $nameConverter = null)
{
public function __construct(
ManagerRegistry $managerRegistry,
IriConverterInterface $iriConverter,
PropertyAccessorInterface $propertyAccessor = null,
LoggerInterface $logger = null,
array $properties = null,
IdentifiersExtractorInterface $identifiersExtractor = null,
NameConverterInterface $nameConverter = null,
ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null,
ResourceClassResolverInterface $resourceClassResolver = null
) {
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);

$this->iriConverter = $iriConverter;
$this->identifiersExtractor = $identifiersExtractor;
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->resourceClassResolver = $resourceClassResolver;
}

protected function getIriConverter(): IriConverterInterface
Expand Down Expand Up @@ -221,9 +236,22 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
$associationResourceClass = $metadata->getAssociationTargetClass($field);
$associationMetadata = $this->getClassMetadata($associationResourceClass);
$associationFieldIdentifier = $associationMetadata->getIdentifierFieldNames()[0];
$associationResourceApiIdField = $associationFieldIdentifier;

// dig into the subResource metadata to find out which field we're looking at
if ($this->isResourceClass($associationResourceClass) && $this->resourceMetadataFactory) {
$associationApiMetadata = $this->resourceMetadataFactory->create($associationResourceClass);
$variables = $associationApiMetadata->getOperation()->getUriVariables();

Check failure on line 244 in src/Doctrine/Orm/Filter/SearchFilter.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.2)

Call to an undefined method ApiPlatform\Metadata\Operation::getUriVariables().
if (1 === \count($variables)) { // otherwise let's just give up at this point, too complicated
$varName = array_key_first($variables);
$associationResourceApiIdField = $variables[$varName]->getIdentifiers()[0]; // this will be needed to get the correct value

Check warning on line 247 in src/Doctrine/Orm/Filter/SearchFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Orm/Filter/SearchFilter.php#L243-L247

Added lines #L243 - L247 were not covered by tests
}
}

$doctrineTypeField = $this->getDoctrineFieldType($associationFieldIdentifier, $associationResourceClass);
$associationRepository = $this->managerRegistry->getManagerForClass($associationResourceClass)?->getRepository($associationResourceClass);

$values = array_map(function ($value) use ($associationFieldIdentifier, $doctrineTypeField) {
$values = array_map(function ($value) use ($associationFieldIdentifier, $associationResourceApiIdField, $doctrineTypeField, $associationRepository) {
if (is_numeric($value)) {
return $value;
}
Expand All @@ -232,11 +260,15 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB

return $this->propertyAccessor->getValue($item, $associationFieldIdentifier);
} catch (InvalidArgumentException) {
/*
* Can we do better? This is not the ApiResource the call was made on,
* so we don't get any kind of api metadata for it without (a lot of?) work elsewhere...
* Let's just pretend it's always the ORM id for now.
*/
// replace the value of $associationResourceApiIdField by that of $associationFieldIdentifier if necessary!
if ($associationFieldIdentifier !== $associationResourceApiIdField && $associationRepository) {
// use $associationResourceApiIdField to fetch the entity
$entity = $associationRepository->findOneBy([
$associationResourceApiIdField => $value,
]);
$value = $this->getPropertyAccessor()->getValue($entity, $associationFieldIdentifier);

Check warning on line 269 in src/Doctrine/Orm/Filter/SearchFilter.php

View check run for this annotation

Codecov / codecov/patch

src/Doctrine/Orm/Filter/SearchFilter.php#L266-L269

Added lines #L266 - L269 were not covered by tests
}

if (!$this->hasValidValues([$value], $doctrineTypeField)) {
$this->logger->notice('Invalid filter ignored', [
'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $associationFieldIdentifier)),
Expand Down

0 comments on commit d050fa0

Please sign in to comment.