Reclaim your repositories from Doctrine

Decoupling the persistence layer

A lot of web applications are very data centric. They evolve as thin layers around a database. This leads to a very tight coupling between your application and the database, creating a lot of untestable or very hard to test code. This also makes the database present in every part of the application, robbing you of the option to switch databases without rewriting most of your code.

ORMs like Doctrine create an abstraction layer between your application and the database. This is a step forward, but the coupling between your application and doctrine still exists. A good way of decoupling your application from the database and doctrine is the REPOSITORY PATTERN.

The Repository Pattern

The only responsibility of a repository is to save and retrieve entities. Your application shouldn’t care where that data goes or where it comes from. This gives you the advantage of being able to switch databases with a very small cost. It makes your database choice a detail, a decision that doesn’t have to be taken upfront. At first you could implement an InMemory repository implementation, that just saves data in a php variable. That’s good enough for developing you business logic using TDD, because the tests will run very fast.

The repository is an interface. Your application will only know about the interface, without caring what the actual implementation is. This creates a boundary between your application and the persistence layer. It’s very important to test well at boundaries. You can use InMemory repositories when writing and running unit tests that should run fast. Also, you can write integration tests with a real repository implementation to make sure everything works with the external database.

You could use Doctrine repositories to do this. But this takes away control over how the entities are saved and retrieved. Your repositories should be specific to the domain of your application. For example, an UserRepository for a billing system can have methods like getAllOverdueUsers(), getAllSuspendedUsers(). With doctrine’s repositories it’s harder to program to an interface.

The way of thinking about this, is to use Doctrine as part of the persistence layer. An actual implementation of your UserRepository interface could be a DoctrineUserRepository. Now your application knows about the UserRepository interface, and nothing about Doctrine.

Here is an example on how this could work:

Repository Diagram

Repository Diagram

.

This is an example of an UserRepository interface:

interface UserRepostory
{
    public function save(User $user);

    public function find($userId);
}

An in-memory implementation could be this:

class InMemoryUserRepository
{

   protected $data;

   public function save(User $user)
   {
       $this->data[] = $user;
   }

   public function find($userId)
   {
       return $this->data[$userId];
   }

}

You might ask what is the purpose of such a repository. The advantage of this repository is that’s incredibly fast, all it does it saves data to a variable. The disadvantage is that it only persists the data while the script is executing, after that is lost. Can you see a situation where you would need such a repository ? Exactly, while running unit tests. This is enough for that. It has all the necessary advantages to be used while running unit tests. It is incredibly fast because it doesn’t talk with external systems, it doesn’t write anything to disk and it’s immediately available.

Now we can create any repository implementation we would like. As long as it implements this interface, it’s all we need. For example, the DoctrineUserRepository could look like this:

class DoctrineUserRepository
{
    protected $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function save(User $user)
    {
        $this->em->persist($user);
        $this->em->flush();
    }

    public function find($userId)
    {
        return $this->em->getRepository('Entity\User')->find($userId);
    }
}

Now we have two implementations. The InMemory and the Doctrine one. Both implement the UserRepository interface. A client code might look like this:

class UserManager
{
    protected $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function setUserAsActivated($userId)
    {
        $user = $this->userRepository->find($userId);
        $user->setActivated(true);
        $this->userRepository->save($user);
    }
}

As you can see, our client code doesn’t know about any concrete implementations of the user repository. We could use either the DoctrineUserRepository or the InMemoryUserRepository. Our client only knows about the UserRepository interface. We have very clearly created a boundry between our business objects and our persistence mechanism.

Our unit test for the user manager class could look like this:

class UserManagerTest extends \PHPUnit_Framework_Test_Case
{
    public function testItCanSetUserAsActivated()
    {
        $userRepository = new InMemoryUserRepository();
        $userManager = new UserManager($userRepository);

        $user = new User();
        $user->setActivated(false);
        $userRepository->save($user);

        $userManager->setUserAsActivated(0);
        $this->assertTrue($user->getActivated());
    }
}

This test won’t hit the database, but it will make sure that our method works. By using Doctrine repositories directly, this would have been much harder to do, and you would need to create a test database, where you should enter known users and interact with them.

If, at some time, you get alot of users and you need to query them very much and your database becomes slow, you could switch to redis by just creating a RedisUserRepository that implements the UserRepository interface, and just get users from Redis.

This technique allows you to separate your application from Doctrine, and also allows you to switch storage methods in a very short time, without having to change anything else.

 

Ovidiu Maghetiu

Passionate Full-Stack Developer with more than 10 years experience in building complex web apps.