J'essaie de créer un exemple simple afin d'apprendre à supprimer une ligne d'une table parent et de supprimer automatiquement les lignes correspondantes de la table enfant à l'aide de Doctrine2.
Voici les deux entités que j'utilise:
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;
}
Père.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;
}
Les tables sont correctement créées sur la base de données, mais l'option On Delete Cascade n'est pas créée. Qu'est-ce que je fais mal?
Il y a deux sortes de cascades dans la doctrine:
1) Niveau ORM - utilise cascade={"remove"}
dans l'association - il s'agit d'un calcul effectué dans UnitOfWork et n'affecte pas la structure de la base de données. Lorsque vous supprimez un objet, UnitOfWork parcourt tous les objets de l'association et les supprime.
2) Niveau base de données - utilise onDelete="CASCADE"
sur la colonne joinColumn de l'association - ceci ajoutera On Delete Cascade à la colonne de clé étrangère de la base de données:
@ORM\JoinColumn(name="father_id", referencedColumnName="id", onDelete="CASCADE")
Je tiens également à souligner que, de la manière dont vous utilisez votre cascade = {"remove"}, si vous supprimez un objet Child, cette cascade supprimera l'objet Parent. Clairement pas ce que vous voulez.
Voici un exemple simple. Un contact a un à plusieurs numéros de téléphone associés. Lorsqu'un contact est supprimé, je souhaite que tous ses numéros de téléphone associés soient également supprimés. J'utilise donc ON DELETE CASCADE. La relation un à plusieurs/plusieurs à un est implémentée avec la clé étrangère dans les numéros de téléphone.
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;
En ajoutant "ON DELETE CASCADE" à la contrainte de clé étrangère, les numéros de téléphone seront automatiquement supprimés lorsque leur contact associé sera supprimé.
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);
Désormais, lorsqu'une ligne de la table des contacts est supprimée, toutes ses lignes phone_numbers associées seront automatiquement supprimées.
DELETE TABLE contacts as c WHERE c.id=1; /* delete cascades to phone_numbers */
Pour obtenir la même chose dans Doctrine, pour obtenir le même comportement "ON DELETE CASCADE" au niveau de la base de données, vous configurez le @JoinColumn avec l'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";
}
Si vous faites maintenant
# doctrine orm:schema-tool:create --dump-sql
vous verrez que le même SQL sera généré comme dans le premier exemple, raw-SQL