doctrine2でカスケードを削除する


227

親テーブルから行を削除し、Doctrine2を使用して子テーブルの一致する行を自動的に削除する方法を学ぶために、簡単な例を作ろうとしています。

ここに私が使用している2つのエンティティがあります:

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

テーブルはデータベースに正しく作成されますが、[削除時にカスケード]オプションは作成されません。何が悪いのですか?


とにかくカスケードが正しく機能するかどうかをテストしましたか?おそらくDoctrineはデータベースではなくコードでそれらを扱います。
問題のある2011年

回答:


408

Doctrineには2種類のカスケードがあります:

1)ORMレベル- cascade={"remove"}関連付けで使用-これはUnitOfWorkで実行される計算であり、データベース構造には影響しません。オブジェクトを削除すると、UnitOfWorkは関連付け内のすべてのオブジェクトを反復処理して削除します。

2)データベースレベル- onDelete="CASCADE"アソシエーションのjoinColumnで使用-データベースの外部キー列にOn Delete Cascadeを追加します。

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

また、cascade = {"remove"}を現在持っている方法についても触れておきます。Childオブジェクトを削除すると、このカスケードによってParentオブジェクトが削除されます。明らかにあなたが望むものではありません。


3
ORMは必要な作業が少なく、パフォーマンスが少し向上するはずなので、通常はonDelete = "CASCADE"を使用します。
Michael Ridgway

58
私もそうですが、状況によります。たとえば、画像を含む画像ギャラリーがあるとします。ギャラリーを削除すると、画像もディスクから削除されます。画像オブジェクトのdelete()メソッドでそれを実装すると、ORMを使用したカスケード削除により、画像のすべてのdelte()関数が確実に呼び出され、孤立した画像ファイルをチェックするcronjobsを実装する手間が省けます。
インフルエンザ

4
@Michaelリッジウェイは、時には両方のステートメントが適用されるべきである- onDeleteだけでなく、cascade = {"remove"}例えばあなたがfosUserに関連するいくつかのオブジェクトを持っている場合。両方のオブジェクトが単独で存在するべきではありません
Luke Adamczewski、

17
@ORM\JoinColumn(onDelete="CASCADE")doctrineに列名を自動的に処理させるだけで、まだ書くことができることに注意してください。
mcfedr 2014年

5
@dVaffectionいい質問ですね。onDelete="CASCADE"Doctrine cascade={"remove"}はルートエンティティを削除する前に関連エンティティを削除するので(それはしなければなりません)、影響はないと思います。したがって、ルートエンティティが削除されるonDelete="CASCADE"と、削除する外部関係は残りません。しかし、確かに、小さなテストケースを作成し、実行中のクエリとその実行順序を確認することをお勧めします。
インフルエンザ

50

これは簡単な例です。連絡先には、1対多の電話番号が関連付けられています。連絡先が削除されると、関連付けられているすべての電話番号も削除してほしいので、ON DELETE CASCADEを使用します。1対多/多対1の関係は、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;

"ON DELETE CASCADE"を外部キー制約に追加すると、関連付けられた連絡先が削除されると、phone_numbersが自動的に削除されます。

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

今するなら

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

最初のraw-SQLの例と同じSQLが生成されることがわかります


4
正しい配置ですか?電話番号を削除しても連絡先は削除されません。削除によってカスケードがトリガーされるのは連絡先です。なぜ子供/電話にカスケードを配置するのですか?
przemo_li 2015年

1
@przemo_li正しい配置です。電話番号には連絡先への参照があり、連絡先には電話番号への参照がないため、連絡先は電話番号が存在することを知りません。したがって、連絡先が削除された場合、電話番号には存在しない連絡先への参照が含まれます。この場合は、何かを実行する必要があります。ONDELETEアクションをトリガーします。削除をカスケードすることにしたので、電話番号も削除しました。
marijnz0r 2016年

3
@przemi_liは、子に配置されるSQLカスケードであるonDelete="cascade"ため、エンティティ(子)に正しく配置されます。Doctrineカスケード(ここでは使用されません)のみが親に配置されます。cascade=["remove"]
モーリス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.