API Platform custom collection filter on identifier

API Platform supports various filters out of the box, like the SearchFilter. The filters though lack support for filtering on an entity's id when using another property as the identifier of the entity. Given you have for example the field ext_ref marked as identifier of the entity, but you want the filter the end user of the API sets to use id as property to filter on that ext_ref you thus need to write a custom filter. An example of such a custom filter is shown below.

<?php
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
use InvalidArgumentException;

final class IdentifiersFilter extends AbstractFilter
{
    const PROPERTY_NAME = 'ids';

    protected function filterProperty($property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, $resourceClass, Operation $operation = null, $context = [])
    {
        $idFields = array_keys($this->getProperties());
        if ($property !== self::PROPERTY_NAME) {
            return;
        }

        $idField = $idFields[0];
        $extRefs = [];
        foreach ($value as $extRef) {
            $extRefs[] = $extRef;
        }

        $alias = $queryBuilder->getRootAliases()[0];
        $queryBuilder->andWhere(
            $queryBuilder->expr()->in($alias.'.'.$idField, $extRefs)
        );
    }

    public function getDescription(string $resourceClass): array
    {
        return [
            self::PROPERTY_NAME => [
                'property' => 'ext_ref',
                'type' => 'iterable',
                'required' => false,
                'swagger' => [
                    'description' => 'Filter by id',
                    'name' => 'Id',
                    'type' => 'iterable',
                ],
            ],
        ];
    }
}

Now you can apply the filter on your entity like so:

#[ApiFilter(IdentifiersFilter::class, properties: ['ext_ref'])]

Querying this entity thus now supports the ids filter allowing you to filter your collection by the ext_ref (while the user thus doesn't know that and passes along id as he/she expects).

query queryMyEntities {
  myEntity(ids:["my-value1-thats-actually-the-ext-ref", "my-value2-thats-actually-the-ext-ref"]) {
    totalCount
    edges {
      cursor
      node {
        ext_ref: id
        name
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

See https://api-platform.com/docs/core/filters/#creating-custom-filters for more information.