skip to Main Content

I am using API Platform 3.1. I need to generate thumbnails on some entities after creation. I want to use Symfony Messenger to trigger this and do it asynchronously since it could take time some to process, but I want the entity to be saved immediately.

When a resource uses both the messenger and a custom processor (to save the entity), either the messages are not created if using messenger=true, or the processor is not called if using messenger='input'.

How to reproduce

#[ORMEntity]
#[ApiResource(
    operations: [
        new Post(
            processor: MyEntityProcessor::class,
            messenger: 'input',
            deserialize: false, 
        )
    ]
)]
class MyEntity
{
}
final class MyEntityProcessor implements ProcessorInterface
{
    public function __construct(private ProcessorInterface $persistProcessor)
    {
    }

    public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
    {
        $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
        return $result;
    }
}
final class MyEntityHandler implements MessageHandlerInterface
{
    public function __construct(private EntityManagerInterface $entityManager)
    {
    }

    public function __invoke(MyEntity $myEntity)
    {
        // my long running function

        $this->entityManager->persist($myEntity);
        $this->entityManager->flush();
    }
}

services.yaml

services:
    AppStateMyEntityProcessor:
        bind:
            $persistProcessor: '@api_platform.doctrine.orm.state.item_provider'

messenger.yaml

framework:
    messenger:
        transports:
            async: 'doctrine://default'

        routing:
            'AppEntityMyEntity': async

API Platform documentation mentions in Symfony Messenger Integration:

Note: when using messenger=true ApiResource attribute in a Doctrine entity, the Doctrine Processor is not called. If you want the Doctrine Processor to be called, you should decorate a built-in state processor and implement your own logic.

It was suggested on a GitHub issue that I should decorate the messenger handler in order to save the entity. However, I need my entity to be saved immediately without waiting for it to be consumed by the messenger:consume worker.

2

Answers


  1. It seems you have encountered a specific challenge with API Platform and Symfony Messenger integration, where you want to use Symfony Messenger to trigger asynchronous thumbnail generation while ensuring immediate entity saving. This can be a bit tricky due to the interactions between the processor, messenger, and entity saving. Here’s a potential approach you could consider:

    1. Decouple Entity Creation and Thumbnail Generation:
      Instead of triggering thumbnail generation directly from the entity creation process, consider decoupling it. Upon entity creation, immediately save the entity and use Symfony Messenger to dispatch a message indicating that a thumbnail needs to be generated for the newly created entity.

    2. Use a Message Handler for Thumbnail Generation:
      Create a separate message handler that listens for the thumbnail generation messages dispatched by the entity creation process. This handler should be responsible for generating thumbnails and updating the entity with the generated thumbnail information. It can also persist and flush the entity immediately after updating it.

    3. Entity Processing:
      In the MyEntityProcessor class, focus solely on processing the entity and avoid dealing with the thumbnail generation logic. This way, you can maintain the separation of concerns and keep the logic clean.

    Here’s a high-level overview of how the components might interact:

    • Upon creation of a MyEntity instance, the MyEntityProcessor processes it by saving it immediately.
    • A message is dispatched to Symfony Messenger indicating that a thumbnail needs to be generated for the newly created entity.
    • The MyEntityHandler message handler receives the message, generates the thumbnail, updates the entity, and then immediately persists and flushes the entity to the database.

    By structuring your application in this way, you can achieve the immediate saving of the entity while still utilizing Symfony Messenger for asynchronous thumbnail generation.

    Remember to configure your message handlers and routing correctly in messenger.yaml to ensure that the thumbnail generation message is handled by the appropriate message handler (MyEntityHandler).

    This approach requires some restructuring of your code but should help you achieve your goal of immediate entity saving and asynchronous thumbnail generation using Symfony Messenger and API Platform.

    Login or Signup to reply.
  2. A possible solution would be to dispatch the Message in your Processor after you have persisted the Entity for the first time. Maybe not as clean, since it requires more code, however you have way more control over how and when the Message is handled. In addition, you could also dispatch it with a DelayStamp.

    In your processor I would dispatch the Message like this:

    final class MyEntityProcessor implements ProcessorInterface
    {
        
        public function __construct(
            private ProcessorInterface $persistProcessor,
            private MessageBusInterface $bus,
        ) {
        }
    
        public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
        {
            $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context);
    
            // $data should be your MyEntity Entity.
            $this->bus->dispatch(new GenerateThumbMessage($data), [
                // wait 5 seconds before processing
                new DelayStamp(5000),
            ]);
            
            return $result;
        }
    }
    

    Create the Message AppMessageGenerateThumbMessage.php:

    class GenerateThumbMessage
    {
    
        public function __construct(private readonly MyEntity $myEntity) {}
    
        public function getMyEntity(): MyEntity
        {
            return $this->myEntity;
        }
    
    }
    

    And then create your MessageHandler AppMessageGenerateThumbMessageHandler.php:

    final class GenerateThumbMessageHandler implements MessageHandlerInterface
    {
        public function __construct(private EntityManagerInterface $em) {}
    
        public function __invoke(GenerateThumbMessage $generateThumbMessage)
        {
            $myEntity = $generateThumbMessage->getEntity();
            
            // your long running function to generate a thumbnail
    
            $this->em->persist($myEntity);
            $this->em->flush();
        }
    }
    

    Your messenger.yaml config should look something like this:

    framework:
        messenger:
            transports:
                async: 'doctrine://default'
    
            routing:
                'AppMessageGenerateThumbMessage': async
    

    In the config, in stead of using the Entity, you have configured the Message to be processed async.

    A similary solution can be found in the API Platform documentation

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search