Saat menghapus kaskade dengan doctrine2

227

Saya mencoba membuat contoh sederhana untuk mempelajari cara menghapus baris dari tabel induk dan secara otomatis menghapus baris yang cocok di tabel anak menggunakan Doctrine2.

Berikut adalah dua entitas yang saya gunakan:

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;
}

Tabel dibuat dengan benar pada database, tetapi opsi On Delete Cascade tidak dibuat. Apa yang saya lakukan salah?

rfc1484
sumber
Sudahkah Anda menguji apakah kaskade berfungsi dengan benar? Mungkin Doktrin menangani mereka dalam kode alih-alih dalam basis data.
Bermasalah

Jawaban:

408

Ada dua jenis kaskade dalam Doktrin:

1) Level ORM - gunakan cascade={"remove"}dalam asosiasi - ini adalah perhitungan yang dilakukan di UnitOfWork dan tidak mempengaruhi struktur database. Saat Anda menghapus objek, UnitOfWork akan beralih ke semua objek dalam asosiasi dan menghapusnya.

2) Level database - digunakan onDelete="CASCADE"pada joinColumn asosiasi - ini akan menambahkan On Delete Cascade ke kolom kunci asing di database:

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

Saya juga ingin menunjukkan bahwa cara Anda memiliki kaskade Anda = {"hapus"} sekarang, jika Anda menghapus objek Anak, kaskade ini akan menghapus objek Induk. Jelas bukan yang Anda inginkan.

Michael Ridgway
sumber
3
Saya biasanya menggunakan onDelete = "CASCADE" karena itu berarti ORM harus bekerja lebih sedikit dan seharusnya memiliki kinerja yang sedikit lebih baik.
Michael Ridgway
58
Saya juga melakukannya tetapi itu tergantung. Katakan misalnya Anda memiliki galeri gambar dengan gambar. Ketika Anda menghapus galeri, Anda ingin gambar juga dihapus dari disk. Jika Anda menerapkannya dalam metode delete () dari objek gambar Anda, penghapusan cascading menggunakan ORM akan memastikan bahwa semua fungsi delte () Anda dipanggil, menghemat pekerjaan Anda mengimplementasikan cronjobs yang memeriksa file gambar yatim.
Flu
4
@Michael Ridgway kadang-kadang kedua pernyataan harus diterapkan - onDeletedan juga cascade = {"remove"}misalnya ketika Anda memiliki beberapa objek yang terkait dengan fosUser. Kedua objek tidak boleh ada sendirian
Luke Adamczewski
17
Perhatikan bahwa Anda dapat menulis @ORM\JoinColumn(onDelete="CASCADE")dan membiarkan doktrin menangani nama kolom secara otomatis.
mcfedr
5
@DVDEKSI Itu pertanyaan yang bagus. Saya pikir hal onDelete="CASCADE"itu tidak akan memengaruhi apa pun karena Doctrine cascade={"remove"}menghapus entitas terkait sebelum menghapus entitas root (harus). Jadi ketika entitas root dihapus, tidak ada hubungan luar negeri yang tersisa untuk onDelete="CASCADE"dihapus. Tetapi untuk memastikan saya menyarankan Anda cukup membuat test case kecil dan melihat pertanyaan yang dieksekusi dan urutan eksekusi mereka.
Flu
50

Ini adalah contoh sederhana. Kontak memiliki satu hingga banyak nomor telepon terkait. Ketika kontak dihapus, saya ingin semua nomor telepon yang terkait juga dihapus, jadi saya menggunakan ON DELETE CASCADE. Hubungan satu-ke-banyak / banyak-ke-satu diimplementasikan dengan kunci asing di nomor telepon.

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;

Dengan menambahkan "ON DELETE CASCADE" ke batasan kunci asing, nomor telepon akan secara otomatis dihapus ketika kontak mereka yang terkait dihapus.

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);

Sekarang ketika baris dalam tabel kontak dihapus, semua baris nomor telepon yang terkait secara otomatis akan dihapus.

DELETE TABLE contacts as c WHERE c.id=1; /* delete cascades to phone_numbers */

Untuk mencapai hal yang sama di Doctrine, untuk mendapatkan behavoir DB-level "ON DELETE CASCADE" yang sama, Anda mengkonfigurasi @JoinColumn dengan opsi 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";
}

Jika sekarang Anda lakukan

# doctrine orm:schema-tool:create --dump-sql

Anda akan melihat bahwa SQL yang sama akan dihasilkan seperti pada contoh, baku-SQL

Kurt Krueckeberg
sumber
4
Apakah penempatannya benar? Menghapus nomor telepon tidak boleh menghapus kontak. Kontaklah yang harus memicu penghapusan kaskade. Lalu mengapa menempatkan kaskade di anak / telepon?
przemo_li
1
@przemo_li Ini penempatan yang benar. Kontak tidak tahu nomor telepon ada, karena nomor telepon memiliki referensi ke kontak, dan kontak tidak memiliki referensi ke nomor telepon. Jadi, jika kontak dihapus, nomor telepon memiliki referensi ke kontak yang tidak ada. Dalam hal ini, kami menginginkan sesuatu terjadi: memicu aksi ON DELETE. Kami memutuskan untuk menghapus penghapusan, jadi untuk menghapus nomor telepon juga.
marijnz0r
3
@przemi_li onDelete="cascade"ditempatkan dengan benar dalam entitas (pada anak) karena itu adalah cascading SQL , yang ditempatkan pada anak. Hanya Cascading Doctrine ( cascade=["remove"], yang tidak digunakan di sini) ditempatkan pada orang tua.
Maurice