Dependency injection with Doctrine - Part 3
On part 2, we managed to have dependencies injected in Doctrine repositories. This time, we would like to have dependencies injected in Doctrine entities. There are many ways to achieve this.
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 !
Half Repository, Half Factory
Our custom repository need to return Company
objects, not CompanyValue
objects.
We could simply do the following with any find*
method:
This is not a very good solution for many reasons, one of them being that we cannot handle results from QueryBuilder and Query instances. Repositories often provides instances of those two classes in case there is need to paginate the results.
This part may be a bit tricky. It really depends on how the entity repository is intended to be used.
As it provides way more flexibility and power without adding much complexity, I would recommend to
never use the find*
magic methods and use the QueryBuilder and Query objects instead. This
post from Benjamin Eberlei provides a really nice start
for using a variant of the specification pattern with
Doctrine. More so, the data hydrated using the magic methods do not use the same work flow as with
DQL. I am far from being a Doctrine-guru, but I don’t like it when there are many ways of doing the
same thing.
Back to our business, we need to hydrate a Company object with services and a CompanyValue. To
hydrate things, we need a Hydrator
.
Right Hydration for a Thirsty Business Model
If you look down in Doctrine’s internals, you should find some
hydrators. A hydrator transforms a SQL result set in something
else. Doctrine provides an ObjectHydrator
but it cannot directly let us inject dependencies in hydrated entities.
Hydrators are provided by the entity manager using the newHydrator($hydrationMode)
method where
the parameter is the chosen hydrator’s identifier. Unlike the repository factory which was made
customizable since Doctrine 2.4, if we want the entity manager to provide hydrators using dependency
injection, we will have to make a custom entity manager and overwrite the newHydrator
method.
The subscribeHydrator
method will be used by a compiler pass. We adopt exactly the same
strategy we used with the repository factory and the subscribeRepository
method. There is a
draft compiler pass implementation
for this purpose on the dedicated Github repository
The hydrators themselves are up to you. I have though of a possible implementation that uses a
dedicated factory for creating rich domain entities. I would recommend to extends the
ObjectHydrator
, overwrite the hydrateAll
and hydrateRow
methods and use their respective
parent::
implementations to get the persistent data. You can find an
example implementation of this hydrator in the
usual Github repository along with the associated factory interface
and an implementation
based on object cloning mechanisms.
Tools of the trade
Having the dependency injection wired with Doctrine’s repositories, entities (somewhat) and
hydrators should help building a rich domain model in many projects. Remember the Company
class
example we used in the first post of this trilogy ? We now
have the proof that entities returned by Doctrine queries can order pizza.
QED as the mathematicians say.