Or: how to make your persistence-related code more reusable
You probably know that you should trim your controllers as much as possible. This usually means extracting some classes from the semi-procedural code inside your controller class. You create services for the extracted classes and inject the necessary dependencies into them. You can no longer use the convenient $this->getDoctrine()->getManager()
. Instead you inject the doctrine.orm.default_entity_manager
service into every service that needs the EntityManager
to persist data in your relational database:
namespace AcmeCustomerBundle\Entity;
use Doctrine\ORM\EntityManager;
class CustomerManager
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function createCustomer(Customer $customer)
{
$this->entityManager->persist($customer);
$this->entityManager->flush();
}
}
When you don't intend to reuse this code in other projects, or share it as open source software: no problem! But when you do, consider the following. The DoctrineBundle offers a way to set up multiple entity managers and database connections. This way you can persist different types of entities in your project in different database. Though I guess most projects use just one entity manager and one database connection, when you want your code to be truly reusable, you need to consider the possibility that it will be used in a project with multiple entity managers.
This is what the Doctrine configuration of a Symfony2 project with multiple entity managers looks like (copied from the official documentation):
doctrine:
dbal:
default_connection: default
connections:
default:
driver: "%database_driver%"
dbname: "%database_name%"
...
customer:
driver: "%database_driver2%"
dbname: "%database_name2%"
...
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AcmeDemoBundle: ~
AcmeStoreBundle: ~
customer:
connection: customer
mappings:
AcmeCustomerBundle: ~
This particular configuration defines two different database connections used by two different entity managers which manage entities from different bundles.
Now let's say you release the CustomerManager
class from the example above as part of the open source bundle AcmeCustomerBundle
. You add the following service definition in Acme/CustomerBundle/Resources/config/services.yml
:
services:
acme_customer.customer_manager:
class: Acme\CustomerBundle\Doctrine\CustomerManager
arguments:
- @doctrine.orm.default_entity_manager
However, when this bundle is installed in a project with the Doctrine configuration above, the entities in the AcmeCustomerBundle
will be managed by the customer
entity manager (which is available as the doctrine.orm.customer_entity_manager
service), not by the default
entity manager. This means that the entity manager that is being injected in the CustomerManager
class is the wrong one: it is not capable at all to manage the Customer
entity from the AcmeCustomerBundle
.
Since you can not know in advance which entity manager the user of your bundle wants to use, you need to remove the hard dependency on the default
entity manager. Instead you should depend on the ManagerRegistry, which knows all about the available entity managers in a project and is able to find the right entity manager for a given entity class.
In fact, when you use $this->getDoctrine()
in your controller, you already receive an instance of the ManagerRegistry
interface. When you call its method getManager()
, it returns the default
entity manager.
The ManagerRegistry
also has a method getManagerForClass($class)
which tries to find an entity manager which is able to manage entities of the given class. When we ask it to find the entity manager for the Acme\CustomerBundle\Entity\Customer
class, it returns the customer
entity manager. When one of the other entity classes is provided, it returns the default
entity manager.
In practice this means that classes that need an entity manager, should not get an EntityManager
instance injected, but instead should receive an ManagerRegistry
instance:
use Doctrine\Common\Persistence\ManagerRegistry;
class CustomerManager
{
private $managerRegistry;
public function __construct(ManagerRegistry $managerRegistry)
{
$this->managerRegistry = $managerRegistry;
}
public function createCustomer(Customer $customer)
{
// retrieve the right entity manager for this type of entity
$entityManager = $managerRegistry->getManagerForClass(get_class($customer));
$entityManager->persist($customer);
$entityManager->flush();
}
}
Finally the service definition of CustomerManager
needs to be modified: the constructor argument should not be doctrine.orm.default_entity_manager
anymore, instead it should be doctrine
, which is the ManagerRegistry
containing all the entity managers known inside the current application.
services:
acme_customer.customer_manager:
class: Acme\CustomerBundle\Doctrine\CustomerManager
arguments:
- @doctrine
We have accomplished maximum reusability since the CustomerManager
can now be used in projects with multiple entity managers.
Bonus: support for different object managers
As you may have noticed, the ManagerRegistry
interface was imported from the Doctrine Common library. Its interface says that the getManager*()
methods return an instance of ObjectManager (which is also part of the Doctrine Common library). All the object managers from Doctrine persistence libraries like Doctrine ORM, Doctrine MongoDB ODM, Doctrine CouchDB ODM, etc. implement this ObjectManager
interface, which itself offers methods like persist()
and flush()
. This means that once you make your classes depend on the ManagerRegistry
interface instead of the EntityManager
class, you have made them reusable in many other contexts than just the context of a project that uses Doctrine ORM for persistence.
Take a look at the FOSUserBundle and see how they solved the problem of different persistence libraries. Also take a look at the interfaces and base classes in the Doctrine Common Persistence sub-library. This will make you aware of all the "cheap" possibilities of making your persistence-related code even more reusable.
For some reason, this doesn't work for me. The method simply returns the Entity Manager that comes first in the Doctrine configuration. I've posted a question on Stack Overflow with the details, but I haven't got an answer yet. http://stackoverflow.com/qu...What type of mapping did you use for your entities? Annotations? XML?UPDATE: solved it. When using annotations, you need to set the
prefix
parameter in the Doctrine configuration for the Annotation Driver to limit its scope to the desired namespace.I am in a situation where I will have entities of the same class, but in different managers.
Am I still able to make use of this?
It does not sound like it since it seems to be searching for the EM by the class_name.
If not, any suggestions on how I can do this, and if it's possible to get the EM from the an object that is not yet persisted.
Thanks.
That's why I prefer to go a slighly different routine that the author of the article suggests: to use actually the ObjectManager interface (also mentioned in the article). Then I inject any kind of persistance manager I want: PHPCR-ODM (NoSQL Document Manager), ORM (Doctrine Entity Manager), or anything else which implements the ObjectManager. Then, it's sufficient to just relpace the Object Manager in the services config (yml, xml...) file. The only thing is that you need to remember to use only the ObjectManager interface's methods, and not Entity Manager's one.
Not really a comment, great article, as always. You added a note in blue kust under the post title, shouldn't it say "Please also read my next article about injecting repositories."?
I inject Doctrine\Common\Persistence\ManagerRegistry.
Work fine, thank you!
But how can I now use transaction mechanism?
[Answer edited]
Just a question, Matthias.
Lets assume I know the type of entities I want to manage in a service, and lets assume I can inject the full repository using ExpressionLanguage, which means I can find correct manager using the entity namespace stored in a parameter.
If a service is designed to persist more than one different entity types, what strategy should we follow? Inject one manager per each Entity?
This scenario tells us that a service should only manage one entity type?
I'd say one service per entity type, even though it's the same service. It may become a different service at some point. Also read my follow-up post on this subject, in most cases it's probably better to inject a repository.
Yes, I'm used to injecting just the repository using ExpressionLanguage when I do not need entityManager for anything else. But is just an specific scenario.
If the full entityManager is needed, IMO a very good solution is injecting it using also the ExpressionLanguage, so is not bussiness logic but service definition ( And better for unit testing )
I like your posts, very very educational :) Thanks!
Interesting Matthias! Thanks
Thanks great article.
In the most case I prefer injecting EntityManager instead of ManagerRegistry
why not inject EntityManager by using the expression language???
tl;dr: the
ObjectManager
and theManagerRegistry
are locators. Don't inject them: instead, inject what you would fetch from them!Injecting a registry (which is just a particular locator) is pretty much always a bad idea.
This approach is better than injecting the
ObjectManager
, as it allows for usage of different storage layers in a single service, but it is still pretty bad.Instead, rely solely on constructor injection of the
ObjectRepository
(you should have your own interface for your repositories) for the specific entity type that you are dealing with. This requires more complex injection, but is a fairly simple enhancement that eases development on the runtime and makes dependencies much more readable and explicit.What happens if you need exactly the
EntityManager
and not some specific repository? Should I still not inject that?If you need the
EntityManager
explicitly, then inject that one, though I'd say that it's a very rare case. Reconsider why you are doing that.BTW one of the reasons why we created the registry was to deal with cases when a flush fails. After a failed flush, the entity/document is no longer useable since it is now in an undefined state, so it is closed to prevent harm. However this can be problematic for code following. So the registry makes it possible to get a fresh copy.
But yes it is a locator, especially if you follow a quite common pattern of injecting the name of a manager as well, ie. not just always using the default manager. I am honestly not yet sure how to best deal with all of this yet.
oh in general the fact that the UOW leaks across services and subrequests has lots of iffy side effects .. then again doing away with this fact is maybe not really feasible in terms of performance (the extreme would be creating new connections all over the place).
Thanks for your comments Lukas, I've written some more about this in the follow-up post. I'm curious what would happen with this when a Symfony application would be used as a continuously running server, like some people have proposed.
This is kinda the problem I had last week, a cilex application that watches a beanstalk tube and processes the jobs.
I had the problem (although manufactured myself in order to try and make the code as robust as possible) where a SQL error during a transaction caused the entitymanager in the worker class to close.
Symfony2 has access to the resetManager method in the doctrine service that's in the container, so at least if you get back to somewhere you can access the container you can reset the Entity Manger and do more work with it.
This isn't available in cilex and I couldn't see any easy way to coax a new version of the entitymanager out of the provider that sets up doctrine as it just creates the entitymanagers.
In the end I managed, in a hack I'm not particularly proud of, to clone the em before the worker starts it's transaction and on sql error it uses the clone to find another fresh copy of the entity it needs to clean up and flush it. But then without a way to reset the main entity manager I was left with just exiting the application and letting supervisor restart it. A very unsatisfactory solution.
Having read your article I'm now wondering about forking the doctrine orm provider for cilex to provide a concrete RegistryManager in the container so I can reset the entitymanager and not have to exit.
That should be just very easy, I've almost done this once. The Doctrine common library contains already an abstract implementation of a ManagerRegistry if I remember correctly.
Indeed it does, but I was hoping for a solution that didn't require rewriting the doctrine orm provider for cilex/silex/pimple (http://goo.gl/U0KjYE). Whilst it would probably benefit from having an implementation of ManagerRegistry I don't really have the time to do it myself right now. In fact making it work with the Pimple DIC looked like it could be pretty complicated when you want to reset an entity manager.
That's right. Also I would inject the repository with save method, which uses entity manager to persist and flush entity (_em). In such situation you abstract data access layer in a way, which is easily to exchange for other implementation (mongo, restfull, etc.)
But isn't that a violation of the SRP, according to the Domain-Driven Design definition of a repository? I mean, should a repository be saving stuff?
Good point. I think it's better to add DomainSession object which will be wrapper around em and add save() method into it. And use it separately.
That's a good idea, thanks. I will write some more about this tomorrow.
I don't get it: what am I supposed to fetch when I call $this->objectManager->persist($entity)?
Marco is talking about fetching a repository from the entity manager.
It's OK for repos. But what about persisting and flushing? I still need ObjectManager for that
Also see my next post: you can easily add a save() method to the repository which does exactly that (persist, flush). From now on this would be my preferred way at least.
But isn't that a violation of the SRP, according to the Domain-Driven Design definition of a repository? I mean, should a repository be saving?
But isn't that a violation of the SRP, according to the Domain-Driven Design definition of a repository? I mean, should a repository be saving things?
Agreed, save() is not such a good name in this case. Traditionally it's called add(), which better fits the domain terminology.
Thanks Marco. I completely agree. Most situations don't require the manager registry, nor the object manager itself. Those situations that do should use the strategy described in my post. I will definitely write some more about the other use cases that you describe in another post.