0

I have a very simple custom API endpoint with an input and output DTO and a Symfony Controller, build like the examples in the documentation page: https://api-platform.com/docs/v2.3/core/dto/

#[AsController]
class MessageDataGetInvokeController extends AbstractController
{
    #[Route(
        path: '/api/messages',
        name: 'messages_get_invoke',
        defaults: [
            '_api_respond' => true,
            '_api_normalization_context' => ['api_sub_level' => true]
        ],
        methods: ['GET']
    )]
    public function __invoke(
        #[MapQueryString] MessageReadInputDto $messageReadInputDto
    ): MessageReadOutputDto {
        return new MessageReadOutputDto(
            data: 'test invoke controller: ' . $messageReadInputDto->getBusinessKey()
        );
    }
}

readonly class MessageReadInputDto
{
    public function __construct(
        #[Groups(['custom:read'])]
        #[ApiProperty(example: 'b19d262e-ad00-4c57-8a4d-dbcbe294da8e')]
        public string $businessKey
    ) {
    }

    public function getBusinessKey(): string
    {
        return $this->businessKey;
    }
}

readonly class MessageReadOutputDto
{
    public function __construct(
        #[Groups(['custom:read'])]
        public string $data
    ) {
    }

    public function getData(): string
    {
        return $this->data;
    }
}

#[ApiResource(
    operations: [
        new Get(
            output: MessageReadOutputDto::class,
            openapiContext: [
                'summary' => 'Retrieve messages',
                'description' => 'This endpoint retrieves a message by given input data',
                'parameters' => [
                    [
                        'name'        => 'businessKey',
                        'in'          => 'query',
                        'description' => 'Business process identifier',
                        'required'    => true,
                        'type'        => 'string',
                        'default'     => '',
                    ],
                ],
            ],
            routeName: 'messages_get_invoke',
            normalizationContext: ['groups' => ['custom:read']],
            read: false
        ),
        new Post(),
        new Put(),
        new Delete()
    ],
    normalizationContext: ['groups' => ['custom:read']],
    denormalizationContext: ['groups' => ['custom:write']],
)]
class Message
{
}

And when I call the endpoint I receive this error:

{
  "@id": "/api/errors",
  "@type": "hydra:Error",
  "title": "An error occurred",
  "detail": "Call to a member function serialize() on null",
  "status": 500,
  "type": "/errors/500",
  "trace": [
    {
      "file": "/var/www/html/vendor/api-platform/core/src/Symfony/EventListener/SerializeListener.php",
      "line": 133,
      "function": "serializeRawData",
      "class": "ApiPlatform\\Symfony\\EventListener\\SerializeListener",
      "type": "->"
    },
    {
      "file": "/var/www/html/vendor/symfony/event-dispatcher/Debug/WrappedListener.php",
      "line": 115,
      "function": "onKernelView",
      "class": "ApiPlatform\\Symfony\\EventListener\\SerializeListener",
      "type": "->"
    },

My api-platform config:

api_platform:
    title: Hello API Platform
    version: 1.0.0
    use_symfony_listeners: true
    formats:
        jsonld: ['application/ld+json']
        json: ['application/json']
    docs_formats:
        jsonld: ['application/ld+json']
        jsonopenapi: ['application/vnd.openapi+json']
        html: ['text/html']
    defaults:
        rfc_7807_compliant_errors: true
        stateless: true
        cache_headers:
            vary: ['Content-Type', 'Authorization', 'Origin']
        extra_properties:
            standard_put: true
            rfc_7807_compliant_errors: true
    event_listeners_backward_compatibility_layer: false
    keep_legacy_inflector: false

What exactly is missing? Thanks!

1 Answer 1

0

The solution was easy, but really not so good documented in the API Platform documentation. If someone from the API Platform reads this, then please make an update.

The issue was, that the Ressource class was not visible and thats why there is no serializer. To fix the issue you have to add the Ressource class in that way:

#[AsController]
class MessageDataGetInvokeController extends AbstractController
{
    #[Route(
        path: '/api/messages',
        name: 'messages_get',
        defaults: [
            '_api_respond' => true,
            '_api_resource_class' => Message::class,
        ],
        methods: ['GET']
    )]
    public function __invoke(
        #[MapQueryString] MessageReadInputDto $messageReadInputDto
    ): MessageReadOutputDto {
        return new MessageReadOutputDto(
            data: 'test invoke controller: ' . $messageReadInputDto->getBusinessKey()
        );
    }
}

In this way, a Serializer processes the returned DTO and everything works.

Not the answer you're looking for? Browse other questions tagged or ask your own question.