The Decorator Pattern, Composition and SRP in PHP

Too often when learning design patterns, I find it hard to understand what they actually mean and how they should be implemented. With the decorator pattern I frequently see abstract classes and inheritance mentioned, I personally feel that it's incorrect and prefer composition, here's how to accomplish that.

To demonstrate this, I'll use a very simple repository to retrieve a random image.

src/Domain/Repository/ImageRepository.php

<?php

namespace App\Domain\Repository;

use App\Domain\Model\Image;

interface ImageRepository
{
    public function getRandomImage(): Image;
}

Now a basic implementation of this repository to fetch images from the database might look like this:

src/Infrastructure/Repository/DbImageRepository.php

<?php

namespace App\Infrastructure\Repository;

use App\Domain\Model\Image;
use App\Domain\Repository\ImageRepository;
use \PDO;

final class DbImageRepository implements ImageRepository
{

    /**
     * @var PDO
     */
    protected $db;

    public function __construct(PDO $db)
    {
        $this->db = $db;
    }

    public function getRandomImage(): Image
    {
        $data = $this->db->query('SELECT * FROM images ORDER BY rand() LIMIT 1'); // get image data from DB however you like
        return new Image($data[0]); // construct image object from our data
    }
}

Great, now we have a concrete implementation of our interface that we can use in our application!

Now, what if we need to dispatch an event with the image that was retrieved from the database?

(Bad) Options

  1. We could inject the EventDispatcher into the DbImageRepository to make that dispatch the event, but this violates the single-responsibility-principle as we now have a class that is responsible for both database access and event dispatching.
  2. We could extend the repository, having: class EventDispatchingImageRepository extends DbImageRepository, but this goes against the idea that composition > inheritance and also wouldn't work if you declare things as final as we did in the example above.

With both of these options our repository that dispatches events can only work with a repository that pulls from the DB (what if we had a repository that interacted with an API? We'd have to either duplicate the work or we could also then easily end up in inheritance hell!)

This is one of the reasons that declaring classes final can be a good idea (for more information on this, see this fantastic blog post from Ocramius)

Rules to follow

Here's a few simple rules (followed by an example) of how to do what we need:

  1. Use an interface.
  2. Have your decorator implement the same interface of the thing it's decorating.
  3. Use composition and not inheritance. Have your decorator take the thing it's decorating as a constructor argument.

Putting it into practice

Now, here's what that looks like in code:

<?php

namespace App\Infrastructure\Repository;

use App\Domain\Event\EventDispatcher;
use App\Domain\Event\RandomImageAccessed;
use App\Domain\Model\Image;
use App\Domain\Repository\ImageRepository;

final class EventDispatchingImageRepository implements ImageRepository
{

    /**
     * @var ImageRepository
     */
    private $repository;

    /**
     * @var EventDispatcher
     */
    private $dispatcher;

    public function __construct(ImageRepository $repository, EventDispatcher $dispatcher)
    {
        $this->repository = $repository; // composition over inheritance!
        $this->dispatcher = $dispatcher;
    }

    public function getRandomImage(): Image
    {
        $image = $this->repository->getRandomImage(); // delegate getting the image to another repository

        $this->dispatcher->dispatch(new RandomImageAccessed($image));

        return $image;
    }
}

Now our EventDispatchingImageRepository has just 1 responsibility, dispatching events when an image is accessed. It delegates the responsibility of actually getting that image to whatever repository it's given.

In doing so, it also doesn't care how that image is retreived, it could be a database, flat file, API - doesn't matter.

Now where you're typehinting for an ImageRepository in your application (you are typehinting via interfaces, right?!), you can give it the DbImageRepository or the EventDispatchingImageRepository and whatever relies on the typehint won't mind as they both implement the interface.

Hopefully this clears up a few things with implementing decorators in your own codebase.