PHP & Symfony About PHP and Symfony2 development

Decoupling your (event) system

Posted on by Matthias Noback

About interface segregation, dependency inversion and package stability

You are creating a nice reusable package. Inside the package you want to use events to allow others to hook into your own code. You look at several event managers that are available. Since you are somewhat familiar with the Symfony EventDispatcher component already, you decide to add it to your package's composer.json:

{
    "name": "my/package"
    "require": {
        "symfony/event-dispatcher": "~2.5"
    }
}

Your dependency graph now looks like this:

Dependency graph

Introducing this dependency is not without any problem: everybody who uses my/package in their project will also pull in the symfony/event-dispatcher package, meaning they will now have yet another event dispatcher available in their project (a Laravel one, a Doctrine one, a Symfony one, etc.). This doesn't make sense, especially because event dispatchers all do (or can do) more or less the same thing.

Though this may be a minor inconvenience to most developers, having just this one extra dependency may cause some more serious problems when Composer tries to resolve version constraints. Maybe a project or one of its dependencies already has a dependency on symfony/event-dispatcher, but with version constraint >=2.3,<2.5...

Some drawbacks of the Symfony EventDispatcher

On top of these usability issues, when it comes to design principles, depending on a concrete library like the Symfony EventDispatcher is not such a good choice either. The EventDispatcherInterface which you will likely use in your code is quite bloated:

namespace Symfony\Component\EventDispatcher;

interface EventDispatcherInterface
{
    public function dispatch($eventName, Event $event = null);

    public function addListener($eventName, $listener, $priority = 0);
    public function addSubscriber(EventSubscriberInterface $subscriber);
    public function removeListener($eventName, $listener);
    public function removeSubscriber(EventSubscriberInterface $subscriber);
    public function getListeners($eventName = null);
    public function hasListeners($eventName = null);
}

The interface basically violates the Interface Segregation Principle, which means that it serves too many different types of clients: most clients will only use the dispatch() method to actually dispatch an event, and other clients will only use addListener() and addSubscriber(). The remaining methods are probably not even used by any of the clients, or only by clients that help you to debug your application (a quick search in the Symfony code-base confirms this suspicion).

Personally, I think it's not really nice that events need to be objects extending the Event class. I understand why Symfony does it (it's basically because that class has the stopPropagation() and isPropagationStopped() methods which enables event listeners to stop the dispatcher from notifying the remaining listeners. I've never liked that, nor wanted it to happen in my code. Just like the original Observer pattern prescribes, all listeners (observers) should be able to respond to the current situation.

I also don't like the fact that the same event class can be used for different events (differentiated by just their name, which is the first argument of the dispatch() method). I prefer each type of event to have its own class. Therefore, to me it would make sense to pass just an event object to the dispatch() method, allowing it to return its own name, which will always be the same anyway. The name returned by the event itself can then be used to determine which listeners need to be notified.

According to these objections to the design of the Symfony EventDispatcher classes, we'd be better off with the following clean interfaces:

namespace My\Package;

interface Event
{
    public function getName();
}

interface EventDispatcher
{
    public function dispatch(Event $event);
}

It would be really great to be able to just use these two interface in my/package!

Symfony EventDispatcher: the good parts

Still, we also want to use the Symfony EventDispatcher. Above all we are familiar with it, but it also has some nice options, for example lazy-loading listeners, and when used in a Symfony application it offers an easy way to hook up listener services using service tags.

Introducing the Adapter

So we want to use our own event dispatching API, defined by the interfaces described above, but we also want to use the Symfony EventDispatcher which has a different public API. The solution is to create an adapter which bridges the gap between the two interfaces (following the famous Adapter pattern). In our case:

