Magnus Nordlander in Symfony 4 minute read

Doctrine Repositories and autowiring in Symfony 3.3

Symfony has had autowiring in some capacity since version 2.8, but until the fairly recently released 3.3 it was mostly there as a "use it if you want to, but we're not really recommending it" feature. With 3.3 however autowiring came back with a vengence, and for me personally (as some listeners to Sound of Symfony may know) I quickly went from skeptical to completely sold on the concept.

It just makes everything so easy! Well, almost everything.

I started experimenting with 3.3 back in the beta stages, and that's when autowiring won me over. Since then, I and many others have been working on updating third party bundles to make them easier to use with autowiring, and we're starting to see results. One of my favorite examples is Kevin Bond's recently accepted PR to SimpleBus/SymfonyBridge. It's insanely great! It means you can go from a configuration file with hundreds, if not thousands of lines of code, defining dozens of services (which you then need to keep updating whenever you add new command handlers and event listeners), to this:

services:
    App\Message\CommandHandler\:
        resource: ../../src/Message/CommandHandler
        tags: [command_handler]

    App\Message\EventSubscriber\:
        resource: ../../src/Message/EventSubscriber
        tags: [event_subscriber]

The problem with Doctrine repositories

Between Symfony 2.4 (when the ExpressionLanguage was introduced) and 3.2, I usually did not register my Doctrine repositories as a service. If I had a service that needed a Doctrine repository, I would usually inject it via the @=service('doctrine').getRepository('App:User') expression syntax. Of course, when 3.3 came, I immediately wanted to be able to inject my Doctrine repositories via autowiring, so I started on a PR to automatically register Doctrine repositories as services.

One of the things I learned as I worked on that PR was that it seems what I had been doing since way back, defining and/or injecting repositories into my services, was really a bad idea. See, if you inject a repository, and for some reason the entity manager closes (which e.g. happens if you get a database error), you're now stuck with a useless repository. You live and you learn.

My solution

So I've given this some thought, and I've come up with what I consider to be the best solution to this problem. As such, I want to share this solution with you in the hopes that either you can offer constructive criticism on why this way isn't as great as I think, or so that you can use it yourselves.

My solution is to ditch custom Doctrine repositories, and create your own repository classes that work. Instead of extending EntityRepository, you create your own repository like this:

<?php

class UserRepository
{
    private $managerRegistry;

    public function __construct(\Doctrine\Common\Persistence\ManagerRegistry $managerRegistry)
    {
        $this->managerRegistry = $managerRegistry;
    }

    public function find($id): ?User
    {
        return $this->getManager()->find(User::class, $id);
    }

    public function findOneUsingACustomQuery($parameter): ?User
    {
        return $this->getManager()->createQueryBuilder()
            ->from(User::class, 'u')
            ->where('u.something = :param')
            ->setParameter('param', $parameter)
            ->setMaxResults(1)
            ->getQuery()
            ->execute()
            ->getSingleResult();
    }

    protected function getManager(): \Doctrine\ORM\EntityManager
    {
        return $this->managerRegistry->getManagerForClass(User::class);
    }
}

Edit: Thanks to @vincentchalamon for making me aware that in an earlier version of this blog post I wasn't type hinting to an interface, but rather a class, in the constructor.

Of course, you can abstract some things here into a base class, but I hope this is enough to illustrate what I am talking about. Instead of extending EntityRepository, we inject ManagerRegistry (which conveniently handles closed entitiy managers for us, and which should be autowireable already), and then we make sure to always grab a potentially fresh manager from the registry. This new class should not be registered in the Doctrine entity mapping as the repositoryClass.

This is not an end-all solution; if you want to be able to handle database errors gracefully, you still need to think about what you're doing, but at least this doesn't put you in a worse position to do so.

Some pros and cons with this solution:

Pros

  • Repositories are easy to autowire into other services
  • Repositories are easily configured using autowiring
  • You can add more typehints
  • Doesn't break when database throws an error

Cons

  • Slightly more verbose
  • Some optional parts of Symfony (form types, user providers) still give preferential treatment to Doctrine's own repository.
  • Can be difficult to migrate an existing application to, if you're (directly or indirectly) relying on being able to get the repository through an entity manager

So what do you think? Does this make sense? Tweet at us (I'm @drrotmos, Fervo is @fervoab), blog about this or talk to us in the Symfony Slack!