Chapters
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.1.3
Subscribe to download the code!Compatible PHP versions: ^7.1.3
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeI love the ability to defer work for later by sending messages to a transport. But, there is at least one practical bummer: it makes it a bit harder to actually develop and code your app. In addition to setting up your web server, database and anything else, you now need to remember to run:
php bin/console messenger:consume
Otherwse... things won't fully work. If you have a robust setup for local development - maybe something using Docker - you could build this right into that setup so that it runs automatically. Except... you'd still need to remember to restart the worker any time you make a change to some code that it uses.
It's not the worst thing ever. But, if this drives you crazy, there is a really nice solution: tell Messenger to handle all of your messages synchronously when you're in the dev
environment.
Hello "sync" Transport
Check out config/packages/messenger.yaml
. One of the commented-out parts of this file is a, kind of, "suggested" transport called sync
. The really important part isn't the name sync
but the DSN: sync://
. We learned earlier that Messenger supports several different types of transport like Doctrine, redis and AMQP. And the way you choose which one you want is the beginning of the connection string, like doctrine://
. The sync
transport is really neat: instead of truly sending each message to an external queue... it just handles them immediately. They're handled synchronously.
Making the Transports sync
We can take advantage of this and use a configuration trick to change our async
and async_priority_high
transports to use the sync://
transport only when we're in the dev
environment.
Go into the config/packages/dev
directory. Any files here are only loaded in the dev
environment and override all values from the main config/packages
directory. Create a new file called messenger.yaml
... though the name of this file isn't important. Inside, we'll put the same configuration we have in our main file: framework
, messenger
, transports
. Then override async
and set it to sync://
. Do the same for async_priority_high
: set it to sync://
.
framework: | |
messenger: | |
transports: | |
async: 'sync://' | |
async_priority_high: 'sync://' |
That's it! In the dev environment, these values will override the dsn
values from the main file. And, we can see this: in an open terminal tab, run:
php bin/console debug:config framework messenger
This command shows you the real, final config under framework
and messenger
. And... yea! Because we're currently in the dev
environment, both transports have a dsn
set to sync://
.
I do want to mention that the queue_name
option is something that's specific to Doctrine. The sync
transport doesn't use that, and so, it ignores it. It's possible that in a future version of Symfony, this would throw an error because we're using an undefined option for this transport. If that happens, we would just need to change the YAML format to set the dsn
key - like we do in the main messenger.yaml
file - and then override the options
key and set it to an empty array. I'm mentioning that just in case.
Ok, let's try this! Refresh the page to be safe. Oh, and before we upload something, go back to the terminal where our worker is running, hit Control+C to stop it, and restart it. Woh! It's busted!
You cannot receive messages from the sync transport.
Messenger is saying:
Yo! Um... the SyncTransport isn't a real queue you can read from... so stop trying to do it!
It's right... and this is exactly what we wanted: we wanted to be able to have our handlers called in the dev
environment without needing to worry about running this command.
Ok, now let's try it: upload a couple of photos and... yea... it's super slow again. But Ponka is added when it finishes. The messages are being handled synchronously.
To make sure this is only happening for the dev
environment, open up the .env
file and change APP_ENV
to be prod
temporarily. Make sure to clear your cache so this works:
php bin/console cache:clear
Now, we should be able to run messenger:consume
like before:
php bin/console messenger:consume -vv async_priority_high async
And... we can! Sync messages in dev, async in prod.
Now that we've accomplished this, change APP_ENV
back to dev
and, just to keep things more interesting for the tutorial, comment out the new sync
config we just added: I want to continue using our real transports while we're coding. Stop and restart the worker:
framework: | |
messenger: | |
# transports: | |
# async: 'sync://' | |
# async_priority_high: 'sync://' |
Now that we're back in the dev
environment, stop and restart the worker:
php bin/console messenger:consume -vv async_priority_high async
Next: let's talk about a similar problem: how do you handle transports when writing automated tests?
4 Comments
Hey Matias C.
There is a way to configure your handler so they can decide which transport to use. Check out this chapter https://symfonycasts.com/screencast/messenger/from-transport
but based on your situation I'd just execute the handler directly in my cronjob command e.g.
// SomeCommand.php
public function __construct(SomeHandler $someHandler)
{
$this->someHandler = $someHandler;
}
protected function execute(Input $input, Output $output)
{
// execute the __invoke method of this handler
($this->someHandler)(
new SomeMessage($argument);
);
}
Cheers!
Hi @MolloKhan thanks for the answer.
Cool thing calling the handler directly, I hadn't thought of that.
On the other hand, the chapter you sent me it's not about choosing transport, but choosing handlers (right?). The handler decides wether to execute or not depending on the transport the message has been sent to.
I'm still confused about how to programmatically route a message to one or another transport. I was browsing the code and there's a HandlersLocatorInterface that looks promising, but I can't find any documentation about it. I can see that there's a compiler pass that configures a HandlersLocator with the yaml configuration, but nothing more.
It would be cool to use a stamp to define which handler to use.. does it make any sense?
Cheers!
Hey Matias C.
Yes, you're right, that's only for choosing which transport to use. I was looking for a solution to your problem and couldn't find anything buit-in. What you can do is to create your own stamp and transport. In the sender you will check if the envelope has such stamp and then decide which transport to use
Here you can read how to create a custom Transport https://symfony.com/doc/cur...
Cheers!
"Houston: no signs of life"
Start the conversation!
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.21.6
"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
}
}
Hi there , thanks for this series, it's very good.
I have a question dough.. What do you think would be the correct approach to dynamically change if a message should be handled in the sync or async transport? I have a message that can be dispatched in a cronjob or in a controller. In the cronjob I want it to be handled sync and in the controller async.
Thanks a lot!