Dependency injection with Doctrine - Part 2
In the quest for a rich domain model, we are struggling to have entities injected with services. In the previous post, we defined what we thought would be nice to have and ran into some troubles with the persistence layer. This time, we’ll address a specific issue : injecting dependencies into our entity repositories.
I keep code samples available on github. Don’t expect that code to work out of the box yet. Do not copy-waste it like an idiot !
Injecting Services in a Doctrine Repository
Typically, entities are retrieved from a repository and repositories are provided by the entity manager. In our case, the repository needs to be injected with dependencies. This is not a problem if you define your repositories as services and use them as regular services provided by the dependency injection container (using setter injection or a decorator, as described here by Jurian Sluiman).
On the other hand, we may want to retrieve repositories using the entity manager for backward compatibility reasons. The solution provided above by Sluiman is not well suited for a this. We’ll try to address this.
Let’s say we want to fetch our newly rich domain object in the usual fashion shown in most documentations.
That getRepository
method is provided by the entity manager. If the Company::__construct
method
does not have the same signature as it’s parent class,
that call will throw an exception. What if we do need to have other parameters in the constructor ?
The solution will be different whether the version of Doctrine ORM we use is older than 2.4 or not.
If it is, I would suggest to update. If it is not possible, you will have to use a custom
EntityManager
and reimplement the getRepository
method so it uses some custom factory to provide
repositories. Else, Doctrine added that feature for us as stated in the
2.4 release blog post.
Also, DoctrineBundle added that
configuration option.
Let’s have a closer look at that repository factory. We need to implement the
RepositoryFactory
interface which defines only the
getRepository
method.
You can then define this factory as a regular service (let’s suppose it is called repository_factory
)
and tell DoctrineBundle to use this one instead of the default one. Don’t forget to feed an
instance of DefaultRepositoryFactory
as a mandatory dependency to our custom factory so it can
handle requests for regular repositories. Next, we define a compiler pass that calls
subscribeRepository
on our DIAwareRepositoryFactory
with any custom repository we defined as a
service and tagged specifically for this purpose.
and define your custom repositories injected with services like this:
The above compiler pass looks for services tagged with the custom_repository
tag and schedule
a subscription to the DIAwareRepositoryFactory
. All those services will then be available from
the factory.
For more details about service definition, using tags and about compiler passes, read some of the Symfony Book and the Symfony Cookbook.
With this setup the custom repository should be accessible via
$this->getDoctrine()->getRepository('Acme\Model\Entity\Company')
as stated in the controller
listing.
We can now have object repositories using arbitrary mandatory dependencies. This is a good start !
It is not obvious how we’ll use it to retrieve a Company
instance yet. We’ll address this in a
future post.