core icon indicating copy to clipboard operation
core copied to clipboard

'uri_variables' missing from context during denormalization

Open darthf1 opened this issue 1 year ago • 7 comments

API Platform version(s) affected: 3.3.11

Description I have a custom Symfony\Component\Serializer\Normalizer\DenormalizerInterface class, which converts the request context to a typed (CQRS) Command class. Based on the uri variables, the Command constructor arguments are set and based on the type, some additional (constructor) logic is executed.

The CommandDenormalizer has the following methods:

<?php

use Application\Shared\MessageBus\CommandInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

final readonly class CommandDenormalizer implements DenormalizerInterface
{
    /**
     * @param array<mixed> $context
     */
    #[\Override]
    public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
    {
        return is_a($type, CommandInterface::class, true) && \is_array($data) && \is_array($context['uri_variables']);
    }

    #[\Override]
    public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): CommandInterface
    {
        // some logic, which also uses the uri_variables
    }
}

However, when updating from 3.3.7 to 3.3.11, the uri_variables seems to be missing from $context.

I guess directly related to https://github.com/api-platform/core/pull/6467

darthf1 avatar Jul 22 '24 11:07 darthf1

We're also affected by this change. We use the uri variables for later processing but they stopped coming and it breaks a lot of endpoints.

SpartakusMd avatar Aug 05 '24 15:08 SpartakusMd

They're removed from the context only in case of a relation. The ApiPlatform\State\UriVariablesResolverTrait may help you gather URIs for a given operation if needed. Last but not least, you should be able to get initial uri_variables using Symfony's Request no?

soyuka avatar Aug 08 '24 14:08 soyuka

They're removed from the context only in case of a relation. The ApiPlatform\State\UriVariablesResolverTrait may help you gather URIs for a given operation if needed.

I just tried with adding ApiPlatform\State\UriVariablesResolverTrait, but the getOperationUriVariables requires the $operation to which I don't have access in the denormalizer (right ?).

Last but not least, you should be able to get initial uri_variables using Symfony's Request no?

Accessing via SF request would be possible I guess, Ill try on monday, but that is a lot of extra logic for something which was easily accessible through the context in a previous patch release?

darthf1 avatar Aug 09 '24 19:08 darthf1

Either you have the operation inside the context or you need to use the ResourceMetadataFactory basicaly it's like:

https://github.com/api-platform/core/blob/ad2d5a78f36273b706e180ebdbdf1617d952138d/src/Serializer/AbstractItemNormalizer.php#L799-L801

soyuka avatar Aug 09 '24 19:08 soyuka

Ok, so I was able to resolve it most easy with:

private RequestStack $requestStack

$request = $this->requestStack->getCurrentRequest();
/** @var array<string, scalar> $uriVariables */
$uriVariables = $request?->attributes->get('_api_uri_variables', []);

darthf1 avatar Aug 20 '24 08:08 darthf1

Not sure that this is correct, uri variables are present for the main resource, but when it's a relation we can't know for sure that uri variables are the one from the request. On something else I don't understand why you would need another subsystem for CQRS, API Platform has embedded CQRS (Processor/Provider).

soyuka avatar Aug 29 '24 20:08 soyuka

Not sure that this is correct, uri variables are present for the main resource, but when it's a relation we can't know for sure that uri variables are the one from the request. On something else I don't understand why you would need another subsystem for CQRS, API Platform has embedded CQRS (Processor/Provider).

Its only a custom denormalizer, not a full subsystem (right?).

Lets say I have the following command:

<?php

final readonly class CreateEngagementCommand
{
  public const string DENORMALIZATION_GROUP = 'engagement_create_denormalize';

  public function __construct(
      public OrganisationId $organisationId,
      #[Groups([self::DENORMALIZATION_GROUP])]
      public string $engagementName,
  ) {}
}

With the following api resource:

<?php

#[ApiResource(
  operations: [
        new Post(
          uriTemplate: '/organisations/{organisationId}/engagements',
          uriVariables: [
              'organisationId' => new Link(
                  fromClass: Organisation::class,
                  fromProperty: 'organsation',
                  security: "is_granted('" . OrganisationVoter::ATTRIBUTE_ORGANISATION_READ . "', organsation)",
              ),
          ],
          requirements: [
              'organisationId' => AbstractDomainId::REQUIREMENT,
          ],
          messenger: 'input',
          input: CreateEngagementCommand::class,
          output: false,
          status: Response::HTTP_ACCEPTED,
          denormalizationContext: [
              'groups' => [CreateEngagementCommand::DENORMALIZATION_GROUP],
          ],
      ),
  ],
)
class Engagement {}

Then I perform the following request:

curl -X POST "http://my-api.com/organisations/{organisationId}/engagements" \
-H "Content-Type: application/json" \
-d '{"engagementName": "my engagement"}'

During denormalization it would throw an error, because the $organisationId property of class CreateEngagementCommand is not an instance of OrganisationId (because no value is given in the payload, and no value is even allowed to be given because of the denormalization group).

What my denormalizer is doing, besides getting all constructor parameter from the request payload in combination with the given denormalization group, is check if there are any unset constructor parameters left, where the property name is equal to one of the provided uri variables (organisationId in this case), and where the property type is one of a domain id. Thats why I need access to those uri variables.

Well, this is the solution I came up with during my migration from v2.7 to v3. If you have a better / simpler solution I would love to hear it :) I'm still getting accustomed to all the specifics of the new operation classes.

darthf1 avatar Oct 13 '24 12:10 darthf1

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Dec 13 '24 01:12 stale[bot]