Service Decoration with Symfony 2/3

One of the least used features on Symfony is the "Service Decorator" feature. It makes the process of creating a "Decorator" (Design Patterns) a way simpler task.

The idea of the Decorator pattern is to "add behavior to an existing object in runtime".

Let's assume we have a repository called "UserRepository":

<?php

namespace App\UserBundle\Repository;

use Doctrine\ORM\EntityManagerInterface;  
use App\UserBundle\Entity\User;

class UserRepository implements UserRepositoryInterface  
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * @param EntityManagerInterface $entityManager
     */
    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * @param User $user
     */
    public function addUser(User $user)
    {
        $this->entityManager->persist($user);
        $this->entityManager->flush();
    }
}

Now let's suppose we need an event to be triggered when a new user is added and we don't want to couple the repository and the event dispatcher together.

Then we can use the Decorator pattern and composition to dispatch the event when needed, as you can see in the "DecoratedUserRepository":

<?php

namespace App\UserBundle\Repository;

use App\ApplicationBundle\EventDispatcher\EventDispatcherInterface;  
use App\UserBundle\Entity\User;  
use App\UserBundle\Event\UserCreatedEvent;  
use App\UserBundle\Repository\UserRepository;

class DecoratedUserRepository implements UserRepositoryInterface  
{
    /**
     * @var UserRepository
     */
    private $userRepository;

    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * @param UserRepository $userRepository
     * @param EventDispatcherInterface $eventDispatcher
     */
    public function __construct(
        UserRepository $userRepository,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->userRepository = $userRepository;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * @param User $user
     */
    public function addUser(User $user)
    {
        $this->userRepository->addUser($user);
        $this->eventDispatcher->dispatch(
            'app.user_created',
            new UserCreatedEvent($user)
        );
    }
}

Notice that the UserRepository class does not know about the DecoratedUserRepository class existence, and this is very important! Now it is already possible to use it wherever we want, but there is still the need of updating every reference to the old repository class to the new one, which makes the maintenance process much harder and complex...

And this complexity is what the Symfony's Service Decoration feature is meant to tackle!

To configure it is very simple. See the services.yml file example below:

services:  
    app.user_repository:
        class: App\UserBundle\Repository\UserRepository
        arguments:
            - '@doctrine.orm.entity_manager'

    app.decorated_user_repository:
        class: App\UserBundle\Repository\DecoratedUserRepository
        decorates: app.user_repository
        public: false
        arguments: 
            - '@app.user_repository.inner'
            - '@app.event_dispatcher'

The attribute "decorates" makes the original service definition to be overwritten by the new service. This means that when the service "app.user_repository" is requested, the service "app.decorated_user_repository" will be the one being used.

I hope this was as helpful for you as it was for me! For detailed information about the usage of this feature, take a look at the documentation.

See you!

Marcelo Santos

PHP Developer, Symfony lover, Geek, Husband and Dad!

Berlin, Germany http://marcelsud.com