use My\Package\Event;
use My\Package\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class SymfonyEventDispatcher implements EventDispatcher
{
    private $eventDispatcher;

    public function __construct(EventDispatcherInterface $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    public function dispatch(Event $event)
    {
        // call the relevant listeners registered to the Symfony event dispatchers

        ...
    }
}

It can be a bit tricky to follow what's going on because of all the names that are so much alike. But as you can see, a Symfony event dispatcher gets injected as a constructor argument into the adapter class. When the dispatch() method gets called, the adapter forwards the call to the Symfony event dispatcher.

Well, it's not very straight-forward to just forward the dispatch() call to the Symfony event dispatcher, since as we saw earlier that its method signature looks like this:

namespace Symfony\Component\EventDispatcher;

interface EventDispatcherInterface
{
    public function dispatch($eventName, Event $event = null);
    ...
}

And unfortunately we don't have an object of type Symfony\Component\Event lying around, and we don't want one either, since that would totally reintroduce coupling to the Symfony component again.

Luckily the interface has another method we can use to bridge the gap: getListeners(), which means we can fetch the listeners ourselves and notify them manually:

class SymfonyEventDispatcher implements EventDispatcher
{
    ...

    public function dispatch(Event $event)
    {
        $listeners = $this->eventDispatcher->getListeners($event->getName());

        foreach ($listeners as $listener) {
            call_user_func($listener, $event);
        }
    }
}

We have now avoided the use of the Symfony Event class entirely. Inside our package we only need to use our own EventDispatcher interface and Event interface. This means we can remove the dependency on the symfony/event-dispatcher package altogether... almost!

The adapter class needs to be in its own package, to be able to fully decouple our package from symfony/event-dispatcher. Depending on what other (Symfony) components we are going to decouple from, we might call the adapter package my/package-symfony-bridge, or my/package-symfony-event-dispatcher, etc.

The resulting package dependency graph is quite beautiful:

New dependency graph

If you've read about package design principles before, you will know that this is a really nice constellation of packages because my/package does not depend on symfony/event-dispatcher anymore. In fact, it doesn't depend on anything anymore. We call such a package an independent package. At the same time, there are only packages depending on it, which makes my/package responsible. Being independent and responsible is what makes a package stable.

Also I'd like to point out that basically we have just applied an old and well-known design principle here: the Dependency inversion principle: we inverted the dependency direction. Instead of directly depending on a class (or in this case an interface) from another package, we defined our own interface. Then we created an adapter which enabled us to bridge the gap between our interface and the external interface.

Conclusion

There is one important conclusion to be mentioned here:

You don't need any dependencies at all.

In theory, your package would never need to depend on another package. Instead, it could define all kinds of interfaces itself and let adapter packages implement them. This would automatically make your package highly stable. It also makes it extremely reusable, because a user only needs to implement those interfaces specifically for the framework they use (if you haven't already done it for them!).

I'd like to mention the Aura packages here, which are designed in this way: no dependencies at all. It appears many people find this awkward and unnecessary, but given the approach described in this article, it should be more clear how such a thing is possible and even desirable.

Cover of Principles of PHP Package DesignIf you'd like to know more about this subject: I'm writing a book called Principles of PHP Package Design. You can buy it now and get all future updates (meaning: new chapters) for free.

Categories: PHP Symfony2

Tags: events package design coupling

Comments: Comments

Symfony2: Event subsystems

Posted on by Matthias Noback

Recently I realized that some of the problems I encountered in the past could have been easily solved by what I'm about to explain in this post.

The problem: an event listener introduces a circular reference

The problem is: having a complicated graph of service definitions and their dependencies, which causes a ServiceCircularReferenceException, saying 'Circular reference detected for service "...", path: "... -> ... -> ...".' Somewhere in the path of services that form the circle you then find the event_dispatcher service. For example: event_dispatcher -> your_event_listener -> some_service -> event_dispatcher.

Your event listener is of course a dependency of the event_dispatcher service, but one of the dependencies of your event listener needs the event_dispatcher itself! So you accidentally introduced a cycle in the dependency graph and now you need to dissolve it.

The wrong solution: injecting the service container

Assuming there is no way to redesign these services in a better way, you finally decide to make the event listener "container-aware". You will fetch some_service directly from the service container, which gets injected as a constructor argument:

use Symfony\Component\DependencyInjection\ContainerInterface;

class YourEventListener
{
    private $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function onSomeEvent()
    {
        $someService = $this->container->get('some_service');

        ...
    }
}

This will break the cycle since you depend on something else entirely: event_dispatcher -> your_event_listener -> service_container. However, it also introduces a nasty dependency on the service container, making your class coupled to the framework, the service definitions themselves, and making it less explicit about its actual dependencies. Besides, I really think that a dependency issue at configuration level should not affect a class this much.

The solution

There is an entirely different and much better way to break the cycle, which is to avoid any dependency on the main event_dispatcher in your own services at all. Let me explain this to you.

In the past, whenever I introduced a new event in my code, I used the application's main event_dispatcher service to dispatch the event and I also registered any listeners for the new event to the same event_dispatcher, using the kernel.event_listener service tag like this:

services:
    my_service:
        arguments:
            - @event_dispatcher

    my_event_listener:
        class: ...
        tags:
            - { name: kernel.event_listener, event: my_event, method: onMyEvent }

However, the application's main event dispatcher depends on so many services (via the event listeners registered to it), that sooner or later the dependency mess will start to show cycles. Still, to work with events, we need an event dispatcher (or event emitter, event manager, etc.). Since our code is already written with the Symfony EventDispatcherInterface in mind, we just need to work around the main event_dispatcher service.

Event subsystems

We can avoid the event_dispatcher altogether by introducing event subsystems.

In the old situation we depended on the event_dispatcher. In the new situation, each set of custom events comes with its own event subsystem. This subsystem consists of a dedicated event dispatcher, and an easy way to register event listeners for the custom events.

Since Symfony 2.3 it's really easy to set up your own event dispatcher and to conveniently register event listeners and subscribers in the same way as before.

Example: a subsystem for domain events

A highly relevant example would be an event subsystem for domain events. You don't want those events to be dispatched by the same event dispatcher that Symfony2 uses for its core events.

To be able to use the domain event subsystem, we first need to define a new event dispatcher service:

services:
    domain_event_dispatcher:
        class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
        arguments:
            - @service_container

Now we want to register event listeners for the domain event subsystem in the following, familiar way:

services:
    some_domain_event_listener
        class: ...
        tags:
            - {
                name: domain_event_listener,
                event: my_domain_event,
                method: onMyDomainEvent
            }

In order to make this work you should register a compiler pass in one of your bundles. The compiler pass itself is already provided by the framework:

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;

class YourBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(
            new RegisterListenersPass(
                'domain_event_dispatcher',
                'domain_event_listener',
                'domain_event_subscriber'
            ),
            PassConfig::TYPE_BEFORE_REMOVING
        );
    }
}

