Inject the ManagerRegistry instead of the EntityManager
Matthias Noback
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.