2011-06-13 15 views
189

Estoy tratando de hacer un ejemplo simple para aprender a eliminar una fila de una tabla primaria y eliminar automáticamente las filas correspondientes en la tabla secundaria con Doctrine2.En cascada de eliminación con doctrine2

Estas son las dos entidades que estoy 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; 
} 

Las tablas se crean correctamente en la base de datos, pero el En la opción Eliminar cascada, no se crea. ¿Qué estoy haciendo mal?

+0

¿Ha probado si las cascadas realizan correctamente de todos modos? Tal vez Doctrine maneja el código en lugar de la base de datos. – Problematic

Respuesta

333

Hay dos tipos de cascadas en Doctrina:

1) Nivel de ORM - cascade={"remove"} utiliza en la asociación - esto es un cálculo que se realiza en el UnitOfWork y no afecta a la estructura de base de datos. Cuando elimina un objeto, UnitOfWork iterará sobre todos los objetos de la asociación y los eliminará.

nivel 2) Base de datos - utiliza onDelete="CASCADE" en joinColumn de la asociación - esto añadirá en Eliminar en cascada a la columna de clave externa en la base de datos:

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

También quiero señalar que la forma en que tiene su cascade = {"remove"} en este momento, si elimina un objeto Child, esta cascada eliminará el objeto Parent. Claramente no es lo que quieres.

+0

Gracias! Creo que ahora entiendo cómo hacerlo correctamente. ¿Cuál de los métodos de cascada considera mejores prácticas o recomendaría? – rfc1484

+3

Generalmente uso onDelete = "CASCADE" porque significa que el ORM tiene que hacer menos trabajo y debería tener un rendimiento un poco mejor. –

+44

Yo también, pero depende. Digamos, por ejemplo, que tienes una galería de imágenes con imágenes. Cuando elimina la galería, también quiere que las imágenes se eliminen del disco. Si implementa eso en el método delete() de su objeto de imagen, la eliminación en cascada usando el ORM asegurará que se invoquen todas las funciones delte() de su imagen, ahorrándole el trabajo de implementar cronjobs que verifiquen los archivos de imagen huérfanos. – flu

36

Aquí hay un ejemplo simple. Un contacto tiene uno a muchos números de teléfono asociados. Cuando se elimina un contacto, quiero que también se eliminen todos sus números de teléfono asociados , entonces uso ON DELETE CASCADE. La relación de uno a muchos/muchos a uno se implementa mediante la clave externa en 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; 

Mediante la adición de "ON DELETE CASCADE" a la restricción de clave externa, numeros_telefono se eliminarán automáticamente cuando su contacto asociado es borrado.

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

Ahora cuando se elimina una fila en la tabla de contactos, automáticamente se eliminarán todos sus asociados numeros_telefono filas.

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

Para lograr lo mismo en Doctrina, para obtener el mismo nivel de DB- "ON DELETE CASCADE" behavoir, se configura el @JoinColumn con la onDelete = "CASCADA" opción.

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

Si ahora haces

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

verá que el mismo SQL se generará como en el primer ejemplo, crudo-SQL

+3

¿Es correcta la ubicación? Eliminar el número de teléfono no debe eliminar el contacto. Es contacto quien debe eliminar desencadenar cascada. ¿Por qué entonces colocar la cascada en el niño/teléfono? –

+1

@przemo_li Es la ubicación correcta. El contacto no sabe que existen números de teléfono, porque los números de teléfono tienen una referencia al contacto, y un contacto no tiene una referencia a los números de teléfono. Entonces, si un contacto se elimina, un número de teléfono tiene una referencia a un contacto no existente. En este caso, queremos que algo suceda: activar la acción ON DELETE. Decidimos conectar en cascada la eliminación, por lo tanto, para eliminar los números de teléfono también. – marijnz0r

+2

@przemi_li 'onDelete =" cascade "' se coloca correctamente en la entidad (en el elemento secundario) porque eso es ** SQL cascading **, que se coloca en el elemento secundario. Solo la cascada de Doctrine ('cascade = [" remove "]', que * no * se usa aquí) se coloca en el padre. – Maurice

Cuestiones relacionadas