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 SubscribeWe already organized our new event class into an Event
subdirectory. Cool! Let's do the same thing for our commands. Create a new Command/
sub-directory, move the two command classes inside... then add \Command
to the end of the namespace on both classes.
Let's see... now that we've changed those namespaces... we need to update a few things. Start in messenger.yaml
: we're referencing AddPonkaToImage
. Add Command
to that class name. Next, in ImagePostController
, all the way on top, we're referencing both commands. Update the namespace on each one.
And finally, in the handlers, we have the same thing: each handler has a use
statements for the command class it handles. Add the Command\
namespace on both.
Cool! Let's do the same thing for the handlers: create a new subdirectory called Command/
, move those inside... then add the \Command
namespace to each one. That's... all we need to change.
I like it! There was nothing technical about this change... it's just a nice way to organize things if you're planning to use more than just commands - meaning events or query messages. And everything will work exactly the same way it did before. To prove it, at your terminal, run debug:messenger
:
php bin/console debug:messenger
Yep! We see the same info as earlier.
But... now that we've separated our event handlers from our command handlers... we can do something special: we can tie each handler to the specific bus that it's intended for. Again, it's not super important to do this, but it'll tighten things up.
Let me show you: open up config/services.yaml
. This App\
line is responsible for auto-registering every class in the src/
directory as a service in the container.
The line below repeats that for classes in the Controller/
directory. Why? This will override the controller services registered above and add a special tag that controllers need to work.
We can use a similar trick with Messenger. Say App\MessageHandler\Command\
, then use the resource
key to re-auto-register all the classes in the ../src/MessageHandler/Command
directory. Whoops - I typo'ed that directory name - I'll see a huge error in a few minutes... and will fix that.
... lines 1 - 7 | |
services: | |
... lines 9 - 29 | |
App\MessageHandler\Command\: | |
resource: '../src/MessageHandler/Command' | |
... lines 32 - 44 |
If we only did this... absolutely nothing would change. This would register everything in this directory as a service... but that's already done by the first App\
entry anyways.
But now we can add a tag to this with name: messenger.message_handler
and bus:
set to... the name of my bus from messenger.yaml
. Copy messenger.bus.default
and say bus: messenger.bus.default
.
... lines 1 - 7 | |
services: | |
... lines 9 - 29 | |
App\MessageHandler\Command\: | |
resource: '../src/MessageHandler/Command' | |
... line 32 | |
tags: [{ name: messenger.message_handler, bus: messenger.bus.default }] | |
... lines 34 - 44 |
There are a few things going on here. First, when Symfony sees a class in our code that implements MessageHandlerInterface
, it automatically adds this messenger.message_handler
tag. This is how Messenger knows which classes are message handlers.
We're now adding that tag manually so that we can also say exactly which one bus this handler should be used on. Without the bus
option, it's added to all buses.
We also need to add one more key: autoconfigure: false
.
... lines 1 - 7 | |
services: | |
... lines 9 - 29 | |
App\MessageHandler\Command\: | |
resource: '../src/MessageHandler/Command' | |
autoconfigure: false | |
tags: [{ name: messenger.message_handler, bus: messenger.bus.default }] | |
... lines 34 - 44 |
Thanks to the _defaults
section on top, all services in our src/
directory will, by default, have autoconfigure
enabled... which is the feature that's responsible for automatically adding the messenger.message_handler
tag to all services that implement MessageHandlerInterface
. We're turning it off for services in this directory so that the tag isn't added twice.
Phew! You can see the end result by running debug:messenger
again.
php bin/console debug:messenger
Oh, the end result is a huge error thanks to my typo! Make sure you're referencing the MessageHandler
directory. Try debug:messenger
again:
php bin/console debug:messenger
Nice! The event bus no longer says that we can dispatch the two commands two it. What this really means is that the command handlers were added to the command bus, but not to the event bus.
Let's repeat this for the events: copy this section, paste, change the namespace to Event\
, the directory to Event
and update the bus
option to event.bus
- the name of our other bus inside messenger.yaml
.
... lines 1 - 7 | |
services: | |
... lines 9 - 34 | |
App\MessageHandler\Event\: | |
resource: '../src/MessageHandler/Event' | |
autoconfigure: false | |
tags: [{ name: messenger.message_handler, bus: event.bus }] | |
... lines 39 - 44 |
Cool! Try debug:messenger
again:
php bin/console debug:messenger
Perfect! Our two command handlers are bound to the command bus and our one event handler is tied to the event bus.
Again, doing this last step wasn't that important... but I do really like these sub-directories... and tightening things up is nice.
Oh, but while we're cleaning things up, back in config/packages/messenger.yaml
, our main bus is called messenger.bus.default
, which becomes the bus's service id in the container. We used this name... just because that's the default value Symfony uses when you have only one bus. But because this is a command bus, let's... call it that! Rename it to command.bus
. And above, use that as our default_bus
.
framework: | |
messenger: | |
default_bus: command.bus | |
buses: | |
command.bus: | |
... lines 7 - 35 |
Where was the old key referenced in our code? Thanks to the fact that we autowire that service via its type-hint... almost nowhere - just in services.yaml
. Change the bus option to command.bus
as well.
... lines 1 - 7 | |
services: | |
... lines 9 - 29 | |
App\MessageHandler\Command\: | |
... lines 31 - 32 | |
tags: [{ name: messenger.message_handler, bus: command.bus }] | |
... lines 34 - 44 |
Check everything out by running debug:messenger
one more time:
php bin/console debug:messenger
That's nice: two buses, each with a great name and only aware of the correct handlers.
Oh, and this AuditMiddleware
is something that we really should also use on event.bus
: it logs the journey of messages... which is equally valid here.
framework: | |
messenger: | |
... lines 3 - 4 | |
buses: | |
... lines 6 - 9 | |
event.bus: | |
... line 11 | |
middleware: | |
- App\Messenger\AuditMiddleware | |
... lines 14 - 37 |
If you love this organization, great! If it seems like too much, keep it simple. Messenger is here to do what you want. Next, let's talk about the last type of message bus: the query bus.
Hey Gianluca
Yea, I don't see a problem injecting two Message busses into the same service, as long as you have a valid use-case
Cheers!
Hello guys,
In Symfony 6.2 when I run php bin/console debug:messenger
, I see there a bunch of components like Symfony\Component\Mailer\Messenger\SendEmailMessage
, Symfony\Component\Notifier\Message\PushMessage
... go to all the bus that I have. The problem is that I have a bus (say bus A) with a middleware which have logic only for one message handler. So if I understand correctly, the SendEmailMessage
for instance could go to my bus A, through the middleware of that bus A and crash. If that's correct how can I prevent it, is there a way to say my bus A is for only one kind of message and no one else should go through it?
Thanks
Hey @Dang!
Hmm. Let's think about this. There are 2 ways for a message to be dispatched through a bus:
A) YOU are dispatching it directly in your code. So you have control of which message bus you're using.
B) Some 3rd party code (e.g. Symfony) dispatches the message. In this case, by convention, Symfony always uses the "main" message bus. Well, more precisely, it uses the main message bus, unless you tell it otherwise: https://github.com/symfony/symfony/blob/43066ff98d158946f6b5f8473802c9301f0abf49/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L2584-L2589 - the same is true for notifier - https://github.com/symfony/symfony/blob/43066ff98d158946f6b5f8473802c9301f0abf49/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L2692-L2696
Via your own code or this config, you can, in practice, control which messages go to which bus. However, on a purely theoretical level, there would still be nothing stopping something from dispatching the "wrong" message into the wrong bus. And that's why you see debug:messenger
reporting things like SendEmailMessage
under your bus (because it is theoretically possible for this to happen). There's no way to strictly prevent this (someone could always grab your bus and call ->dispatch()
on it), but if you're concerned, I would add a middleware to your bus that detects if an "invalid" message is dispatched and throws an exception. It would still "crash" but at least in a controlled way (and then ideally you'd be notified and could fix whatever bug caused this).
Anyway, I hope this explanation helps!
Cheers!
// 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
}
}
We are using two bus for command and event; let you have an Handler who react to an Event. This handler has to perform an action that usually is handled by a Command but after it has to dispatch an Event.
Example: we have a microservices who send SMS. There is a main command "SendSMSCommand"
Another MS need to send an SMS when dispatching an Event. This Event is catched from SMS with a dedicated Handler, who has to inject: eventBus AND commandBus, cause it has to use CommandBus to handle the sending of SMS, and eventBus to throw an new SmsSentEvent. Is it correct? To use two buses in an EventHandler?