Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolve Target Entity with multiple entity managers

Is it possible to resolve a target entity over multiple entity managers?

I have a class person (in a re-usable bundle):

/**
 *
 * @ORM\Entity
 * @ORM\Table(name="my_vendor_person")
 */
class Person
{ 
    /**
     * Unique Id
     *
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * First Name
     *
     * @var string $name
     *
     * @ORM\Column(name="first_name", type="string", length=32)
     */
    protected $firstName;
    // etc...

And a class user (in my main application):

/**
 * @ORM\Entity
 *
 * @ORM\Table(name="my_financial_user")
 *
 */
class User extends BaseUser
{

    /**
     * @ORM\OneToOne(targetEntity="My\FinancialBundle\Model\PersonInterface")
     * @var PersonInterface
     */
    protected $person;

Basically I want to couple the user to a person from the re-usable bundle.

I had set resolve target entity option in doctrine's configuration which I thought it allows me to do this:

doctrine:
  orm:
      auto_generate_proxy_classes: "%kernel.debug%"
      default_entity_manager: default
      resolve_target_entities:
          My\FinanceBundle\Model\PersonInterface: My\VendorBundle\Entity\Person
      entity_managers:
          default:
              naming_strategy: doctrine.orm.naming_strategy.underscore
              connection: default
              mappings:
                  MyFinanceBundle: ~
          second:
              naming_strategy: doctrine.orm.naming_strategy.underscore
              auto_mapping: false
              connection: second
              mappings:
                  MyVendorBundle: ~
                  MyVendorUserBundle: ~

Also, the user class in the main bundle, extends a base user in the vendor bundle. The user class of course is maintained, in the main-applications db.

With this configuration it gives me an error.

[Doctrine\Common\Persistence\Mapping\MappingException]                                                                                       
The class 'My\VendorBundle\Entity\Person' was not found in the chain configured namespaces My\FinanceBundle\Entity, FOS\UserBundle\Model 

Does anyone know how to solve this?

like image 704
apfz Avatar asked Mar 10 '16 14:03

apfz


1 Answers

As mentioned in the comment the entity managers don't talk to each other so you have to work around it. One way is to use a doctrine listener and insert a callback that can be invoked by the getPerson method.

AppBundle\Doctrine\InsertPersonListener

use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use My\FinancialBundle\Entity\Person;
use My\VendorBundle\Entity\User;
use Symfony\Bridge\Doctrine\RegistryInterface;

class InsertPersonListsner
{
    /**
     * @var RegistryInterface
     */
    private $registry;

    /**
     * Insert the registry into the listener rather than the entity manager
     * to avoid a cyclical dependency issue
     *
     * @param RegistryInterface $registry
     */
    public function __construct(RegistryInterface $registry)
    {
        $this->registry = $registry;
    }

    /**
     * On postLoad insert person callback
     *
     * @var LifecycleEventArgs $args
     */
    public function postLoad(LifecycleEventArgs $args)
    {
        $user = $args->getObject();

        // If not a user entity ignore
        if (!$user instanceof User) {
            return;
        }

        $reflectionClass = new \ReflectionClass(User::class);

        $property = $reflectionClass->getProperty('personId');
        $property->setAccessible(true);

        // if personId is not set ignore
        if (null === $personId = $property->getValue($user)) {
            return;
        }

        // get the repository for your person class
        // - changed to your version from the comments
        $repository = $this->registry
            ->getManagerForClass(Person::class)
            ->getRepository('MyVendorBundle:Person');

        // set the value as a callback rather than the entity
        // so it's not called unless necessary
        $property = $reflectionClass->getProperty('personCallback');
        $property->setAccessible(true);
        $property->setValue($user, function() use ($repository, $personId) {
            return $repository->find($personId);
        });
    }
}

My\VendorBundle\Entity\User

use My\FinancialBundle\Entity\Person;

class User
{
    //..

    /**
     * @var Person
     */
    private $person;

    /**
     * @var integer
     */
    private personId;

    /**
     * @var callable
     */
    private $personCallback;

    /**
     * Set person
     *
     * @param Person|null $person
     * @return $this
     */
    public function setPerson(Person $person = null)
    {
        $this->person = $person;

        // if the person is null reset the personId and
        // callback so that it's not called again
        if (null === $person) {
            $this->personId = null;
            $this->personCallback = null;
        } else {
            // set the personId to be stored on the db for the next postLoad event
            $this->personId = $person->getId();
        }

        return null;
    }

    /**
     * Get person
     *
     * @return Person|null
     */
    public function getPerson()
    {
        // if the person has not been set and the callback is callable,
        // call the function and set the result (of $repository->find($personId))
        // to the person property and then return it. The callback is
        // reset to stop unnecessary repository calls in case of a null response 
        if (null === $this->person && is_callable($this->personCallback)) {
            $this->person = call_user_func($this->personCallback);
            $this->personCallback = null;
        }

        return $this->person;
    }
}

AppBundle\Resources\services.yml

app.listener.insert_person:
    class: AppBundle\Doctrine\InsertPersonListener
    arguments:
        - '@doctrine'
    tags:
        - { name: doctrine.event_listener, event: postLoad }

UPDATE

After looking for an example of this in real life I found this. From this I saw that you could change the

$reflectionClass = new \ReflectionClass(User::class)

to

$reflectionClass = $args->getObjectManager()
    ->getClassMetadata(User::class)
    ->reflClass

which, I assume, would mean one less new \ReflectionClass(...) call per load.

like image 140
qooplmao Avatar answered Nov 12 '22 08:11

qooplmao