All'eliminazione della cascata con doctrine2


227

Sto cercando di fare un semplice esempio per imparare come eliminare una riga da una tabella padre ed eliminare automaticamente le righe corrispondenti nella tabella figlio usando Doctrine2.

Ecco le due entità che sto usando:

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

Le tabelle sono state create correttamente nel database, ma l'opzione On Elimina cascata non è stata creata. Che cosa sto facendo di sbagliato?


Hai testato comunque se le cascate funzionano correttamente? Forse Doctrine li gestisce nel codice anziché nel database.
Problematico

Risposte:


408

Ci sono due tipi di cascate in Dottrina:

1) Livello ORM - utilizza cascade={"remove"}nell'associazione - questo è un calcolo che viene eseguito in UnitOfWork e non influisce sulla struttura del database. Quando rimuovi un oggetto, UnitOfWork eseguirà l'iterazione su tutti gli oggetti dell'associazione e li rimuoverà.

2) Livello database - utilizza onDelete="CASCADE"su joinColumn dell'associazione - questo aggiungerà Su Elimina Cascade alla colonna chiave esterna nel database:

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

Voglio anche sottolineare che il modo in cui hai il tuo cascade = {"remove"} in questo momento, se elimini un oggetto Child, questa cascata rimuoverà l'oggetto Parent. Chiaramente non quello che vuoi.


3
In genere uso onDelete = "CASCADE" perché significa che l'ORM deve fare meno lavoro e dovrebbe avere prestazioni leggermente migliori.
Michael Ridgway,

58
Lo faccio anch'io ma dipende. Supponiamo ad esempio di avere una galleria di immagini con immagini. Quando si elimina la galleria, si desidera che anche le immagini vengano eliminate dal disco. Se lo implementate nel metodo delete () del vostro oggetto immagine, l'eliminazione a cascata usando l'ORM farà in modo che tutte le funzioni delte () dell'immagine vengano chiamate, risparmiando il lavoro di implementazione di cronjob che controllano i file immagine orfani.
influenza

4
@Michael Ridgway a volte devono essere applicate entrambe le affermazioni onDelete, cascade = {"remove"}ad esempio quando si hanno oggetti correlati a fosUser. Entrambi gli oggetti non dovrebbero esistere da soli
Luke Adamczewski,

17
Nota che puoi semplicemente scrivere @ORM\JoinColumn(onDelete="CASCADE")e lasciare che la dottrina gestisca automaticamente i nomi delle colonne.
mcfedr,

5
@dVaffection Questa è una buona domanda. Penso che onDelete="CASCADE"non avrà alcun effetto poiché Doctrine cascade={"remove"}rimuove le entità correlate prima di rimuovere l'entità radice (deve). Pertanto, quando l'entità radice viene eliminata, non sono rimaste relazioni esterne onDelete="CASCADE"da eliminare. Ma per essere sicuro suggerirei semplicemente di creare un piccolo test case e guardare le query in esecuzione e il loro ordine di esecuzione.
influenza

50

Ecco un semplice esempio. Un contatto ha uno o più numeri di telefono associati. Quando un contatto viene eliminato, voglio che vengano eliminati anche tutti i numeri di telefono associati, quindi utilizzo ON DELETE CASCADE. La relazione uno-a-molti / molti-a-uno è implementata dalla chiave esterna nei numeri di telefono.

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;

Aggiungendo "ON DELETE CASCADE" al vincolo di chiave esterna, i numeri di telefono verranno automaticamente eliminati quando viene eliminato il loro contatto associato.

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

Ora, quando una riga nella tabella dei contatti viene eliminata, tutte le righe dei numeri di telefono associati verranno automaticamente eliminate.

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

Per ottenere lo stesso risultato in Doctrine, per ottenere lo stesso comportamento "ON DELETE CASCADE" a livello di DB, devi configurare @JoinColumn con l' opzione 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";
}

Se adesso lo fai

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

vedrai che lo stesso SQL verrà generato come nel primo esempio raw-SQL


4
È il posizionamento corretto? L'eliminazione del numero di telefono non deve eliminare il contatto. È il contatto a cui la cancellazione dovrebbe attivare la cascata. Perché quindi posizionare la cascata su bambino / telefono?
przemo_li,

1
@przemo_li È il posizionamento corretto. Il contatto non sa che esistono numeri di telefono, poiché i numeri di telefono hanno un riferimento al contatto e un contatto non ha un riferimento ai numeri di telefono. Pertanto, se un contatto viene eliminato, un numero di telefono ha un riferimento a un contatto inesistente. In questo caso, vogliamo che accada qualcosa: innescare l'azione ON DELETE. Abbiamo deciso di eliminare in cascata la cancellazione, in modo da eliminare anche i numeri di telefono.
marijnz0r,

3
@przemi_li the onDelete="cascade"è posizionato correttamente nell'entità (sul child) perché si tratta di SQL cascading , che è posizionato sul child. Solo il Doctrine a cascata ( cascade=["remove"], che non viene utilizzato qui) viene posizionato sul genitore.
Maurice,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.