Beim Löschen Kaskade mit Doctrine2

227

Ich versuche, ein einfaches Beispiel zu erstellen, um zu lernen, wie eine Zeile aus einer übergeordneten Tabelle gelöscht und die übereinstimmenden Zeilen in der untergeordneten Tabelle mithilfe von Doctrine2 automatisch gelöscht werden.

Hier sind die beiden Entitäten, die ich verwende:

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

Die Tabellen werden korrekt in der Datenbank erstellt, die Option "Kaskade löschen" wird jedoch nicht erstellt. Was mache ich falsch?

rfc1484
quelle
Haben Sie getestet, ob die Kaskaden trotzdem richtig funktionieren? Vielleicht behandelt Doctrine sie im Code anstatt in der Datenbank.
Problematisch

Antworten:

408

In der Lehre gibt es zwei Arten von Kaskaden:

1) ORM-Ebene - wird cascade={"remove"}in der Zuordnung verwendet - Dies ist eine Berechnung, die in UnitOfWork durchgeführt wird und die Datenbankstruktur nicht beeinflusst. Wenn Sie ein Objekt entfernen, durchläuft UnitOfWork alle Objekte in der Zuordnung und entfernt sie.

2) Datenbankebene - wird onDelete="CASCADE"in der joinColumn der Zuordnung verwendet. Dadurch wird der Fremdschlüsselspalte in der Datenbank On Delete Cascade hinzugefügt:

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

Ich möchte auch darauf hinweisen, dass die Art und Weise, wie Sie Ihre Kaskade haben = {"remove"}, wenn Sie ein untergeordnetes Objekt löschen, diese Kaskade das übergeordnete Objekt entfernt. Ganz klar nicht was du willst.

Michael Ridgway
quelle
3
Ich verwende im Allgemeinen onDelete = "CASCADE", weil dies bedeutet, dass das ORM weniger Arbeit leisten muss und eine etwas bessere Leistung haben sollte.
Michael Ridgway
57
Ich auch, aber es kommt darauf an. Angenommen, Sie haben eine Bildergalerie mit Bildern. Wenn Sie die Galerie löschen, möchten Sie, dass die Bilder auch von der Festplatte gelöscht werden. Wenn Sie dies in der delete () -Methode Ihres Bildobjekts implementieren, stellt das kaskadierende Löschen mit dem ORM sicher, dass alle delte () -Funktionen Ihres Bildes aufgerufen werden, und erspart Ihnen die Implementierung von Cronjobs, die nach verwaisten Bilddateien suchen.
Grippe
4
@Michael Ridgway Manchmal sollten beide Anweisungen angewendet werden - onDeleteund cascade = {"remove"}zum Beispiel, wenn Sie ein Objekt haben, das mit fosUser zusammenhängt. Beide Objekte sollten nicht alleine existieren
Luke Adamczewski
17
Beachten Sie, dass Sie einfach schreiben @ORM\JoinColumn(onDelete="CASCADE")und die Doktrin die Spaltennamen automatisch verarbeiten lassen können.
mcfedr
5
@dVaffection Das ist eine gute Frage. Ich denke, dass onDelete="CASCADE"dies keine Auswirkungen haben wird, da Doctrine cascade={"remove"}die verwandten Entitäten entfernt, bevor die Stammentität entfernt wird (es muss). Wenn also die Stammentität gelöscht wird, sind keine Fremdbeziehungen mehr onDelete="CASCADE"zum Löschen übrig . Um sicherzugehen, würde ich vorschlagen, dass Sie einfach einen kleinen Testfall erstellen und sich die ausgeführten Abfragen und ihre Ausführungsreihenfolge ansehen.
Grippe
50

Hier ist ein einfaches Beispiel. Einem Kontakt sind eine bis viele Telefonnummern zugeordnet. Wenn ein Kontakt gelöscht wird, möchte ich, dass auch alle zugehörigen Telefonnummern gelöscht werden. Daher verwende ich ON DELETE CASCADE. Die Eins-zu-Viele / Viele-zu-Eins-Beziehung wird durch den Fremdschlüssel in den Telefonnummern implementiert.

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;

Durch Hinzufügen von "ON DELETE CASCADE" zur Fremdschlüsseleinschränkung werden phone_numbers automatisch gelöscht, wenn der zugehörige Kontakt gelöscht wird.

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

Wenn nun eine Zeile in der Kontakttabelle gelöscht wird, werden alle zugehörigen phone_numbers-Zeilen automatisch gelöscht.

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

Um dasselbe in Doctrine zu erreichen und dasselbe Verhalten auf DB-Ebene "ON DELETE CASCADE" zu erhalten, konfigurieren Sie die @ JoinColumn mit der Option 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";
}

Wenn Sie es jetzt tun

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

Sie werden sehen, dass dasselbe SQL generiert wird wie im ersten Raw-SQL-Beispiel

Kurt Krueckeberg
quelle
4
Ist es die richtige Platzierung? Das Löschen der Telefonnummer sollte den Kontakt nicht löschen. Es ist der Kontakt, dessen Löschung eine Kaskade auslösen soll. Warum dann Kaskade auf Kind / Telefon legen?
przemo_li
1
@przemo_li Es ist die richtige Platzierung. Der Kontakt weiß nicht, dass Telefonnummern vorhanden sind, da die Telefonnummern einen Verweis auf den Kontakt haben und ein Kontakt keinen Verweis auf die Telefonnummern hat. Wenn also ein Kontakt gelöscht wird, verweist eine Telefonnummer auf einen nicht vorhandenen Kontakt. In diesem Fall möchten wir, dass etwas passiert: Auslösen der Aktion EIN LÖSCHEN. Wir haben beschlossen, die Löschung zu kaskadieren, um auch die Telefonnummern zu löschen.
Marihnz0r
3
@przemi_li the onDelete="cascade"wird korrekt in der Entität (auf dem untergeordneten Element) platziert, da dies eine SQL-Kaskadierung ist , die auf dem untergeordneten Element platziert wird . Nur die Doctrine-Kaskadierung ( cascade=["remove"]die hier nicht verwendet wird) wird auf dem Elternteil platziert.
Maurice