New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(doctrine): searchfilter using id instead of full IRI when it's not an int #5771
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,9 @@ | |
use ApiPlatform\Exception\InvalidArgumentException; | ||
use ApiPlatform\Metadata\IriConverterInterface; | ||
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; | ||
|
@@ -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 | ||
|
@@ -221,9 +236,22 @@ | |
$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(); | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed this is too complicated and deserves its own search filter... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, with multiple identifiers this becomes even clumsier to handle, and i'm not even sure what the request should look like anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Best would be to make some mix with #5732 to handle complicated stuff not sure how it'd apply on filters though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A similar handlelinks option in filters? That's a really interesting idea, but every filter would need to be reworked, that's quite an impact. I'd keep that idea in mind with #2400 for a major overhaul of filters, maybe? |
||
} | ||
} | ||
|
||
$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; | ||
} | ||
|
@@ -232,11 +260,15 @@ | |
|
||
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); | ||
} | ||
|
||
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)), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lots of new dependencies I wish we wouldn't have...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that's mostly why i didn't include this part in the previous PR. And that's kind of the point I was making in #5687 - some of the metadata is not easy to get to at times.