core icon indicating copy to clipboard operation
core copied to clipboard

Automatically add `requirements` on uriVariables

Open VincentLanglet opened this issue 4 months ago • 0 comments

Description
When defining a route

new Get()

The uri template 'uriTemplate': 'projects/{uuid}' is automatically added by ApiPlatform and the Links too.

It would be great having the requirement detected base on the ORM field.

Example
I tried locally with something like

class RequirementResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
    private const string INT_PATTERN = '^-?[0-9]+$';
    private const string UUID_PATTERN = '^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$';
    private const string ULID_PATTERN = '^[0-7][0-9A-HJKMNP-TV-Z]{25}$';

    public function __construct(
        private ResourceMetadataCollectionFactoryInterface $decorated,
        private EntityManagerInterface $entityManager
    ) {}

    public function create(string $resourceClass): ResourceMetadataCollection
    {
        $collection = $this->decorated->create($resourceClass);

        foreach ($collection as $i => $resource) {
            $operations = $resource->getOperations();
            if (null === $operations) {
                continue;
            }

            foreach ($resource->getOperations() as $operationName => $operation) {
                $requirements = $operation->getRequirements() ?? [];

                $uriVariables = (array) ($operation->getUriVariables() ?? []);
                if ([] === $uriVariables) {
                    continue;
                }

                foreach ($uriVariables as $paramName => $uriVariable) {
                    if (isset($requirements[$paramName])) {
                        continue;
                    }

                    if (!$uriVariable instanceof Link) {
                        continue;
                    }
                    $identifiers = $uriVariable->getIdentifiers();
                    if (count($identifiers) !== 1) {
                        continue;
                    }

                    $fromClass = $uriVariable->getFromClass();
                    $fieldPattern = $this->getFieldPattern($fromClass, $identifiers[0]);
                    if (null !== $fieldPattern) {
                        $requirements[$paramName] = $fieldPattern;
                    }
                }

                if ([] !== $requirements) {
                    $operations->add($operationName, $operation->withRequirements($requirements));
                }
            }

            $collection[$i] = $resource->withOperations($operations);
        }

        return $collection;
    }

    private function getFieldPattern(string $class, string $fieldName): ?string
    {
        try {
            $classMetadata = $this->entityManager->getClassMetadata($class);
        } catch (\Exception) {
            return null;
        }

        if (!$classMetadata->hasField($fieldName)) {
            return null;
        }

        return match ($classMetadata->getFieldMapping($fieldName)->type) {
            'uuid', 'guid' => self::UUID_PATTERN,
            'ulid' => self::ULID_PATTERN,
            'smallint', 'integer', 'bigint' => self::INT_PATTERN,
            default => null,
        };
    }
}

I dunno if my implementation is missing something

VincentLanglet avatar Oct 22 '25 10:10 VincentLanglet