Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using a DTO for batch operations #1554

Open
ctrl-f5 opened this issue Jun 9, 2020 · 5 comments
Open

Using a DTO for batch operations #1554

ctrl-f5 opened this issue Jun 9, 2020 · 5 comments

Comments

@ctrl-f5
Copy link

ctrl-f5 commented Jun 9, 2020

There is some discussion on batch operations here api-platform/core#1645

And the conclusion seems to be to just use DTOs for batch actions, so that's what I'm doing. But I can't get the DTO in my controller, it always want to deserialize it into the resource class, which ends up in a, Exception Cannot create an instance of App\Entity\User from serialized data because its constructor requires parameter

The way to disable this normalization is to configure input: false, but then the request body does not get converted to the DTO anymore...

So what's the DTO batch setup supposed to look like?

My current setup which produces the error looks like this:

# resource config
resources:
    App\Entity\User:
        collectionOperations:
            enabled:
                path: /users/enabled-batch
                method: PUT
                controller: App\Controller\User\EnableUserBatchController
                read: false
                input: App\DTO\User\UserEnabledBatchInput
                output: App\DTO\User\UserEnabledBatchOutput
                openapi_context:
                    summary: 'Updates enabled status of multiple User resources.'
# controller
class EnableUserBatchController extends AbstractController
{
    public function __invoke(UserEnabledbatchInput $data): UserEnabledBatchOutput
    { ... }
}
# input DTO
final class UserEnabledBatchInput
{
    /**
     * @Assert\All(
     *     @Assert\NotNull(),
     *     @Assert\Type("int")
     * )
     */
    public array $ids;

    /**
     * @Assert\NotNull()
     */
    public bool $enabled;
}
@ctrl-f5
Copy link
Author

ctrl-f5 commented Jun 9, 2020

Currently found a workaround by poking around in the code that handles messenger: true config, since it basically does what I need, except it also passes it to a handler.

I've added the following data transformer, which keeps the input DTO as is, when receive: false is configured for the operation:

final class DtoInputTransformer implements DataTransformerInterface
{
    private ResourceMetadataFactoryInterface $resourceMetadataFactory;

    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory)
    {
        $this->resourceMetadataFactory = $resourceMetadataFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function transform($object, string $to, array $context = [])
    {
        return $object;
    }

    /**
     * {@inheritdoc}
     */
    public function supportsTransformation($data, string $to, array $context = []): bool
    {
        if (
            \is_object($data) // data is not normalized yet, it should be an array
            ||
            null === ($context['input']['class'] ?? null)
        ) {
            return false;
        }

        $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? $to);

        return false === $metadata->getTypedOperationAttribute(
                $context['operation_type'],
                $context[$context['operation_type'].'_operation_name'] ?? '',
                'receive',
                true,
                false
            );
    }
}

I'm probably missing some config that enables this behavior without this custom class though, seems like a trivial use case.

@kryst3q
Copy link

kryst3q commented Jul 10, 2020

@ctrl-f5 Can I ask you to describe the way you did this more precisely? I really need that functionality now. Does receive: false is applied to all messenger messages or simply this one?

@ctrl-f5
Copy link
Author

ctrl-f5 commented Jul 10, 2020

  • Add the class from my previous post, and configure it as a data transformer (just create the class if autoconfigure is enabled)
  • add receive: false instead of read: false to the operation for which you want the controller to receive the DTO (so add it to the collectionOperation.enabled config of the original post)

Not this is for an action that does not use the messenger config, no idea if it works the same way when using messenger.

@kryst3q
Copy link

kryst3q commented Jul 10, 2020

Thanks for description and fast response @ctrl-f5 !

@pourquoi
Copy link

thanks @ctrl-f5 this is literally the only answer I found on the internets for this particular issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants