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

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选项。我做错了什么?

188221 次浏览

在《教义》中有两种级联:

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

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

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

我还想指出,你现在有cascade={"remove"}的方式,如果你删除一个子对象,这个级联将删除父对象。显然不是你想要的。

这里有一个简单的例子。联系人有一个到多个相关联的电话号码。当一个联系人被删除时,我想要它所有相关的电话号码 也要删除,所以我使用ON DELETE CASCADE。一对多/多对一关系由phone_numbers中的外键实现。< / p >
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将在其关联的联系人被删除时自动删除 删除。< / p >
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 with onDelete =“级联”选项。< / p >
<?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的答案,但也有可能听do doctrine事件来做同样的事情。

为什么?当删除父实体时,你可能想要做额外的事情,可能在一些实体上使用软可删除对象,或者在其他实体上使用硬删除对象。你也可以将他的子实体重新影响到另一个实体,如果你想保留它,并将它影响到一个父实体等等……

所以这样做的方法是监听教条事件预先移动

preRemove - preRemove事件发生在一个给定的实体 为该实体执行相应的EntityManager删除操作。

. 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添加到services.yaml中

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

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