お久しぶりです、今日は眠いので大量にブログを投稿しようかと思います。
本日は、Table間のJoinを自動化してくれる、というかきちんと設定しないとまったくDAOの意味をなさないリレーションの設定に関してです。
詳しい中身はこちらの英語ドキュメントを参照しましょう。
http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
具体例として以下の2つのエンティティを考えてみましょう
TblTicket.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | <?php namespace Hoge\FugaBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; /** * * @ORM\Table(name="tbl_ticket") * @ORM\Entity(repositoryClass="Hoge\FugaBundle\Repository\TicketRepository") */ class TblTicket { /** * @var bigint $id * * @ORM\Column(name="id", type="bigint", nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ protected $id ; /** * @ORM\ManyToOne(targetEntity="TblTicket", inversedBy="childs") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true) */ private $parent ; /** * @ORM\OneToMany(targetEntity="TblTicket", mappedBy="parent") */ private $childs ; /** * @ORM\OneToOne(targetEntity="TblTicketData", mappedBy="ticket", cascade={"persist"}) */ private $data ; public function __construct(){ $this ->childs = new ArrayCollection(); } /** * Get id * * @return bigint */ public function getId() { return $this ->id; } /** * get parent * @return TblTicket */ public function getParent () { return $this ->parent; } /** * setparent * @param TblTicket $parent * @return TblTicket */ public function setParent ( $parent ) { $this ->parent = $parent ; return $this ; } /** * get data * @return TblTicketData */ public function getData () { return $this ->data; } /** * set data * @param TblTicketData $parentData * @return TblTicketData */ public function setParentData ( $parentData ) { $this ->parentData = $parentData ; return $this ; } /** * Add childs * * @param TblTicket $childs */ public function addChild(TblTicket $childs ) { $this ->childs[] = $childs ; } /** * Get childs * * @return Doctrine\Common\Collections\Collection */ public function getChilds() { return $this ->childs; } } |
TblTicketData.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | <?php namespace Hoge\FugaBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; /** * TblTicketData * * @ORM\Table(name="tbl_ticket_data") * @ORM\Entity */ class TblTicketData { /** * @var bigint $id * * @ORM\Column(name="id", type="bigint", nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ private $id ; /** * @ORM\OneToOne(targetEntity="TblTicket", inversedBy="data") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=false) */ private $ticket ; /** * @var string $content * * @ORM\Column(name="content", type="string", length=256, nullable=true) */ private $content ; /** * Get id * * @return bigint */ public function getId() { return $this ->id; } /** * get parent * @return TblTicket */ public function getTicket () { return $this ->ticket; } /** * setparent * @param TblTicket $parent * @return TblTicket */ public function setTicket ( $ticket ) { $this ->ticket = $ticket ; return $this ; } /** * get content * @return string */ public function getContent () { return $this ->content; } /** * set content * @param string $content * @return TblTicket */ public function setContent ( $content ) { $this ->content = $content ; return $this ; } } |
さて、まずは中身の説明に移る前に、このエンティティから生成されるテーブル定義を見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | CREATE TABLE tbl_ticket ( id bigint (20) NOT NULL AUTO_INCREMENT, parent_id bigint (20) DEFAULT NULL , PRIMARY KEY (id), KEY IDX_1A06C41C727ACA70 (parent_id), ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE tbl_ticket_data ( id bigint (20) unsigned NOT NULL AUTO_INCREMENT, ticket_id bigint (20) unsigned NOT NULL , content varchar (255) DEFAULT NULL , UNIQUE KEY id (id), UNIQUE KEY ticket_id (ticket_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
ここから、判断できるポイントを列挙していきましょう。
- tbl_ticketは木構造となっており、親チケットを自身のテーブルに持っている
- tbl_ticketは一番親のチケットを判断するために、parent_idにNullを許可している
- tbl_ticketはそのチケットに付随するデータticket_dataを1つ持っている
それでは、エンティティの中身を見ていきましょう、
@OneToOne
TblTicket.php と TblTicketData.phpにはそれぞれ@OneToOneアノテーションがありますが中身が少々違うようです。
TblTicket.php
1 | @ORM\OneToOne(targetEntity= "TblTicketData" , mappedBy= "ticket" , cascade={ "persist" }) |
TblTicketData.php
1 2 | @ORM\OneToOne(targetEntity= "TblTicket" , inversedBy= "data" ) @ORM\JoinColumn(name= "parent_id" , referencedColumnName= "id" , nullable=false) |
ポイントは
- 所属する側のデータ(TblTicketData)にはinversedByとJoinColumnを付与する
- 所属される側のデータ(TblTicket)にはmappedByを付与する
- JoinColumnによってTblTicketDataのparent_idとTblTicketのid間のリレーションが作られる
- nullable=falseによって、すべてのデータは必ずTblTicketへの所属が保障される
です。
では、続いて、このデータのInsertに関して考えてみましょう。
パターン1
1 2 3 4 5 6 7 8 9 10 11 12 13 | public function createAction() { $ticket = new TblTicket(); $data = new TblTicketData(); $data ->setContent( "aaaa" ); $data ->setTicket( $ticket ); $em = $this ->getDoctrine()->getEntityManager(); $em ->persist( $ticket ); $em ->persist( $data ); $em -> flush (); return new Response( 'Created product' ); } |
$dataには、ticket_idが必要なので、TblTicketオブジェクトをセットしないとErrorとなってしまいます。
ただし今回は、cascade={"persist"}をTblTicketに付与しているため、以下のコードによってもInsert可能です。
パターン2
1 2 3 4 5 6 7 8 9 10 11 12 13 | public function createAction() { $ticket = new TblTicket(); $data = new TblTicketData(); $data ->setContent( "aaaa" ); $data ->setTicket( $ticket ); $ticket ->setData( $data ); $em = $this ->getDoctrine()->getEntityManager(); $em ->persist( $ticket ); $em -> flush (); return new Response( 'Created product' ); } |
これは、persistの際にエンティティマネージャーが管理していないオブジェクトに関してもまとめてpersistするという設定によるものです。
注意
1 2 3 4 5 6 7 8 9 10 11 | public function errorAction() { $em = $this ->getDoctrine()->getEntityManager(); $repo = $em ->getRepository( "HogeFugaBundle:TblTicket" ); $ticket = $repo ->find(1); // エラー出るかも echo $ticket ->getData()->getContent(); return new Response( 'get content' ); } |
mappedByのOneToOneでは、対象エンティティのデータの存在を保証できない(逆の場合は保証される)ので、DataがNullの場合がありえます
OneToManyもやりたかったのですが、量が多くなってしまったのでそれは次回に回します。
投稿者プロフィール
-
中の人には主に、
PHP・Symfony2系の人と
Ruby・Rails系の人がいます。
ときどきJavascript・データベースにも手を出すかもしれません。
最新の投稿
データベース2015年2月3日Symfony2 Doctrine2の小ネタ(OneToMany,ManyToOneリレーション)
データベース2015年1月28日Symfony2 Doctrine2の小ネタ(OneToOneリレーション)
開発2015年1月21日Symfony2でもデザインパターン(PHPクラス編)
開発2014年11月26日Google検索結果画面にパンくずリストを表示する方法 (リッチスニペット対応)