Symfony 2.8 : Doctrine getManagerForClass() not re

2019-05-21 10:11发布

问题:

tl;dr How does the getManagerForClass() method find out which entity manager is the right one for a specific class?

I've made a generic controller that should be able to handle basic actions for different entities. I also have connections to two different databases, so I'm using two entity managers.

In my controller, I'm trying to use Doctrine's getManagerForClass() method to find which manager to use for each class, as explained on this blog and this SO answer.

But the method does not seem to differentiate my two entity managers and simply returns the first one in the configuration.

My controller action starts like this:

public function indexAction($namespace, $entityName)
{
    $classFullName = "AppBundle:$namespace\\$entityName";
    $em = $this->getDoctrine()->getManagerForClass($classFullName);

This is my Doctrine configuration:

dbal:
    default_connection: postgres
    connections:
        postgres:
            driver:   pdo_pgsql
            host:     "%database_host%"
            port:     "%database_port%"
            dbname:   "%database_name%"
            user:     "%database_user%"
            password: "%database_password%"
            charset:  UTF8

        oracle:
            driver:   oci8
            host:     "%oracle_host%"
            port:     "%oracle_port%"
            dbname:   "%oracle_name%"
            user:     "%oracle_user%"
            password: "%oracle_password%"
            charset:  UTF8

orm:
    auto_generate_proxy_classes: true
    entity_managers:
        postgres:
            connection: postgres
            mappings:
                AppBundle:
                   type:      annotation
                   dir:       Entity\Postgres
        oracle:
            connection: oracle
            mappings:
                AppBundle:
                  type:       annotation
                  dir:        Entity\Oracle

And my folder structure is as follows:

AppBundle
    |___Controller
    |      |___EntityController.php
    |
    |___Entity
           |___Postgres
           |     |___SomePostgresBasedEntity.php
           |
           |___Oracle
                 |___SomeOracleBasedEntity.php

Now I don't know exactly how the method works, and how it it supposed to know about the mapping if not through the configuration. But if I call it this way, for example:

 $em = $this->getDoctrine()->getManagerForClass("AppBundle:Oracle\\SomeOracleBasedEntity");

...I get the entity manager for Postgres.

But if I simply switch the entity manager configuration, putting the one for oracle first, the previous call works, but the following doesn't:

 $em = $this->getDoctrine()->getManagerForClass("AppBundle:Postgres\\SomePostgresBasedEntity");

Update 1

getManagerForClass() cycles through every manager and for each one, checks if the class is "non-transient":

    foreach ($this->managers as $id) {
        $manager = $this->getService($id);

        if (!$manager->getMetadataFactory()->isTransient($class)) {
            return $manager;
        }
    }

This goes all the way down to AnnotationDriver->isTransient(). Here the doc says the following:

A class is non-transient if it is annotated with an annotation from the AnnotationDriver::entityAnnotationClasses.

@Entity seems to be one of those annotations that makes a class non-transient. But then, how could any of my entities be transient at all? How could the driver distinguish an entity that belongs to a specific manager based solely on its annotations? I must have missed something in the higher level classes.

Update 2

The method works when using yml mappings.

I kind of expected this behaviour. The difference comes from the implementations of the isTransient() method in the different drivers. The FileDriver implementation of isTransient returns true if the metadata file exists in the dir: directory of the mapping configuration.

I would have expected the AnnotationDriver to search for annotations only in the entities contained in the specified dir: directory, but it seems to ignore that parameter. Or should I use another one?

回答1:

At long last, I solved it. The solution was using the prefix parameter.

entity_managers:
    postgres:
        connection: postgres
        mappings:
            AppBundle:
                type: annotation
                dir: Entity\Postgres
                prefix: AppBundle\Entity\Postgres
                alias: Postgres
    oracle:
        connection: oracle
        mappings:
            AppBundle:
                type: annotation
                dir: Entity\Oracle
                prefix: AppBundle\Entity\Oracle
                alias: Oracle

Explanation

The prefix parameter gets passed to the corresponding Entity Manager service, and is added to the entityNamespaces property, which otherwise defaults to AppBundle/Entity. The Annotation Driver will then check for annotations in that specific namespace, whereas the File Driver checks for existing mapping files in the directory specified through the dir parameter. (The alias parameter is not mandatory.)

At least, that's how I understand it.