外部キー=主キーのスキーマを使う場合のowningSide
Twitterで@s_kanedaさんがスキーマのowningSideについてつぶやいていました。
symfonyのスキーマファイルに出てくるowningSideってなんだろう
http://twitter.com/s_kaneda/status/10203798714@hidenorigoto ありがとうございます!外部キーが主キーになっているカラムをonDelete: cascadeにしたくて、owningSideを使ってみたらうまくいきました。でもまだよくわかっていません・・・。
http://twitter.com/s_kaneda/status/10209810165
この「owningSide」ですが、どういう時に指定するものなのか分かりやすいドキュメントを見つけられませんでしたが、doctrineのサイトでは以下のページがありました。
抜粋すると、次のように説明があります。
・The many side of one-to-many/many-to-one bidirectional relationships must be the owning side, hence the mappedBy element cannot be specified on the ManyToOne side.
・For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
一対多のリレーションでは、「多」の側がowning side、一対一のリレーションでは、外部キーを含む側がowning sideとあります。
で、@s_kanedaさんの書かれているような「外部キーが主キーになっている場合」なのですが、これは以前から私も気になっていることでしたので、ちょっと調べてみました。
Doctrineのソース(/Relation/Parser.php)
いろいろソースを追いかけたところ、まず次のソースにたどり着きました。
http://trac.doctrine-project.org/browser/branches/1.2/lib/Doctrine/Relation/Parser.php
このパーサークラスのcompleteDefinition()メソッドで、リレーションの定義をテーブルに読み込んでいます。
$def['localKey']という変数にtrueが設定されると、リレーションがDoctrine_Relation_LocalKeyクラスで読み込まれ、そうでない場合はDoctrine_Relation_ForeignKeyクラスで読み込まれます。
リレーションとして正しく動作するためには、リレーションの両端のいずれかがLocalKeyになっていないといけないのですが、主キー=外部キーというテーブルをそのまま設定すると、LocalKeyになりません。
そこで、436行目あたりにあるように、owningSideをtrueに設定すると、うまくLocaKeyと認識してくれるようです。
実際の例
簡単なスキーマで試してみます。
TableAは単純なテーブル、TableBの主キーは、TableAのidを参照しています。
schema.ymlは以下のようになります。
TableA: actAs: Timestampable: columns: id: primary: true type: integer autoincrement: true name: type: string(32) notnull: true TableB: actAs: Timestampable: columns: table_a_id: primary: true type: integer detail: type: string relations: TableA: class: TableA foreignAlias: TableB foreignType: one onDelete: CASCADE type: one local: table_a_id foreign: id owningSide: true
★一番最後の行のowningSideに注意
これでモデルの生成・テーブルの生成(doctrine:build --all)を実行すると、データベース上にもきちんとリレーションが定義されました。
おまけ
テーブルに定義されているリレーションを調べる
<?php $table = Doctrine_Core::getTable('TableB'); $relations = $table->getRelations(); foreach ($relations as $relation_name=>$relation_obj) { echo $relation_name . "\n"; echo get_class($relation_obj) . "\n"; }
出力
TableA Doctrine_Relation_LocalKey
テーブルのスキーマのエクスポート形式を出力する
<?php $table = Doctrine_Core::getTable('TableB'); var_dump($table->getExportableFormat());
出力
array(3) { ["tableName"]=> string(7) "table_b" ["columns"]=> array(4) { ["table_a_id"]=> array(3) { ["primary"]=> bool(true) ["type"]=> string(7) "integer" ["length"]=> int(8) } ["detail"]=> array(2) { ["type"]=> string(6) "string" ["length"]=> NULL } ["created_at"]=> array(3) { ["notnull"]=> bool(true) ["type"]=> string(9) "timestamp" ["length"]=> int(25) } ["updated_at"]=> array(3) { ["notnull"]=> bool(true) ["type"]=> string(9) "timestamp" ["length"]=> int(25) } } ["options"]=> array(20) { ["name"]=> string(6) "TableB" ["tableName"]=> string(7) "table_b" ["sequenceName"]=> NULL ["inheritanceMap"]=> array(0) { } ["enumMap"]=> array(0) { } ["type"]=> NULL ["charset"]=> string(4) "UTF8" ["collate"]=> NULL ["treeImpl"]=> NULL ["treeOptions"]=> array(0) { } ["indexes"]=> array(0) { } ["parents"]=> array(2) { [0]=> string(16) "sfDoctrineRecord" [1]=> string(10) "BaseTableB" } ["joinedParents"]=> array(0) { } ["queryParts"]=> array(0) { } ["versioning"]=> NULL ["subclasses"]=> array(0) { } ["orderBy"]=> NULL ["declaringClass"]=> object(ReflectionClass)#285 (1) { ["name"]=> string(10) "BaseTableB" } ["foreignKeys"]=> array(1) { ["table_b_table_a_id_table_a_id"]=> array(6) { ["name"]=> string(29) "table_b_table_a_id_table_a_id" ["local"]=> string(10) "table_a_id" ["foreign"]=> string(2) "id" ["foreignTable"]=> string(7) "table_a" ["onUpdate"]=> NULL ["onDelete"]=> string(7) "CASCADE" } } ["primary"]=> array(1) { [0]=> string(10) "table_a_id" } } }
Doctrineでは、このエクスポート形式を元にデータベースのテーブル生成用SQLが作成されます。
owningSideを定義していない場合、エクスポート形式を調べると該当リレーション情報が出力されません。
P.S.
データモデルはORM Designerで作ってます。(上の画像も)
何度かバージョンアップされて、かなりいい感じになってきました。(まだ不満点もありますが・・・)