При видаленні каскаду з doctrine2


227

Я намагаюся зробити простий приклад для того, щоб навчитися видаляти рядок із батьківської таблиці та автоматично видаляти відповідні рядки з дочірньої таблиці за допомогою 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;
}

Батько.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;
}

Таблиці правильно створені в базі даних, але параметр «Видалити каскад» він не створюється. Що я роблю неправильно?


Ви перевірили, чи каскади все-таки спрацьовують правильно? Можливо, доктрина обробляє їх у коді, а не в базі даних.
Проблемні

Відповіді:


408

У Вченні існує два види каскадів:

1) Рівень ORM - використання cascade={"remove"}в асоціації - це розрахунок, який проводиться в UnitOfWork і не впливає на структуру бази даних. Коли ви видаляєте об'єкт, UnitOfWork буде повторювати всі об'єкти в асоціації та видаляти їх.

2) Рівень бази даних - використовує onDelete="CASCADE"в JoinColumn асоціації - це додасть «Видалити каскад» у стовпчик із зовнішнім ключем у базі даних:

@ORM\JoinColumn(name="father_id", referencedColumnName="id", onDelete="CASCADE")

Я також хочу зазначити, що, як ви зараз маєте ваш cascade = {"delete"}, якщо ви видалите дочірній об'єкт, цей каскад видалить батьківський об'єкт. Ясна річ не те, що ти хочеш.


3
Я, як правило, використовую onDelete = "CASCADE", тому що це означає, що ORM повинен робити менше роботи, і він повинен мати трохи кращі показники.
Майкл Рідвей

57
Я теж, але це залежить. Скажімо, наприклад, у вас є галерея зображень із зображеннями. Коли ви видаляєте галерею, ви хочете, щоб зображення також були видалені з диска. Якщо ви реалізуєте це в методі delete () вашого об'єкта зображення, то каскадне видалення за допомогою ORM переконається, що всі функції delte () вашого зображення викликаються, заощаджуючи роботу над виконанням кронштейнів, які перевіряють наявність осиротілих файлів зображень.
грип

4
@Michael Ridgway іноді слід застосовувати обидва твердження - onDeleteяк і, cascade = {"remove"}наприклад, коли у вас є якийсь об'єкт, пов'язаний з fosUser. Обидва об’єкти не повинні існувати поодинці
Лука Адамчевський

17
Зауважте, що ви можете просто писати @ORM\JoinColumn(onDelete="CASCADE")та все ж дозволяти доктрині автоматично обробляти назви стовпців.
mcfedr

5
@dVaffection Це гарне запитання. Я думаю, що це onDelete="CASCADE"не вплине, оскільки Doctrine cascade={"remove"}видаляє пов'язані сутності перед тим, як видалити кореневу сутність (це повинно). Тож, коли кореневу сутність видалено, для видалення не залишається жодних зовнішніх відносин onDelete="CASCADE". Але для впевненості я б запропонував вам просто створити невеликий тестовий випадок і переглянути запити, що виконуються, та їх порядок виконання.
грип

50

Ось простий приклад. Контакт має один до багатьох асоційованих номерів телефонів. Коли контакт видаляється, я хочу, щоб усі його асоційовані телефонні номери також були видалені, тому я використовую НАДАЛИТИ КАСКАД. Взаємовідношення один-багато-багато-до-одного реалізується за допомогою зовнішнього ключа в номерах телефонів.

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" до обмеження зовнішнього ключа, телефонні номери автоматично видаляються при видаленні відповідного контакту.

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, щоб отримати той самий рівень DB "ON DELETE CASCADE", ви налаштовуєте @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


4
Це правильне розміщення? Видалення номера телефону не повинно видаляти контакт. Це контакт, кого видалення повинно викликати каскад. Чому тоді розміщувати каскад на дитині / телефоні?
przemo_li

1
@przemo_li Це правильне розміщення. Контакт не знає телефонних номерів, оскільки в телефонних номерах є посилання на контакт, а в контактному немає посилань на телефонні номери. Отже, якщо контакт видаляється, телефонний номер має посилання на неіснуючий контакт. У цьому випадку ми хочемо, щоб щось сталося: ініціював дію ON DELETE. Ми вирішили каскадно видалити, щоб також видалити телефонні номери.
marijnz0r

3
@przemi_li onDelete="cascade"розміщено правильно в об'єкті (на дитині), оскільки це каскад SQL , який розміщується на дитині. На батьківщині розміщується лише Доктрина, що каскадує ( cascade=["remove"]яка тут не використовується).
Моріс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.