Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what's the proper way to use Symfony's EventDispatcher component?

I'd like to promote loose coupling in my PHP code, by making certain classes observable. Symfony's EventDispatcher component looks promising, as does the SPL SplObserver/SplSubject pair of classes.

What's the best way to do this? I can see a couple of different possibilities:

(1) Inject an EventDispatcher instance into each observable class (keeping track of a global EventDispatcher instance):

class Foo
{
    public function __construct($dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function bar()
    {
        $this->dispatcher->dispatch(...);
    }
}

(2) Have the observable class extend the EventDispatcher class:

class Foo extends EventDispatcher
{
    public function bar()
    {
        $this->dispatch(...);
    }
}

(3) Use SplObserver/SplSubject - straightforward, but not as flexible as the EventDispatcher component

like image 739
Josh Avatar asked Mar 19 '12 19:03

Josh


2 Answers

Disclaimer: this answer has nothing to do with the Symfony EventDispatcher but everything to do with your question. If you just want an answer you can skip the (somewhat) academic discussion and jump to the end.

Discussion

FACT: Increasing application size means a congruent increase in complexity.

As the scope of your application grows you find yourself adding more and more classes to implement the requisite functionality. All of a sudden, it's not so easy to remember that a Foo object needs to perform some specific action when a Bar object is created. Further, as your objects start providing complimentary functionality to one another it becomes increasingly more difficult to maintain the necessary relationships without ending up with very tightly coupled objects.

We need a way for objects to communicate without hard-coding explicit references that we'll forget to alter when something changes. So how do we manage this kind of interrelated functionality between nodes of a rapidly growing object graph?

If You Want It to Last, You've Gotta Talk

Let's take a slight detour for a moment and consider a romantic metaphor ...

Any relationship requires consistent communication if it's going to last. Sure, you and your partner can get together for shady hookups on Saturday night and not talk to each other the rest of the week. However, this type of communication usually results in a brittle relationship where neither party understands what the other actually needs to function well in the context of the relationship.

Continuing the analogy, as your personality slowly changes over time (and it will), this lack of communication prevents your partner from understanding how best to interact with you. Eventually, all the broken promises and missed calls come to a head and the relationship just doesn't work anymore. It's broken.

Your application works in the same way. Code should be mature enough to say, "Hey baby, I might change, but if I do, I promise I'll always let you know what's going on with me." Unfortunately, as complexity increases, traditional straight-line application design makes this communication difficult to maintain without tight coupling between classes.

Enter Event Management

And this is what event management is all about. The goal is to provide our objects a means to communicate with each other in a way that doesn't hard-code relationships to the objects they need to communicate with. As with most programming problems, there's not a singular, specific, "correct" way to do this. Your question specifically mentions two of the available methods for accomplishing this, so I'll address those. If you want to learn about some of the other options, @ircmaxell recently posted a nice survey blog post about making PHP apps "pluggable".

Observer

In practice you'll find few real-world PHP applications for the Observer pattern. This is because if you want your code to be very dynamic it doesn't take long before you're attaching observers to subject objects all over the place.

When this happens, you've got the loose coupling you started out trying to achieve, but you've created a different sort of problem: manually attaching all the observers and subjects. For example, you've created a lot of work for yourself if every class in your application is the subject of a Logger observer object. Also, IMHO this method sometimes obscures your API by moving things that might be more accurately described as actual dependencies of the subject out of the subject constructor's method signature.

Our application would be much more flexible if we used a centralized dispatcher to notify interested objects when an event occurs, though the Observer pattern can be ideal for one-off or simple cases.

Mediator

A more robust way to manage events is by inserting a centralized layer to handle dispatching events to the appropriate listeners. This is what the Mediatorwiki pattern (and the Symfony event dispatcher) does.

The point of a Mediator is that it's a centralized transit station for every event in the system, so it needs to be accessible across the entire scope of the application (or the mediated parts, anyway). Note carefully, this does not mean you should treat it as a global variable and access the Mediator willy-nilly with global keywords or wrap it in some sort of evil singleton object or static property/method. This kind of abuse will lead to the problems @liquorvicar posited in the first answer. However, I strongly disagree with that answer's assessment that:

"Having an eventDispatcher that's everywhere in your app and does almost everything can make your code harder to test/understand/maintain etc (it can approach a God object)"

This is only the case if you misuse the Mediator; it should dispatch event notifications and nothing else. I would caution you against extending it like you suggest in option (2) of your question for this reason. When used correctly, a mediator object is exceedingly testable. There's nothing simpler than mocking the behavior of dependency objects specified in your constructors. That's what unit-testing is all about.

Answer

So, if you need non-linear event management in your application I would strongly suggest option (1) from your question. It's perfectly acceptable as long as you don't abuse it. Glossing over the Symfony implementation it appears to support any PHP callable as a listener. Personally, I prefer a system that allows the lazy-instantiation of class-based listeners for a more efficient and object oriented paradigm, but the implementation details are up to you.

The Chain of Responsibility pattern is closely related to the Mediator and is another valid method for accomplishing similar results. If you're interested, I'd suggest the link posted earlier to @ircmaxell's blog post.

like image 94
rdlowrey Avatar answered Nov 02 '22 03:11

rdlowrey


I would avoid (2). Inheritance is possibly the most over-used pattern and probably isn't relevant here. The choice between options (1) and (3) probably depends on your context. Whilst avoiding tight coupling is good, you should be wary of swiss-army knife solutions. Having an eventDispatcher that's everywhere in your app and does almost everything can make your code harder to test/understand/maintain etc (it can approach a God object). On the other hand, the Spl solution is much simpler and hence if you do need multiple observers/observables you might find you are having to maintain too many SplObservers/SplSubjects.

As with most things in OOP, there is no best way, and will normally depend on your exact use cases...

like image 30
liquorvicar Avatar answered Nov 02 '22 02:11

liquorvicar