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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With