Я хочу, чтобы две таблицы 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(); });
Если у кого-то есть более чистое решение, он получит награду :-).