Skip to content

[RFC] Mapping query, path and header parameters to a DTO #7112

Open
@joelwurtz

Description

@joelwurtz

This RFC propose to allow using a class to declare parameters, and map those parameters to an object of this class.

As an example we could have a parameters class like this :

use App\Entity\Store;

use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\Metadata\PathParameter;
use ApiPlatform\Metadata\HeaderParameter;
use Symfony\Component\Validator\Constraints as Assert;

class GetBookForStoreParameters
{
    #[QueryParameter]
    #[Assert\Range(min: 1, max: 100)] // it is possible to declare constraint
    public int $page; // this parameter has no default value, which means it is required, if it is not present then it will return a 400 response

    #[QueryParameter(name: 'max')] 
    public int $maxItemsPerPage = 100; // there is a default value, so it's not required

    #[PathParameter(name: 'id', security('is_granted("ROLE_GET_BOOK")')]
    public Store $store;  // here the store is a doctrine entity and it would use a provider to fetch this, we could also link security check on a parameter

    #[QueryParameter(provider: AuthorFromNameProvider::class)]
    public ?Author $author = null; // Here we use a custom provider to fetch the author from name

    #[HeaderParameter(name: 'X-Custom-Header')]
    #[Assert\Length(max: 100)]
    public string $customHeader = '';
}

Then we could use this class to declare parameters of an endpoint :

#[ApiResource(
    operations: [
        new GetCollection(
            uriTemplate: '/store/{id}/books',
            parameters: GetBookForStoreParameters::class,
            provider: GetBookForStoreProvider::class,
        ),
    ],
)]
class Book
{
   // ...
}

Then in our provider instead of having to use the $uriVariables or request field in context we could directly use this object

class GetBookForStoreProvider implements ProviderInterface
{
    
    public function provide(Operation $operation, GetBookForStoreParameters: $parameters): array
    {
         // At this point i'm sure that our parameters are valid and that security on them has been checked so i can safely use the store
         $books = $this->service->getBooksForStoreAndMaybeAuthor($parameters->store, $parameters->author);

         return $books;
    }
}

This allow to :

  • unify uriVariables / parameters / headers declaration into the same object
  • add a way to link an object to a parameter (without the fromClass / toProperty which are vague IMO see [RFC] Add a system for getting parent resource and play security #7107)
  • provide security easily on those parameters
  • provide better static analysis (like a parameter has been removed but was still used in a critical place)
  • better reusability of same parameters across operations (by using traits and / or inheritance)

Not sure how BC could be supported, but i'm sure we can find something

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions