Я хочу, чтобы две таблицы Person
и Address
делили свой первичный ключ:
CREATE TABLE Person (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY); CREATE TABLE Address (personId INT UNSIGNED NOT NULL PRIMARY KEY);
Я пытаюсь моделировать идею, что при создании Person
вы также создаете для него Address
.
Я придумал следующее сопоставление:
class Person { /** * @Id * @Column(type="integer") * @GeneratedValue */ private $id; /** * @OneToOne(targetEntity="Address", cascade={"all"}) * @JoinColumn(name="id", referencedColumnName="personId") */ private $address; public function __construct() { $this->address = new Address($this); } } class Address { /** * @Id * @OneToOne(targetEntity="Person") * @JoinColumn(name="personId", referencedColumnName="id") */ private $person; public function __construct(Person $person) { $this->person = $person; } }
Когда я пытаюсь сохранить Person
, я ожидаю, что он будет каскадом Address
, но я получаю следующее исключение:
Учение \ ORM \ ORMException
Сущность типа
Address
имеет личность через чужое лицоPerson
, однако у этого объекта нет самого идентификатора. Вы должны вызватьEntityManager#persist()
для связанного объекта и убедиться, что идентификатор был создан, прежде чем пытаться сохранитьAddress
. В случае создания Post Insert ID Generation (например, MySQLAuto-Increment
или PostgreSQLSERIAL
) это означает, что вы должны вызыватьEntityManager#flush()
между обеими персистентными операциями.
Я не могу явно вызвать flush()
, потому что я не явно persist()
ing Address
, вместо этого я ожидаю, что Person
будет каскадом persist Address
.
Так что, насколько я понимаю, это должно быть задачей Doctrine, чтобы flush()
в нужный момент, чтобы получить id
.
Эта взаимосвязь «один-к-одному» отлично каскадируется при использовании разных идентификаторов auto_increment
как для Person
и для Address
(но тогда я должен добавить addressId
для Person
).
Моя цель – сделать их одинаковыми.
Это возможно?
Как насчет использования Идентичность через внешние сущности в доктрине 2.1?
В вашем сценарии вы можете использовать @ManyToOne
вместо @OneToOne
.
Ассоциация сохраняется, когда вы сохраняете свою сторону. В вашем сопоставлении это Address
. Измените свое сопоставление, чтобы у владельца был внешний ключ, и все должно работать нормально.
/** * @OneToOne(targetEntity="Address", cascade={"all"}, inversedBy="person") * @JoinColumn(name="id", referencedColumnName="personId") */ private $address;
Другими словами, если Address
должен быть владельцем Person
тогда вы должны перенести Address
на первое место, чтобы сохранить связь. Если вы хотите, чтобы ассоциация сохранялась с Person
, тогда вы должны пометить Person
, являющуюся стороной-владельцем, и Address
который будет обратной стороной:
/** * @Id * @OneToOne(targetEntity="Person", inversedBy="address") * @JoinColumn(name="personId", referencedColumnName="id") */ private $person;
Вы можете использовать прослушиватель symfony и удалить стратегию автоматического увеличения из объекта-адреса:
Сначала создайте Listner и создайте свой класс listner, например AdressGenerator.php
<?php namespace Zodiac\HelperBundle\Listener; use Doctrine\ORM\Event\LifecycleEventArgs; use AAA\XXXBundle\Entity\Person; use AAA\XXXBundle\Entity\Adress; class AdressGenerator { public function prePersist(LifecycleEventArgs $args) { // This function is called just beforethe event Persist // The AdressGeneratorlistner is defined in the config.yml file // Get the entity manager and the entity from le LifecycleEventArgs $em = $args->getEntityManager(); $entity = $args->getEntity(); if ($entity instanceof Person) { //Create the Adress entity $adress = new Adress(); $adress->setId($entity->getId()); $em->persist($adress); $entity->setAdress($adress); $em->persist($entity); $em->flush(); } } }
Затем добавьте службу слушателя в config.yml:
services: AdressGenerator.listener: class: AAA\xxxBundle\Listener\AdressGenerator tags: - { name: doctrine.event_listener, event: prePersist }
Я нашел решение, но не знаю, лучшее ли это (мне это тяжело).
Это не проблема отображения, а то, как мы сохраняем:
Вместо того, чтобы делать:
$em->persist($person); $em->flush();
Мы должны сделать:
$em->transactional(function($em) use ($person) { $address = $person->getAddress(); $person->setAddress(null); $em->persist($person); $em->flush(); $address->setPerson($person); $em->persist($address); $em->flush(); });
Если у кого-то есть более чистое решение, он получит награду :-).