繁体   English   中英

在删除级联与教义2

[英]On delete cascade with doctrine2

我试图做一个简单的例子,以了解如何从父表中删除一行,并使用 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;
}

表已在数据库上正确创建,但未创建 On Delete Cascade 选项。 我究竟做错了什么?

Doctrine中有两种级联:

1) ORM 级别 - 在关联中使用cascade={"remove"} - 这是在 UnitOfWork 中完成的计算,不会影响数据库结构。 当您删除 object 时,UnitOfWork 将遍历关联中的所有对象并删除它们。

2) 数据库级别 - 在关联的 joinColumn 上使用onDelete="CASCADE" - 这会将 On Delete Cascade 添加到数据库中的外键列:

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

我还想指出,您现在拥有 cascade={"remove"} 的方式,如果您删除子 object,此级联将删除父 object。 显然不是你想要的。

这是一个简单的例子。 一个联系人有一对多关联的电话号码。 删除联系人时,我希望也删除其所有关联的电话号码,因此我使用 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 中实现相同的目的,要获得相同的 DB 级“ON DELETE CASCADE”行为,请使用onDelete="CASCADE"选项配置 @JoinColumn。

<?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

虽然在级联上删除的正确方法是使用@Michael Ridgway 答案,但也有可能听做 doctrine 事件来做同样的事情。

为什么? 好吧,您可能希望在删除父实体时做一些额外的事情,可能对某些实体使用软删除或硬删除其他实体。 您还可以将他的孩子重新影响到另一个实体,以防您想保留它并将其影响到父实体等...

所以这样做的方法是监听doctrine 事件 preRemove

preRemove - 在执行该实体的相应 EntityManager 删除操作之前,该实体发生 preRemove 事件。 DQL DELETE 语句不调用它。

请注意,只有在使用->remove时才会调用此事件。

首先创建您的事件订阅者/侦听器以收听此事件

<?php

namespace App\EventSubscriber;

use Doctrine\Common\EventSubscriber;
use App\Repository\FatherRepository;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use App\Entity\Father;
use App\Entity\Child;

class DoctrineSubscriber implements EventSubscriber
{
    private $fatherRepository;

    public function __construct(FatherRepository $fatherRepository) 
    {
        $this->fatherRepository = $fatherRepository;
    }
    
    public function getSubscribedEvents(): array
    {
        return [
            Events::preRemove => 'preRemove',
        ];
    }
    
    public function preRemove(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();

        if ($entity instanceof Father) {
            //Custom code to handle children, for example reaffecting to another father:
            $childs = $entity->getChildren();
            foreach($childs as $child){
                $otherFather = $this->fatherRepository->getOtherFather();
                child->setFather($otherFather);
            }
        }
    }
}

并且不要忘记将此 EventSubscriber 添加到您的服务中。yaml

  App\EventSubscriber\DoctrineSubscriber:
    tags:
      - { name: doctrine.event_subscriber }

在此示例中,父亲仍将被删除,但孩子不会因为有了新父亲而被删除。 例如,如果实体Father添加其他家庭成员,我们可以将孩子重新影响到家庭中的其他人。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM