Я пытаюсь сделать простой пример, чтобы узнать, как удалить строку из родительской таблицы и автоматически удалить соответствующие строки в дочерней таблице с помощью Doctrine2.
Вот два объекта, которые я использую:
Child.php:
<?php namespace Acme\CascadeBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="child") */ class Child { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\ManyToOne(targetEntity="Father", cascade={"remove"}) * * @ORM\JoinColumns({ * @ORM\JoinColumn(name="father_id", referencedColumnName="id") * }) * * @var father */ private $father; }
Father.php
<?php namespace Acme\CascadeBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="father") */ class Father { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ private $id; }
Таблицы правильно создаются в базе данных, но опция On Delete Cascade не создается. Что я делаю не так?
В Доктрине есть два вида каскадов:
1) Уровень ORM – использует cascade={"remove"}
в ассоциации – это расчет, который выполняется в UnitOfWork и не влияет на структуру базы данных. Когда вы удаляете объект, UnitOfWork будет перебирать все объекты в ассоциации и удалять их.
2) Уровень базы данных – использует onDelete="CASCADE"
в joinColumn ассоциации – это добавит On Delete Cascade в столбец внешнего ключа в базе данных:
@ORM\JoinColumn(name="father_id", referencedColumnName="id", onDelete="CASCADE")
Я также хочу указать, что у вас есть ваш cascade = {"remove"} прямо сейчас, если вы удалите дочерний объект, этот каскад удалит объект Parent. Ясно, что вы не хотите.
Вот простой пример. Контакт имеет один или несколько связанных телефонных номеров. Когда контакт удален, я хочу, чтобы все связанные с ним номера телефонов также были удалены, поэтому я использую ON DELETE CASCADE. Отношение «один-ко-многим / много-к-одному» реализуется с помощью внешнего ключа в phone_numbers.
CREATE TABLE contacts (contact_id BIGINT AUTO_INCREMENT NOT NULL, name VARCHAR(75) NOT NULL, PRIMARY KEY(contact_id)) ENGINE = InnoDB; CREATE TABLE phone_numbers (phone_id BIGINT AUTO_INCREMENT NOT NULL, phone_number CHAR(10) NOT NULL, contact_id BIGINT NOT NULL, PRIMARY KEY(phone_id), UNIQUE(phone_number)) ENGINE = InnoDB; ALTER TABLE phone_numbers ADD FOREIGN KEY (contact_id) REFERENCES \ contacts(contact_id) ) ON DELETE CASCADE;
Добавив «ON DELETE CASCADE» к ограничению внешнего ключа, phone_numbers автоматически удаляются, когда их связанный контакт удаляется.
INSERT INTO table contacts(name) VALUES('Robert Smith'); INSERT INTO table phone_numbers(phone_number, contact_id) VALUES('8963333333', 1); INSERT INTO table phone_numbers(phone_number, contact_id) VALUES('8964444444', 1);
Теперь, когда строка в таблице контактов удалена, все связанные с ней строки phone_numbers будут автоматически удалены.
DELETE TABLE contacts as c WHERE c.id=1; /* delete cascades to phone_numbers */
Чтобы достичь того же самого в Doctrine, чтобы получить тот же уровень «ON DELETE CASCADE» уровня DB, вы настраиваете @JoinColumn с опцией onDelete = «CASCADE» .
<?php namespace Entities; use Doctrine\Common\Collections\ArrayCollection; /** * @Entity * @Table(name="contacts") */ class Contact { /** * @Id * @Column(type="integer", name="contact_id") * @GeneratedValue */ protected $id; /** * @Column(type="string", length="75", unique="true") */ protected $name; /** * @OneToMany(targetEntity="Phonenumber", mappedBy="contact") */ protected $phonenumbers; public function __construct($name=null) { $this->phonenumbers = new ArrayCollection(); if (!is_null($name)) { $this->name = $name; } } public function getId() { return $this->id; } public function setName($name) { $this->name = $name; } public function addPhonenumber(Phonenumber $p) { if (!$this->phonenumbers->contains($p)) { $this->phonenumbers[] = $p; $p->setContact($this); } } public function removePhonenumber(Phonenumber $p) { $this->phonenumbers->remove($p); } } <?php namespace Entities; /** * @Entity * @Table(name="phonenumbers") */ class Phonenumber { /** * @Id * @Column(type="integer", name="phone_id") * @GeneratedValue */ protected $id; /** * @Column(type="string", length="10", unique="true") */ protected $number; /** * @ManyToOne(targetEntity="Contact", inversedBy="phonenumbers") * @JoinColumn(name="contact_id", referencedColumnName="contact_id", onDelete="CASCADE") */ protected $contact; public function __construct($number=null) { if (!is_null($number)) { $this->number = $number; } } public function setPhonenumber($number) { $this->number = $number; } public function setContact(Contact $c) { $this->contact = $c; } } ?> <?php $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); $contact = new Contact("John Doe"); $phone1 = new Phonenumber("8173333333"); $phone2 = new Phonenumber("8174444444"); $em->persist($phone1); $em->persist($phone2); $contact->addPhonenumber($phone1); $contact->addPhonenumber($phone2); $em->persist($contact); try { $em->flush(); } catch(Exception $e) { $m = $e->getMessage(); echo $m . "<br />\n"; }
Если вы сейчас
# doctrine orm:schema-tool:create --dump-sql
вы увидите, что тот же SQL будет сгенерирован, как и в первом примере raw-SQL