diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst index 4c73ef6f741..ec7d4b9d4ea 100644 --- a/doctrine/multiple_entity_managers.rst +++ b/doctrine/multiple_entity_managers.rst @@ -9,6 +9,8 @@ application. This is necessary if you are using different databases or even vendors with entirely different sets of entities. In other words, one entity manager that connects to one database will handle some entities while another entity manager that connects to another database might handle the rest. +It is also possible to use multiple entity managers to manage a common set of +entities, each with their own database connection strings or separate cache configuration. .. note:: @@ -39,34 +41,33 @@ The following configuration code shows how you can configure two entity managers driver: 'pdo_mysql' server_version: '5.7' charset: utf8mb4 - customer: + human: # configure these for your database server - url: '%env(DATABASE_CUSTOMER_URL)%' + url: '%env(DATABASE_HUMAN_URL)%' driver: 'pdo_mysql' server_version: '5.7' charset: utf8mb4 - orm: default_entity_manager: default entity_managers: default: connection: default mappings: - Main: + Animal: is_bundle: false type: annotation - dir: '%kernel.project_dir%/src/Entity/Main' - prefix: 'App\Entity\Main' - alias: Main - customer: - connection: customer + dir: '%kernel.project_dir%/src/Entity/Animal' + prefix: 'App\Entity\Animal' + alias: Animal + human: + connection: human mappings: - Customer: + Human: is_bundle: false type: annotation - dir: '%kernel.project_dir%/src/Entity/Customer' - prefix: 'App\Entity\Customer' - alias: Customer + dir: '%kernel.project_dir%/src/Entity/Human' + prefix: 'App\Entity\Human' + alias: Human .. code-block:: xml @@ -91,8 +92,8 @@ The following configuration code shows how you can configure two entity managers /> - - + @@ -140,8 +141,8 @@ The following configuration code shows how you can configure two entity managers 'charset' => 'utf8mb4', ], // configure these for your database server - 'customer' => [ - 'url' => '%env(DATABASE_CUSTOMER_URL)%', + 'human' => [ + 'url' => '%env(DATABASE_HUMAN_URL)%', 'driver' => 'pdo_mysql', 'server_version' => '5.7', 'charset' => 'utf8mb4', @@ -155,24 +156,24 @@ The following configuration code shows how you can configure two entity managers 'default' => [ 'connection' => 'default', 'mappings' => [ - 'Main' => [ + 'Animal' => [ is_bundle => false, type => 'annotation', - dir => '%kernel.project_dir%/src/Entity/Main', - prefix => 'App\Entity\Main', - alias => 'Main', + dir => '%kernel.project_dir%/src/Entity/Animal', + prefix => 'App\Entity\Animal', + alias => 'Animal', ] ], ], - 'customer' => [ - 'connection' => 'customer', + 'human' => [ + 'connection' => 'human', 'mappings' => [ - 'Customer' => [ + 'Human' => [ is_bundle => false, type => 'annotation', - dir => '%kernel.project_dir%/src/Entity/Customer', - prefix => 'App\Entity\Customer', - alias => 'Customer', + dir => '%kernel.project_dir%/src/Entity/Human', + prefix => 'App\Entity\Human', + alias => 'Human', ] ], ], @@ -181,10 +182,10 @@ The following configuration code shows how you can configure two entity managers ]); In this case, you've defined two entity managers and called them ``default`` -and ``customer``. The ``default`` entity manager manages entities in the -``src/Entity/Main`` directory, while the ``customer`` entity manager manages -entities in ``src/Entity/Customer``. You've also defined two connections, one -for each entity manager. +and ``human``. The ``default`` entity manager manages entities in the +``src/Entity/Animal`` directory, while the ``human`` entity manager manages +entities in ``src/Entity/Human``. You've also defined two connections, one +for each entity manager, but you are free to define the same connection for both. .. caution:: @@ -212,8 +213,8 @@ When working with multiple connections to create your databases: # Play only with "default" connection $ php bin/console doctrine:database:create - # Play only with "customer" connection - $ php bin/console doctrine:database:create --connection=customer + # Play only with "human" connection + $ php bin/console doctrine:database:create --connection=human When working with multiple entity managers to generate migrations: @@ -223,9 +224,9 @@ When working with multiple entity managers to generate migrations: $ php bin/console doctrine:migrations:diff $ php bin/console doctrine:migrations:migrate - # Play only with "customer" mappings - $ php bin/console doctrine:migrations:diff --em=customer - $ php bin/console doctrine:migrations:migrate --em=customer + # Play only with "human" mappings + $ php bin/console doctrine:migrations:diff --em=human + $ php bin/console doctrine:migrations:migrate --em=human If you *do* omit the entity manager's name when asking for it, the default entity manager (i.e. ``default``) is returned:: @@ -244,42 +245,361 @@ the default entity manager (i.e. ``default``) is returned:: $entityManager = $this->getDoctrine()->getManager('default'); $entityManager = $this->get('doctrine.orm.default_entity_manager'); - // Both of these return the "customer" entity manager - $customerEntityManager = $this->getDoctrine()->getManager('customer'); - $customerEntityManager = $this->get('doctrine.orm.customer_entity_manager'); + // Both of these return the "human" entity manager + $humanEntityManager = $this->getDoctrine()->getManager('human'); + $humanEntityManager = $this->get('doctrine.orm.human_entity_manager'); } } You can now use Doctrine just as you did before - using the ``default`` entity -manager to persist and fetch entities that it manages and the ``customer`` +manager to persist and fetch entities that it manages and the ``human`` entity manager to persist and fetch its entities. -The same applies to repository calls:: +Multiple Entity Managers and repositories +========================================= +Your entities usually have a custom repository associated with them. These repositories +are usually generated by Symfony commands such as the :doc:`make:entity ` command that will +create repositories classes supporting autowiring, such as:: - use AcmeStoreBundle\Entity\Customer; - use AcmeStoreBundle\Entity\Product; - // ... + // src/Repository/HumanRepository.php + namespace App\Repository; - class UserController extends AbstractController + use App\Entity\Human\Customer; + use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; + use Doctrine\Common\Persistence\ManagerRegistry; + + class CustomerRepository extends ServiceEntityRepository { - public function index() + public function __construct(ManagerRegistry $registry) { - // Retrieves a repository managed by the "default" em - $products = $this->getDoctrine() - ->getRepository(Product::class) - ->findAll() - ; - - // Explicit way to deal with the "default" em - $products = $this->getDoctrine() - ->getRepository(Product::class, 'default') - ->findAll() - ; - - // Retrieves a repository managed by the "customer" em - $customers = $this->getDoctrine() - ->getRepository(Customer::class, 'customer') - ->findAll() - ; + parent::__construct($registry, Customer::class); } } + +As long as you are explicit about what configuration you want, repositories calls can be really flexible:: + + // src/Controller/DefaultController.php + use App\Entity\Human\Customer; + use App\Repository\CustomerRepository; + + // ... + public function index(EntityManagerInterface $em, CustomerRepository $customerRepository) + { + // Retrieves a repository managed by the "human" em using autowiring + $customers = $customerRepository->findAll(); + + // Retrieves a repository managed by the "human" em + // because only the "human" entity manager is set to manage the Customer entity + $customers = $this->getDoctrine() + ->getRepository(Customer::class) + ->findAll(); + + // Retrieves a repository managed by the "human" em, in an explicit way + $customers = $this->getDoctrine() + ->getRepository(Customer::class, 'human') + ->findAll(); + + // Same as the previous call + $customers = $this->getDoctrine() + ->getManager('human') + ->getRepository(Customer::class) + ->findAll(); + + // Throws a "MappingException": the "default" em does not manage the Customer entity! + $customers = $this->getDoctrine() + ->getRepository(Customer::class, 'default') + ->findAll(); + + // Throws a "MappingException": the autowired $em instance is the "default" em + // and this entity manager does not manage the Customer Entity! + $customers = $em + // Note: this method of a concrete $em object cannot take a second argument! + ->getRepository(Customer::class) + ->findAll(); + } + +Entity Managers managing common Entities +========================================= + +Some specific use cases may lead to define entities that are managed by more than one entity manager. While this is a supported use case, there are some important limitations to consider. + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/doctrine.yaml + doctrine: + dbal: + default_connection: default + connections: + default: + # configure these for your database server + url: '%env(DATABASE_URL)%' + driver: 'pdo_mysql' + server_version: '5.7' + charset: utf8mb4 + human: + # configure these for your database server + url: '%env(DATABASE_HUMAN_URL)%' + driver: 'pdo_mysql' + server_version: '5.7' + charset: utf8mb4 + orm: + default_entity_manager: default + entity_managers: + default: + connection: default + mappings: + Animal: + is_bundle: false + type: annotation + dir: '%kernel.project_dir%/src/Entity/Animal' + prefix: 'App\Entity\Animal' + alias: Animal + human: + connection: human + mappings: + Human: + is_bundle: false + type: annotation + dir: '%kernel.project_dir%/src/Entity/Human' + prefix: 'App\Entity\Human' + alias: Human + creature: + connection: default + mappings: + Creature: + is_bundle: false + type: annotation + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: Creature + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/doctrine.php + $container->loadFromExtension('doctrine', [ + 'dbal' => [ + 'default_connection' => 'default', + 'connections' => [ + // configure these for your database server + 'default' => [ + 'url' => '%env(DATABASE_URL)%', + 'driver' => 'pdo_mysql', + 'server_version' => '5.7', + 'charset' => 'utf8mb4', + ], + // configure these for your database server + 'human' => [ + 'url' => '%env(DATABASE_HUMAN_URL)%', + 'driver' => 'pdo_mysql', + 'server_version' => '5.7', + 'charset' => 'utf8mb4', + ], + ], + ], + + 'orm' => [ + 'default_entity_manager' => 'default', + 'entity_managers' => [ + 'default' => [ + 'connection' => 'default', + 'mappings' => [ + 'Animal' => [ + is_bundle => false, + type => 'annotation', + dir => '%kernel.project_dir%/src/Entity/Animal', + prefix => 'App\Entity\Animal', + alias => 'Animal', + ] + ], + ], + 'human' => [ + 'connection' => 'human', + 'mappings' => [ + 'Human' => [ + is_bundle => false, + type => 'annotation', + dir => '%kernel.project_dir%/src/Entity/Human', + prefix => 'App\Entity\Human', + alias => 'Human', + ] + ], + ], + 'creature' => [ + 'connection' => 'default', + 'mappings' => [ + 'Human' => [ + is_bundle => false, + type => 'annotation', + dir => '%kernel.project_dir%/src/Entity', + prefix => 'App\Entity', + alias => 'Creature', + ] + ], + ], + ], + ], + ]); + +In this case, you've defined a third entity manager called ``creature`` that manages +**all our entities**. The ``creature`` entity manager can reuse an existing connection or +defines a new one, here the ``default`` connection is used. + +This specific use case however suffers from a limitation due to how our entity repositories +are defined and the fact that **service repositories only use by default the first defined entity manager for a given entity**:: + + // src/Controller/DefaultController.php + use App\Entity\Human\Customer; + use App\Repository\CustomerRepository; + + // ... + public function index(CustomerRepository $customerRepository) + { + // Retrieves a repository managed by the "human" em + // because it's the first entity manager configured to manage the Customer Entity + $customers1 = $customerRepository->findAll(); + + // Retrieves a repository managed by the "human" em + // again because it's the first entity manager configured to manage the Customer Entity + $customers2 = $this->getDoctrine() + ->getRepository(Customer::class) + ->findAll(); + + // Retrieves a repository managed by the "human" em + $customers3 = $this->getDoctrine() + ->getRepository(Customer::class, 'human') + ->findAll(); + + // Same as the previous call + $customers3 = $this->getDoctrine() + ->getManager('human') + ->getRepository(Customer::class) + ->findAll(); + + // Retrieves a repository managed by the "human" em, not the "creature" em! + $customers4 = $this->getDoctrine() + ->getRepository(Customer::class, 'creature') + ->findAll(); + } + +The ``$customers4`` array here contains the very same entities instances than the ``$customers{1,2,3}`` array, while they shoud be different, **separate entities instances**, from **two different** entities managers. The "service-styled definition" of the Customer repository does not allow the selection of a specific entity manager to use for the ``Customer`` entity, and, by default, **the first defined one is always used**. + +One of the possible workaround to the limitation is to remove the autowiring support of multi-managed entities repositories to ensure no implicit default choice is made internally: :: + + // src/Repository/CustomerRepository.php + namespace App\Repository; + + use App\Entity\Human\Customer; + use Doctrine\ORM\EntityRepository; + + class CustomerRepository extends EntityRepository + { + // + } + +The legacy definition of an entity repository, but making it non-serviceable, leads to the expected behavior:: + + // src/Controller/DefaultController.php + use App\Entity\Human\Customer; + + // ... + public function index(/* The Customer repository is not serviceable anymore */) + { + // Retrieves a repository managed by the "human" em (first defined em still wins here) + // You shoud be explicit! + $customers1 = $this->getDoctrine() + ->getRepository(Customer::class) + ->findAll(); + + // Retrieves a repository managed by the "human" em + $customers2 = $this->getDoctrine() + ->getRepository(Customer::class, 'human') + ->findAll(); + + // Same as: + $customers2 = $this->getDoctrine() + ->getManager('human') + ->getRepository(Customer::class) + ->findAll(); + + // Retrieves a repository managed by the "creature" em + $customers4 = $this->getDoctrine() + ->getRepository(Customer::class, 'creature') + ->findAll(); + + // Same as: + $customers4 = $this->getDoctrine() + ->getRepository(Customer::class, 'creature') + ->findAll(); + } + +Here, ``$customers{1, 2}`` contain the same entities instances while ``$customers4`` now correctly contains its own entities instances.