Khi xóa tầng với doctrine2


227

Tôi đang cố gắng tạo một ví dụ đơn giản để tìm hiểu cách xóa một hàng khỏi bảng cha và tự động xóa các hàng khớp trong bảng con bằng Doctrine2.

Đây là hai thực thể tôi đang sử dụng:

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

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

Các bảng được tạo chính xác trên cơ sở dữ liệu, nhưng tùy chọn On Delete Cascade không được tạo. Tôi đang làm gì sai?


Bạn đã kiểm tra xem các thác thực hiện chính xác chưa? Có lẽ Doctrine xử lý chúng trong mã thay vì trong cơ sở dữ liệu.
Có vấn đề

Câu trả lời:


408

Có hai loại thác trong Học thuyết:

1) Cấp độ ORM - sử dụng cascade={"remove"}trong liên kết - đây là một phép tính được thực hiện trong UnitOfWork và không ảnh hưởng đến cấu trúc cơ sở dữ liệu. Khi bạn xóa một đối tượng, UnitOfWork sẽ lặp lại trên tất cả các đối tượng trong liên kết và xóa chúng.

2) Cấp độ cơ sở dữ liệu - sử dụng onDelete="CASCADE"trên joColumn của hiệp hội - điều này sẽ thêm On Xóa Cascade vào cột khóa ngoại trong cơ sở dữ liệu:

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

Tôi cũng muốn chỉ ra rằng cách bạn có tầng của bạn = {"remove"} ngay bây giờ, nếu bạn xóa một đối tượng Con, tầng này sẽ xóa đối tượng Parent. Rõ ràng không phải những gì bạn muốn.


3
Tôi thường sử dụng onDelete = "CASCADE" bởi vì điều đó có nghĩa là ORM phải thực hiện ít công việc hơn và nó sẽ có hiệu suất tốt hơn một chút.
Michael Ridgway

57
Tôi làm quá nhưng nó phụ thuộc. Nói ví dụ bạn có một bộ sưu tập hình ảnh với hình ảnh. Khi bạn xóa bộ sưu tập, bạn cũng muốn xóa hình ảnh khỏi đĩa. Nếu bạn thực hiện phương thức xóa () của đối tượng hình ảnh của mình, việc xóa tầng bằng ORM sẽ đảm bảo rằng tất cả các hàm delte () của hình ảnh của bạn được gọi, giúp bạn tiết kiệm công việc thực hiện các cronjobs kiểm tra các tệp hình ảnh mồ côi.
cúm

4
@Michael Ridgway đôi khi cả hai câu lệnh nên được áp dụng - onDeletecũng như cascade = {"remove"}ví dụ khi Bạn có một số đối tượng liên quan đến fosUser. Cả hai đối tượng không nên tồn tại một mình
Luke Adamczewski

17
Lưu ý rằng bạn chỉ có thể viết @ORM\JoinColumn(onDelete="CASCADE")và vẫn để học thuyết tự động xử lý tên cột.
mcfedr

5
@dVaffection Đó là một câu hỏi hay. Tôi nghĩ rằng onDelete="CASCADE"sẽ không có bất kỳ ảnh hưởng nào vì Doctrine cascade={"remove"}sẽ xóa các thực thể liên quan trước khi xóa thực thể gốc (nó phải). Vì vậy, khi thực thể gốc bị xóa, sẽ không có bất kỳ quan hệ nước ngoài nào bị onDelete="CASCADE"xóa. Nhưng để chắc chắn tôi sẽ đề nghị bạn chỉ cần tạo một trường hợp thử nghiệm nhỏ và xem xét các truy vấn đang được thực hiện và thứ tự thực hiện của chúng.
cúm

50

Đây là ví dụ đơn giản. Một số liên lạc có một đến nhiều số điện thoại liên quan. Khi một số liên lạc bị xóa, tôi muốn tất cả các số điện thoại liên quan của nó cũng bị xóa, vì vậy tôi sử dụng BẬT XÓA CASCADE. Mối quan hệ một-nhiều / nhiều-một được thực hiện bằng khóa ngoại trong 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;

Bằng cách thêm "BẬT XÓA CASCADE" vào ràng buộc khóa ngoài, phone_numbers sẽ tự động bị xóa khi liên hệ liên kết của chúng bị xóa.

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

Bây giờ khi một hàng trong bảng liên hệ bị xóa, tất cả các hàng phone_numbers được liên kết của nó sẽ tự động bị xóa.

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

Để đạt được điều tương tự trong Doctrine, để có cùng một hành vi "TRÊN XÓA CASCADE" cấp DB, bạn định cấu hình @JoinColumn với tùy chọn 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";
}

Nếu bây giờ bạn làm

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

bạn sẽ thấy rằng cùng một SQL sẽ được tạo như trong ví dụ SQL thô đầu tiên


4
Là vị trí chính xác? Xóa số điện thoại không nên xóa liên lạc. Đó là liên hệ với những người xóa nên kích hoạt thác. Tại sao sau đó đặt thác trên trẻ em / điện thoại?
przemo_li

1
@przemo_li Đó là vị trí chính xác. Số liên lạc không biết số điện thoại tồn tại, vì số điện thoại có tham chiếu đến số liên lạc và số liên lạc không có tham chiếu đến số điện thoại. Vì vậy, nếu một liên hệ bị xóa, một số điện thoại có tham chiếu đến một liên hệ không tồn tại. Trong trường hợp này, chúng tôi muốn một cái gì đó xảy ra: kích hoạt hành động BẬT XÓA. Chúng tôi quyết định xếp tầng xóa, vì vậy để xóa các số điện thoại là tốt.
marijnz0r

3
@przemi_li cái onDelete="cascade"được đặt chính xác trong thực thể (trên con) vì đó là tầng SQL , được đặt trên con. Chỉ có tầng thuyết ( cascade=["remove"], mà là không sử dụng ở đây) được đặt trên công ty mẹ.
Maurice
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.