The idea of event dispatcher is to allow communication between different parts of the application without coupling them. In this way, an application can be easily maintained and extended, and it does not have to know about the classes that do it.
Event dispatcher can be used when refactoring legacy code, creating a new application, or adding new functionality to existing code with minimal changes.
Ways of extending behaviour
The most common way. Simply extend a class and original behaviour can be overwritten.
Here new SportCar class overwrites appropriate methods to extend base functionality.
Of course, you must be very careful overwriting behaviour. It is important to ensure that there was no violation of Liskov Substitution Principle and all your class hierarchy behaves as one data type.
Composition - is a way to create complex objects from single ones. In our example with cars we should abstract changeable functionality and place it into specialized classes.
In our case changeable functionality consists of two methods:
So we can extract an interface from them:
Then we create a family of classes that implement this interface. Each class will be specialized version of changeable behaviour.
Now we can inject these classes in our Car class through constructor. So we no longer need inheritance and SportCar class.
Here now we have polymorphism (one name - different logic). A single class Car can use different versions of CarDriveInterface. By using different implementations our car can behave like a simple car, like a sport car or any other implementation.
Mediator design pattern
Interface limits us with its methods. But what if we want to extend behaviour beyond interface functionality? Here comes Mediator Pattern. The Mediator pattern is a behaviour pattern. Its main purpose is to allow classes to communicate without knowing anything about each other. To achieve this pattern defines an intermediary class as a dispatcher. Dispatcher becomes a central hub for all communications between classes.
The consumer registers with the dispatcher to listen to events. Then the dispatcher notifies the consumer when event raises. Both the consumer and the producer of events know about dispatcher, but don’t know anything about each other.
When the producer raises event he sends it to the dispatcher. An event can be sent with an event object associated with this event. This object may contain information about the event. The producer doesn’t have to know anything about what happens next. The dispatchers job is to notify then the consumer which is waiting for the event.
The EventDispatcher Component
In Symfony the consumers are called listeners. Listeners are callable objects: class objects or functions. The Symfony Event Dispatcher component consists of 2 interfaces: EventDispatcherInterface and EventSubscriberInterface and a class Event.
The EventDispatcherInterface has methods to add, remove, get and check listeners, 2 methods to add and remove subscribers. A subscriber - is a specialized listener, that implements EventSubscriberInterface (“auto-configured” listener). In addition, it also provides a method for dispatching events.
Let’s create listener - a simple function that will send an email to a registered user.
Then we need to add our listener to the event dispatcher object. It is done by EventDispatcher::addListener() method. It has 3 parameters: an event to listen for, a callable listener and a priority value. Priority indicates in which order to call listeners, the higher the number, the higher the priority.
Every time the producer needs to dispatch an event it calls the dispatcher object with the name of the event:
Listeners and Subscribers
So what is the difference between them?
Let’s create a listener class and then add it to our dispatcher object.
Here, for listener the client code is responsible for telling the dispatcher what event each listener is registered to.
The difference with listeners comes in that subscribers can tell the dispatcher exactly which events they are listening for. That’s why all subscribers must implement the EventSubscriberInterface. This interface defines a static method getSubscribedEvents that returns an array with methods names, that should be called by the dispatcher for each event they subscribe. Let’s change mailSender class from a listener to subscriber.
Adding a subscriber to the dispatcher is simplier than adding listeners, becouse we don’t need to tell the dispatcher events names and priorities.