Я пытаюсь сделать простой пример, чтобы узнать, как удалить строку из родительской таблицы и автоматически удалить соответствующие строки в дочерней таблице с помощью 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" } прямо сейчас, если вы удалите дочерний объект, этот каскад удалит родительский объект. Ясно, что вы не хотите.
Вот простой пример. Контакт имеет один или несколько связанных телефонных номеров. Когда контакт удаляется, я хочу, чтобы все его связанные телефонные номера также удаляться, поэтому я использую 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);
Теперь, когда строка в таблице контактов удаляется, все связанные с ней строки номера телефона будут удалены автоматически.
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 будет сгенерирован, как и в первом примере с исходным кодом SQL