Being explicit about entity managers

Kennard • August 28, 2019

php laravel doctrine

Let's say we have two entity managers: master and tenant. When the dependency inversion principle is being applied, it might not be very obvious which entity manager is actually being used inside a class.

Imagine seeing this everywhere:

public function __construct(
    EntityManagerInterface $entityManager,  // which one is it??
    Foo $foo,
    Bar $bar,
    Baz $baz
)

You must assume it's either the default one, or you have to hunt the instantiation call down.

When utilising the auto-resolving feature of Laravel's service container, you also have to let the service container know how to resolve this dependency (because it's an interface). There are two ways to do this:

$this->app->bind(
    SomeService::class,
    function () {
        /** @var EntityManagerInterface $entityManager */
        $entityManager = $this->app->get(ManagerRegistry::class)->getManager('tenant');

        return new SomeService(
            $entityManager,
            $this->app->make(Foo::class),
            $this->app->make(Bar::class),
            $this->app->make(Baz::class),
        );
    }
);

// OR

$this->app->when(SomeService::class)
    ->needs(EntityManagerInterface::class)
    ->give(
        function () {
            return $this->app->get(ManagerRegistry::class)->getManager('tenant');
        }
    );

If you have a lot of classes requiring an entity manager, this will get old very quickly. The second option also won't work if you have a service requiring both entity managers.

The solution is to provide a "marker" or "tag" interface for every entity manager.

In the example below we will refactor a service requiring a tenant entity manager. The process is the same for every other entity manager.

Start by creating an interface which extends the base interface.

use Doctrine\ORM\EntityManagerInterface;

interface TenantEntityManager extends EntityManagerInterface
{
}

Then we need a decorator class. Doctrine provides an abstract base decorator which we extend. The base decorator implements all methods we need so this will be an empty class.

use Doctrine\ORM\Decorator\EntityManagerDecorator;

final class TenantEntityManagerDecorator extends EntityManagerDecorator implements TenantEntityManager
{
}

Configure the service container to bind the interface to the decorator.

$this->app->singleton(
    TenantEntityManager::class,
    function () {
        $manager = $this->app->get(ManagerRegistry::class)->getManager('tenant');

        return new TenantEntityManagerDecorator($manager);
    }
);

And refactor every constructor requiring a tenant entity manager.

public function __construct(
    TenantEntityManager $entityManager,  // This is very obvious
    Foo $foo,
    Bar $bar,
    Baz $baz
)

Since the service container knows what it should do when it sees TenantEntityManager type-hinted somewhere, we don't need binding statements for each and every class requiring a tenant entity manager anymore.