(As of Symfony 2.5 the RegisterListenersPass can be found in the Symfony\Component\EventDispatcher\DependencyInjection namespace.)

As you can see, we only need to provide the name of the event dispatcher service and the tag names we want to use, and this will all work. From now on you can inject the specific domain_event_dispatcher service and use the corresponding tags whenever you want to dispatch events within this event subsystem. This will cause much less of a dependency mess. But it will also make it clear by reading the service definitions, which kind of event subsystem the code is about.

By the way, you can read more on domain events here.

One last note

All of the above can only be applied to your own stand-alone application, library or bundle code. When you are creating event listeners for kernel events or for events dispatched from within third-party bundles, go ahead and use the main event_dispatcher service!

Categories: PHP Symfony2

Tags: events

Comments: Comments

Celebrating a year with "A Year With Symfony"

Posted on by Matthias Noback

Almost a year ago, on September 4th, in front of a live audience, I published my first book "A Year With Symfony". Since the first release, well over a 1000 people have read it. In February of this year, I added a new chapter to the book (about annotations).

A third edition?

Flipping through the pages and thinking about how I could best celebrate 1 year and 1000+ readers, I had the following ideas:

  1. I think it's time to rewrite the "Project structure" chapter.
  2. I think it would be nice to expand the book with yet another bonus chapter.

Feedback?

The book itself has proven to be quite popular, though I don't often get feedback about its contents. I would like to know what you really think of it, and I'd like some more (public) reviews.

Notebook

Prizes!

I want all the above things and I want to celebrate the book's anniversary. So this is what I've come up with.

You can win the following prizes:

  • Two of you get free printed copies of "A Year With Symfony" for your entire team (up to 5 copies).
  • Four of you get free digital copies of "A Year With Symfony" for your entire team (up to 10 copies).
  • Two of you get limited edition notebooks (see the image on the right) for your entire team (up to 5 copies).

You only need to fill in the form below in order to apply for the raffle. The winners will be announced near the end of September.

Categories: Book PHP Symfony2

Tags: A Year With Symfony prizes

Comments: Comments