Created
May 24, 2022 13:04
-
-
Save orangevinz/99999fd28b53db9421c59e5dface76a5 to your computer and use it in GitHub Desktop.
Api Platform exclusion custom filter - Exclude an array of items, by their IRI or value
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Entity; | |
use ApiPlatform\Core\Annotation\ApiFilter; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use App\Filter\ExcludeSearchFilter; | |
use App\Repository\EntityRepository; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ORM\Entity(repositoryClass=EntityRepository::class) | |
* @ApiResource() | |
* @ApiFilter(ExcludeSearchFilter::class, properties={"status"}) | |
*/ | |
class Entity | |
{ | |
/** | |
* @ORM\Column(type="smallint") | |
*/ | |
private $status = 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Filter; | |
use ApiPlatform\Core\Api\IriConverterInterface; | |
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter; | |
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; | |
use Doctrine\ORM\QueryBuilder; | |
use Doctrine\Persistence\ManagerRegistry; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
use Symfony\Component\PropertyInfo\Type; | |
use Symfony\Component\Serializer\NameConverter\NameConverterInterface; | |
class ExcludeSearchFilter extends AbstractContextAwareFilter | |
{ | |
public const QUERY_PARAMETER_KEY = 'exclude'; | |
private IriConverterInterface $iriConverter; | |
public function __construct( | |
ManagerRegistry $managerRegistry, | |
?RequestStack $requestStack = null, | |
LoggerInterface $logger = null, | |
array $properties = null, | |
NameConverterInterface $nameConverter = null, | |
IriConverterInterface $iriConverter | |
) { | |
parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter); | |
$this->iriConverter = $iriConverter; | |
} | |
// This function is only used to hook in documentation generators (supported by Swagger and Hydra) | |
public function getDescription(string $resourceClass): array | |
{ | |
if (!$this->properties) { | |
return []; | |
} | |
$description = []; | |
foreach ($this->properties as $property => $strategy) { | |
$descriptionKey = 'exclude['.$property.'][]'; | |
$description[$descriptionKey] = [ | |
'property' => $property, | |
'type' => Type::BUILTIN_TYPE_ITERABLE, | |
'schema' => [ | |
'type' => Type::BUILTIN_TYPE_ARRAY, | |
'items' => [ | |
'type' => Type::BUILTIN_TYPE_STRING | |
], | |
'example' => '"<iri> or value"' | |
], | |
'required' => false, | |
'description' => 'Filter using an array of excluded items.', | |
]; | |
} | |
return $description; | |
} | |
protected function filterProperty( | |
string $property, | |
$value, | |
QueryBuilder $queryBuilder, | |
QueryNameGeneratorInterface $queryNameGenerator, | |
string $resourceClass, | |
string $operationName = null | |
) { | |
// otherwise filter is applied to order and page as well | |
if ( | |
$property !== self::QUERY_PARAMETER_KEY | |
|| !is_array($value) | |
) { | |
return; | |
} | |
/** | |
* From: | |
* ?exclude[fieldName1][]=<IRI1>&exclude[fieldName1][]=<IRI2>&exclude[fieldName2][]=<value>. | |
* | |
* To: | |
* $mappedValues = [ | |
* 'fieldName1' => [ | |
* ['IRI1'], | |
* ['IRI2'] | |
* ], | |
* 'fieldName2' => [ | |
* ['value'] | |
* ] | |
* ];. | |
*/ | |
$mappedValues = []; | |
foreach ($value as $key => $values) { | |
// Restrict to enabled properties | |
if ($this->isPropertyEnabled($key)) { | |
// Allow receiving array or string | |
$values = (array) $values; | |
// Relation mapped field | |
if ($this->getClassMetadata($resourceClass)->hasAssociation($key)) { | |
try { | |
foreach ($values as $item) { | |
$mappedValues[$key][] = $this->iriConverter->getItemFromIri($item); | |
} | |
} catch (\Exception $e) { | |
// Invalid IRI, wrong uuid ... | |
$this->logger->warning($e); | |
} | |
} | |
// Regular mapped field | |
if ($this->getClassMetadata($resourceClass)->hasField($key)) { | |
foreach ($values as $item) { | |
$mappedValues[$key][] = $item; | |
} | |
} | |
} | |
} | |
// Override query | |
$rootAlias = $queryBuilder->getRootAliases()[0]; | |
foreach ($mappedValues as $key => $items) { | |
$queryBuilder | |
->andWhere(sprintf('%s.%s NOT IN(:%s)', $rootAlias, $key, $key)) | |
->setParameter($key, $items) | |
; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment