Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Sending Handlers to Different Transports: from_transport

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The last option I want to mention is interesting... but can also be confusing. It's called from_transport.

If you look at messenger.yaml, this DeleteImagePost isn't being routed anywhere, which means it's handled synchronously. Let's pretend that we want to handle it asynchronously... and that we're routing it to the async transport. Set from_transport to async...

... lines 1 - 12
class DeleteImagePostHandler implements MessageSubscriberInterface
... lines 15 - 34
public static function getHandledMessages(): iterable
yield DeleteImagePost::class => [
... lines 38 - 44
'from_transport' => 'async'

then temporarily route this class to that transport in messenger.yaml.

Now, pretend that the DeleteImagePost message actually has two handlers... something that's very possible for events. Assuming that we did not add this from_transport config yet, if you sent DeleteImagePost to the async transport, then when that message is read from that transport by a worker, both handlers will be executed one after another.

But what if you wanted to, sort of, send one handler of that message to one transport, maybe async_priority_high, and another handler to another transport. Well, in Messenger, you don't send "handlers"... you send messages... and when Messenger consumes a message, it calls all the handlers for that message. Does that mean it's impossible to make one handler of a message "high" priority and another one low? Nope! This workflow is possible.

Route to Two Transports

First, route DeleteImagePost to both the async and async_priority_high transports.

... lines 3 - 34
... lines 36 - 38
'App\Message\Command\DeleteImagePost': [async, async_priority_high]

If we only did this, the message would be sent to both transports, it would be consumed two times, and every handler would be called twice... which is totally not what we want... unless each handler is baking cookies... or something.

But when we add this from_transport option set to async, it means that this handler should only be called when a DeleteImagePost object is consumed from the async transport. If we configured a second handler with from_transport set to async_priority_high, that handler would only be called when the message is being consumed from that transport.

In other words, you're sending the message to two transports, but each transport knows that it should only execute one handler. This allows your two handlers to be queued and executed by workers independently of each other. It's a really powerful feature... but because Messenger is centered around sending messages to transports, over-using this can be confusing.

Let's comment that out and remove the routing config.

... lines 1 - 12
class DeleteImagePostHandler implements MessageSubscriberInterface
... lines 15 - 34
public static function getHandledMessages(): iterable
yield DeleteImagePost::class => [
... lines 38 - 44
//'from_transport' => 'async'

That's basically it for the options you can pass here... though you can always check MessageSubscriberInterface: it talks about what's available.

Next, let's up our queueing game by changing from the Doctrine transport to RabbitMQ - also commonly referred to as AMQP. It's buckets of fun!

Leave a comment!

Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial is built with Symfony 4.3, but will work well on Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // v1.8.0
        "doctrine/doctrine-bundle": "^1.6.10", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
        "doctrine/orm": "^2.5.11", // v2.6.3
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.1
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.9", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/property-access": "4.3.*", // v4.3.2
        "symfony/property-info": "4.3.*", // v4.3.2
        "symfony/serializer": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.7
        "symfony/debug-bundle": "4.3.*", // v4.3.2
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/var-dumper": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2