Installing EventDispatcher0:00
Next up, we will review Symfony's EventDispatcher component, and this is going to feel pretty familiar because, once again, Laravel makes use of this and wraps it up. So let's say, let's make a directory called events and cd in there, and then require it. composer require symfony/event-dispatcher. So I will now open that in Sublime, and we'll create an index.php file, and let's start per usual by requiring the vendor/autoload.php file so that we can autoload any class we need. Okay, so here's the way it works. Generally, your event dispatcher will be a singleton, or in other words, you're going to have one instance of this class.
Registering and Dispatching0:34
Generally, your event dispatcher will be a singleton, or in other words, you're going to have one instance of this class. And that way, anywhere in your project, you can add listeners to that single instance and then dispatch events to all of those listeners. So for example, I could say, create a new event dispatcher, and let's import that. Okay, so now we basically have an event dispatcher container, but we don't have any listeners bound. So imagine this. We want to add a listener, and literally, when I say listener, think of somebody listening for somebody else to broadcast something that took place.
We want to add a listener, and literally, when I say listener, think of somebody listening for somebody else to broadcast something that took place. So the symphony docs actually recommend that you use a dot notation, like user became premium or something like that. And in fact, this is what Laravel recommended as well. Not as quite a fan of this anymore. There is some value to it because you sort of, in a weird way, namespace your events. But instead, I prefer to use classes for my events, and then the full namespaced class, like user became premium class, you know, the full namespaced path for that, that will actually be the name of your event.
like User became premium class, you know, the full namespaced path for that, that will actually be the name of your event. But anyways, let's just stick with user signed up for now. We don't have an event class, we just have a string, a simple identifier. So when a user signs up, to start, I'm going to reference a closure here. So this will accept an event, and we're going to handle that event in some way. And I will import that once again. Okay, so to start, I'm just going to say, die handling it. To prove that it was called. But right now, if we were to run this in the browser, of course, nothing would happen.
To prove that it was called. But right now, if we were to run this in the browser, of course, nothing would happen. We've registered a listener, but there hasn't been any broadcast. There hasn't been anyone with a big microphone announcing that the User signed up. So let's do that now. Imagine this will be in your UsersController or your SubscriptionsController, anywhere there. And at some point, we're done creating the User, and now we're going to announce to the rest of the project that this User signed up. We can say dispatcher, dispatch, and now we're going to use that identifier, user signed.
rest of the project that this User signed up. We can say dispatcher, dispatch, and now we're going to use that identifier, user signed up. Okay, so let's just keep it like that, and then view this in the browser. And sure enough, we get handling it. Okay, so already it's working. And what's nice is, you can register multiple listeners. So maybe one listener sends the User a welcome email. Maybe another listener schedules a follow-up email. Maybe another listener updates a payments table or some kind of long-running task.
Maybe another listener schedules a follow-up email. Maybe another listener updates a payments table or some kind of long-running task. Anything like that. So let's do this. var_dump, and we are going to send a welcome email. And then this next one will be compile suggested things for the user to start doing. I don't know. Maybe something specific to how they signed up. I don't know. But anyways, if we come back and refresh, now we have two different listeners.
whatever class it might be. So in those situations, you make the announcement, like a User signed up, and then you allow other areas of your system to respond to that in their own specific ways. This guy responds to it by sending a welcome email, and this person responds to it by compiling suggested resources or something for the User. But now, if we were to just var_dump the event itself we receive, give that a refresh, yes, of course, it's an instance of the event class, but there's not much here to work with. So what you'll find is in most situations, you want to send through data with the event. So for example, the User signed up, well, which User? What's their name?
Creating Event Classes4:36
So for example, the User signed up, well, which User? What's their name? What's their email address? We didn't send through any of that data. So in these situations, it's useful to create dedicated event classes like this. We'll say class UserSignedUp. And by the way, if you use Laravel, and I assume you do, this is going to feel pretty familiar, right? Once again, Laravel just kind of wraps it up. It gives you a slightly cleaner interface.
Once again, Laravel just kind of wraps it up. It gives you a slightly cleaner interface. But behind the scenes, it's still making use of this same API. Okay, so basically we are subclassing Symfony's event class in order to provide additional data, such as the User who signed up, and I'm just going to make that public for now. Okay, so now we're going to dispatch that the User signed up, and specifically we're going to send through our custom event class. So that will be a new User signed up, where we give a User. And I'm just going to cast a dummy array here. Name is Joe.
And I'm just going to cast a dummy array here. Name is Joe. Email is joe@example.com. Yeah, in real life, that'll be like an instance of a User class or something. So we create an event, and this would be done somewhere in your system, whether it's a controller or a command handler or however you structure things. You create an event, and then you dispatch it through Symfony's event dispatcher. With Laravel, you might use the helpful event global function, or you might use event fire, or you might import Illuminate\Events\Dispatcher. All of those are going to point to the same thing.
or you might import Illuminate\Events\Dispatcher. All of those are going to point to the same thing. Okay, so now let's come back to Chrome and give this a refresh. And now you'll see, once again to remind you, let's get rid of this guy. This one is what gets handled. So if we come back, it receives an event class, but specifically our subclass called UserSignedUp. And now we can access any of the information associated with the event. So if I want to say, give me the user for the event, now we can get that. And it turns out the person who signed up has a name of Joe.
So if I want to say, give me the User for the event, now we can get that. And it turns out the person who signed up has a name of Joe. So you create an Event that corresponds to the thing that took place. And you might store that within an app, how about app/Events directory. And we'll call this UserSignedUp. Like so. Okay, makes sense. So now let's come back. And we would have to update this. So let me show you something.
Setting Up PSR-47:01
And we would have to update this. So let me show you something. Right now, this shouldn't work, right? If we give it a refresh, can't find the class. And that makes sense, right? It's a fresh project, we don't have PSR-4 autoloading. So let's do this. Autoload really quickly. PSR-4 and our app namespace will be stored within app. All right, so let's composer dump-autoload.
PSR-4 and our app namespace will be stored within app. All right, so let's composer dump-autoload. And then if I come back, I think that should fix it. And it did, but we just forgot to import the Event class. So we come back here. And now it's trying to extend Event, and it's assuming we have app\Events\Event, but we don't. So import that. Come back to Chrome, give it a refresh. And now we have exactly what we had before, but we now have a dedicated location for events.
Come back to Chrome, give it a refresh. And now we have exactly what we had before, but we now have a dedicated location for events. Now if we come back to index.php, notice that we have a bit of duplication going on. So earlier I said that the approach I like is to have a namespaced path to the class as the event name. But right now, the namespaced class is app\Events\UserSignedUp, but the event we're firing is just UserSignedUp. So instead, let's import this at the very top, like so. And then down here, yeah, we can replace this with UserSignedUp class, and that'll give us the full namespaced path.
And then down here, yeah, we can replace this with UserSignedUp class, and that'll give us the full namespaced path. Okay, so now if we come back and give it a refresh, we're not going to see anything. And that's because we're firing a different event now. We are literally firing app\Events\UserSignedUp. So that means that is what you need to listen for in your listeners. UserSignedUp class, or just write out the full class path. So give it a refresh, and now we have what we had before, but things are more consistent. And now, yeah, you don't have to use that dot syntax for your notation or for namespacing. You totally can if you want.
And now, yeah, you don't have to use that dot syntax for your notation or for namespacing. You totally can if you want. It's what the Symfony docs recommend. But I think, once again, this is a cleaner approach. So now let's do a recap, and then we'll move on to expanding our listeners. So we create an event dispatcher. Once again, that will be a singleton in your project. And in fact, if you use Laravel, you'll likely find an EventServiceProvider that does something like this app singleton events. And then it returns a Laravel specific instance of the event dispatcher.
like this app singleton events. And then it returns a Laravel specific instance of the event dispatcher. Okay, but anyways, once you have your dispatcher, you can resolve it out of the container using whatever DI container you have. And then you call a dispatch method. You give it the name of the event, and then you give it the event itself. Now once again, you don't have to provide an event. But just know, if you don't, you are just sending a message out into the world, and you're not providing any context. You're not providing any data.
you're not providing any context. You're not providing any data. And in some cases, that's okay. When no data is really associated with it, no problem there. But I think most of the time, you will want to provide information for the event, like the person who signed up, or the order that was processed, or the item that was refunded. Any of those would be relevant. So in those cases, you create an Event class. You have it extend Symfony\Component\EventDispatcher\EventDispatcher. And now, yeah, it's just a simple subclass.
You have it extend Symfony's event dispatcher. And now, yeah, it's just a simple subclass. Inject any data that is relevant. And now that is the event you will pass when you dispatch this event. Now your listener can be registered with the dispatcher as well. And yeah, this is just a simple mediator pattern, or some people call it PubSub. I kind of prefer that, honestly, because it's simpler. But the exact same thing here. So we add a listener to the dispatcher. And then when you fire, or when you dispatch the event, all it's doing behind the scenes
Using Listener Classes10:47
So we add a listener to the dispatcher. And then when you fire, or when you dispatch the event, all it's doing behind the scenes is it's reading the name of the event you want to fire. And then it's going into its listeners, and it's saying, okay, give me all of the listeners that were registered for this specific event type. And then I'm going to filter through each of those and dispatch them, or trigger them. So closures can be really useful for basic stuff. But you might find in some cases you need a bit more power there. So you could create a dedicated listener class. You could say app/Listeners, and we'll do the, let's do a SendThankYouEmail.
So you could create a dedicated listener class. You could say app/Listeners, and we'll do the, let's do a SendThankYouEmail. Okay, so once again, we set our namespace, and then our class, SendThankYouEmail. Now if we come back, we don't pass a closure. Let's do this. Let's copy that, and then paste it. Okay, so add a listener, but this time we won't trigger a closure. Instead, we're going to trigger our listener instance, and then specifically a method on it that we wish to call. So let's add that.
it that we wish to call. So let's add that. A handle method. Now here is where you would send the email using whatever your framework or your library provides. Okay, so it sounds like we need to new up our listener. New SendThankYouEmail, and I will import that at the top. And now, once again, we register a listener for when a User signs up, and we want to respond to that by triggering this class in this method. So if we come back to Chrome and we give it a refresh, now we've triggered it, but
to that by triggering this class in this method. So if we come back to Chrome and we give it a refresh, now we've triggered it, but once again, we've moved from a closure up to a class. And that doesn't mean it's better, by the way. There's a lot of value from these nice closures here. But yeah, when you need a little more flexibility, maybe your listener has dependencies of its own, yeah, you should go for that route. Now here's another option you could do. If you just pass the listener itself, this is going to fail. But one thing you can do is you could add an invoke method.
If you just pass the listener itself, this is going to fail. But one thing you can do is you could add an __invoke method. So basically, if an __invoke method is declared on the class, then it will trigger that, and now you'll get the exact same thing. So yeah, I mean, that's an option as well. I think I would tend to stick with the named version. And that way, potentially, you could have multiple listeners on the same class. It just depends on how you want to structure things. Now you can even take this further. You could register event subscribers.
Full Workflow Example13:17
Now you can even take this further. You could register event subscribers. Once again, just like you do in Laravel, well, that's actually coming from Symfony. And that can have its use cases. But you can maybe review that on your own. So why don't we finish up by just reviewing the workflow one last time. Maybe you can work along, and then we'll call it a day. Okay, so we want to prepare an event that a User became a forever subscriber. That's something you can do at Laracast. You pay one time $350, but then you basically have access to the site for life.
That's something you can do at Laracast. You pay one time $350, but then you basically have access to the site for life. So let's do that. You're going to create an event dispatcher. And again, remember, that's something you would do only once, and then you put it into your container as a singleton. So it's not like you create a new event dispatcher every time you want to prepare a new event. You'd only do it once for project, ideally. Anyways, we are going to somewhere in our project, so the part of the controller where you upgrade the user to forever.
Anyways, we are going to somewhere in our project, so the part of the controller where you upgrade the User to forever. Or it doesn't have to be the controller. It could be your model, anything you want. Anyways, at that point, we're going to dispatch an event. And by the way, let's get rid of these old namespace versions. Okay, but anyways, the class I want to dispatch is UserBecameForeverSubscriber. So let's send that through. And then I will new up that class. And it doesn't exist yet, but that's okay.
And then I will new up that class. And it doesn't exist yet, but that's okay. New User became forever subscriber. And then once again, we're going to cheat and then pass through just the name of some person who signed up. Now we'll save that to event. And then that event containing the data for Samantha will be dispatched. So let's create that. App\Events\UserBecameForeverSubscriber. This App\Events, create the class, have it extend Event, import the event.
App, events, User became forever subscriber. This app, events, create the class, have it extend Event, import the Event. And then here, I'll use a little macro to accept the User who signed up. And then I will make that public. I don't mind public methods on Events. Some people really freak out about it. If so, make it protected and then add a getter method. So there's our Event. We can come back here and import that at the top. And now if we switch to Chrome, we wouldn't see anything, right?
We can come back here and import that at the top. And now if we switch to Chrome, we wouldn't see anything, right? And that's because we fired an event or we dispatched an event. Once again, that's something like this with Laravel. But there are no listeners registered for this event. And for Laravel, you would go to your EventServiceProvider, and then you would add a list of events and listeners within that array in your EventServiceProvider. But anyways, let's register an event listener. Add a listener for when a User became a forever subscriber. And we'll start with the closure.
Add a listener for when a User became a forever subscriber. And we'll start with the closure. dump, send a thank you email. Here's the first option. Give it a refresh. And now we are responding. Maybe we have another one where we prepare a site-wide notification announcing the upgrade. I don't know. That would be weird. But yeah, you can handle that event in multiple ways.
That would be weird. But yeah, you can handle that event in multiple ways. As many ways as you need to. Just register a new listener. Now this also, of course, accepts the event. So we could say to, and then the event User's name. And that will be Samantha. And further, this will now be an instance of User became forever subscriber. So that works. Or, like we said, you can register a dedicated listener class.
So that works. Or, like we said, you can register a dedicated listener class. Like this. App\Listeners\SendThankYouEmail. Oh, I guess we already had that from before. Good enough. So we're going to use that. Pass through the listener and the method we wish to call on it. And then let's just build up the listener. New SendThankYouEmail.
And then let's just build up the listener. New, sendThankYouEmail. And import that. So if we come back and give it a refresh, once again we get the exact same thing. Or here we could say, User became forever subscriber. Import that. And then once again, to event userName. Cool, right? And I think this is really readable. So we have a class called SendThankYouEmail.
And I think this is really readable. So we have a class called SendThankYouEmail. And we're going to handle the case that a User became a forever subscriber. And we handle it by delegating to a mail dependency or something like that. So that about does it for this lesson. Again, just for organizational purposes, this might be in some form of an IOC container or a ServiceProvider. That's where you might register that. Or if you use Laravel, it gets done for you. You don't need to know any of this.
Or if you use Laravel, it gets done for you. You don't need to know any of this. Next, your listener, yeah, if you use Laravel, that will be in your EventServiceProvider class. And you would do something like this. When a User became a forever subscriber, then I want to trigger this array of listeners. But here's the long form way of doing it. And of course, behind the scenes, Laravel also takes the liberty of newing up these classes for you and handling automatic resolution and all of that cool stuff.
And of course, behind the scenes, Laravel also takes the liberty of newing up these classes for you and handling automatic resolution and all of that cool stuff. Finally, this is where you dispatch the event. And this is where, once again, it could be like in a command handler if you want. It could be in a service class. It could be right in your controller. You inject your dispatcher. You call a dispatch method. You give it the name of the class. And then you new up the event.
You give it the name of the class. And then you new up the event. So once again, with Laravel, that would be something like new UserBecameForeverSubscriber. That's how it would look in Laravel. But behind the scenes, in a roundabout way, this is sort of what gets done, just in a much more mature way. Okay, so that is Symfony's event dispatcher component. Very useful. Heavily leveraged by Laravel.
