/symfony-advanced-resolving

Advanced argument resolving related on php 8 attributes

Primary LanguagePHP

Symfony advanced resolving

Requirements

  • php >= 8.0
  • symfony >= 5.3

Installation

composer require nzour/symfony-advanced-resolving

Important: Make sure bundle \AdvancedResolving\AdvancedResolvingBundle become enabled, otherwise enable it in bundles.php

Examples

#1 Using #[FromQuery] attribute:

#[AsController, Route(path: '/foo')]
final class FooController
{
    #[Route(methods: ['GET'])]
    public static function index(#[FromQuery] string $value): JsonResponse
    {
        return new JsonResponse([
            'value' => $value,
        ]);
    }
}

GET {host}/foo?value={value}


#2 Using #[FromQuery] attribute with different param name:

#[AsController, Route(path: '/foo')]
final class FooController
{
    #[Route(methods: ['GET'])]
    public static function index(
        #[FromQuery(paramName: 'foobar')] string $value,
        #[FromQuery] int $score,
    ): JsonResponse {
        return new JsonResponse([
            'value' => $value,
            'score' => $score,
        ]);
    }
}

GET {host}/foo?foobar={value}&score={score}


#3 Using #[FromQuery] with class typehint

final class FooQuery
{
    public function __construct(
        public string $value,
        public int $score,
    ) {
    }
}

#[AsController, Route(path: '/foo')]
final class FooController
{
    #[Route(methods: ['GET'])]
    public static function index(#[FromQuery] FooQuery $fooQuery): JsonResponse {
        return new JsonResponse($fooQuery);
    }
}

GET {host}/foo?value={value}&score={score}

Note: it's not possible to rename properties of FooQuery via additional attributes


#4 Using #[FromBody]

final class FooCommand
{
    public function __construct(
        public string $foobar,
        public int $barfoo,
    ) {
    }
}

#[AsController, Route(path: '/foo')]
final class FooController
{
    #[Route(methods: ['POST'])]
    public static function command(#[FromBody] FooCommand $command): JsonResponse
    {
        return new JsonResponse([
            'command' => $command,
        ]);
    }
}

Docs

Feature based on Symfony's ArgumentValueResolver flow, therefore make sure your controllers tagged with controller.service_arguments or marked via #[AsController] attribute.

Built in
  • bin/console debug:meta-resolvers - View list of defined meta resolvers

  • FromBody

    Implementation class FromBodyMetaResolver

    Uses symfony serializer to instantiate objects from plain data, default format of data is json. There is a way to specify another format: #[FromBody(format: XmlEncoder::FORMAT)]. It is not possible to change format globally.

  • FromQuery

    Implementation class FromQueryMetaResolver

    You can specify different param name #[FromQuery(paramName: 'foobar')]

    Parameter FromQuery::$disableTypeEnforcement is responsible for flag AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT when resolve object. It's value true by default.

Not implemented yet
  • FromHeader
  • FromForm
Errors
Extend by user land

If you want to create your own attribute and algoritm that resolves it, follow steps:

  • Define your attribute or use existing attribute
  • Define class-service, that implements MetaResolverInterface
  • Method supportedAttribute should return class-string of which attribute it supports
  • Mark your service with tag meta-resolver

Limitations

  • Built in resolvers work with Symfony Serializer

  • No interop with Symfony Validator (yet, or maybe not yet)

  • Built in attributes work only with endpoint's parameters, it's not possible to combine attributes:

    final class FooDto
    {
        public function __construct(
           #[FromQuery]
           public string $foobar,
           public int $barfoo,
       ) {
       }
    }
    
    #[AsController]
    final class FooController
    {
        #[Route(path: '/foo', methods: ['POST'])
        public function index(#[FromBody] FooDto $dto): void
        {
         // ...
        }
    }

    The entire class FooDto would be compiled from Body parameters.

    However property FooDto::$foobar is marked with attribute #[FromQuery], resolver would not try to find parameter foobar inside query-parameters and try set it as property's value.

Inspired by