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 SubscribeMessenger is a "message bus". And it turns out that a "message" is a pretty generic term in computer science. In fact, there are three types of messages you'll commonly hear about.
The first type of message is a "command". And that is the type we've been creating so far: we create message classes that sound like a command: AddPonkaToImage
or DeleteImagePost
and whose handlers do some action. When you create message classes & handlers that look like this, you're using Messenger as a "command bus". And one of the, sort of, "rules" of commands is that each command should have exactly one handler. That's the "command" design pattern.
The second type of message is an "event". If you create an "event" class and pass it to Messenger, then you're using Messenger as an "event" bus. The difference between what a "command" class looks like and what an "event" class looks like is subtle: it comes down to naming conventions and what you're ultimately trying to accomplish. An event is dispatched after something happens and can have zero to many handlers. Don't worry, we'll see what this looks like soon.
The third type of message is a "query" and we'll talk about those later. For now, let's focus on understanding events and how they're different from commands... because... it can be super confusing. And Messenger, being a generic "message bus" works perfectly with either.
Before we create our first event, I'll close a few things and then open config/packages/messenger.yaml
. If our app leverages both commands and events, it's totally ok to use just one bus to handle all of that. But, in the interest of making our life a bit more difficult and learning more, let's continue to use our existing bus only as a command bus and create a new bus to only use with events.
To do that, under the buses:
key, add a new one called, how about, event.bus
. Set this to ~
which is null... just because we don't have any other configuration that we need to put here yet. This will cause a new MessageBus
service to be added to the container.
framework: | |
messenger: | |
buses: | |
... lines 4 - 7 | |
event.bus: ~ | |
... lines 9 - 32 |
So far, whenever we needed the message bus - like in ImagePostController
- we autowired it by using the MessageBusInterface
type-hint. The question now is: how can we get access to the new message bus service?
Find your terminal and run:
php bin/console debug:autowiring
... which... explodes! My bad:
Invalid configuration for path
framework.messenger
: you must specifydefault_bus
Copy the name of the default bus. Once you define more than one bus, you need a default_bus
key set to your "main" bus. This tells Symfony which MessageBus service to pass us when we use the MessageBusInterface
type-hint.
framework: | |
messenger: | |
default_bus: messenger.bus.default | |
buses: | |
... lines 6 - 9 | |
event.bus: ~ | |
... lines 11 - 34 |
Try the debug:autowiring
command again... and search for "mess".
php bin/console debug:autowiring
Ah, now we have two entries! This tells me that if we use the MessageBusInterface
type-hint, we'll get the messenger.bus.default
service. Ignore the debug.traced
part - that's just Symfony adding some debug tools. But now, if you use the MessageBusInterface
type-hint and you name the argument $eventBus
, it will pass you the new event bus service!
This is a new feature in Symfony 4.2 where you can autowire things by a combination of the type-hint and argument name. Symfony took the name of our bus - event.bus
- and made it possible to use the $eventBus
argument name.
Great! We now know how to get the event bus! But.. what's the difference between these two buses? Do they behave differently? The answer is... no!
A bus is nothing more than a set of middleware. If you have two bus objects that have the same middleware... well then... those message buses effectively are identical! So, other than the fact that, so far, we've only added our AuditMiddleware
to the first bus, these buses will work and act identically. That's why, even though I've created one service to handle commands and another service to handle events... ah... we really could send all our commands and events to just one service.
Next, let's create an event, learn what it looks like, why we might use them, and how they're different than commands.
Hey Sergey Smile!
Great question :). Yes, this is possible, but instead of just relying on the "add __invoke() with the type-hint for the event that should be handled" magic, you'll need to make your handler implement MessageSubscriberInterface
, which will allow you to say exactly which messages your handler handlers and which methods should be called :). https://symfony.com/doc/current/messenger.html#handler-subscriber-options
Another option would be to make both event classes implement the same interface and have your handler type-hint that interface. I believe (could be wrong) that your handler would then handle any messages that implement that interface.
Let me know if that helps!
Cheers!
Thank you for the fastest answer in the world ;)
My bad, I feel like an ... I assure you that MessageSubscriberInterface was missing in docs last time I was reading it...
So, your faith is true. I just tried with Interface for events and it works. But I think using MessageSubscriberInterface is more obviously way.
// 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
}
}
Thanks a lot for awesome tutorial.
Using events through messenger sounds like a good idea. They can be async, can be handled by different handlers.
But what about if i need be possible to handle multiple events by one handler like i can do it in event subscriber.
For example:
I have event NewDataSaved witch has Handler UpdateTotalAmount. And now i'm implementing new feature and dispatch new event DataUpdated.
Is it possible to use UpdateTotalAmount handler to handle both events: NewDataSaved and NewDataUpdated?