Cake PHPの覚書

アソシエーション | 結合モデルのデータ表示と更新 | hasMany


 アソシエーションは複数のモデルを1つのモデルにまとめる技術。テーブル結合のJOINと似ているが少し異なる。
一つのモデルに複数のテーブル情報を定義しておけば、あたかも一つのテーブルのようにデータを取得や更新ができる。
ただし、バリデーションは各テーブルに該当するモデルごとに定義したほうが良い。
またDB更新も複雑なテーブル連結だと上手く行かないようである。下記の更新処理もテーブルごとに分けて更新している。


コントローラ:/Controller/TestHasManyController
	class TestHasManyController extends AppController {
		public $name = 'TestHasMany';
		public $uses = array (
				'TestHasManyA',
				'TestHasManyB',
				'TestHasManyC'
		);
		public function index() {
			if (! empty ( $this->request->data )) {

				$dataSource = $this->TestHasManyA->getDataSource ();
				$dataSource->begin ();

				$this->TestHasManyA->saveAll ( $this->request->data ['TestHasManyA'], array (
						'atomic' => false
				) );
				$errors = $this->TestHasManyA->validationErrors;

				if (empty ( $errors )) {
					$this->TestHasManyB->saveAll ( $this->request->data ['TestHasManyB'], array (
							'atomic' => false
					) );
					$errors = $this->TestHasManyB->validationErrors;
				}
				if (empty ( $errors )) {
					$this->TestHasManyC->saveAll ( $this->request->data ['TestHasManyC'], array (
							'atomic' => false
					) );
					$errors = $this->TestHasManyC->validationErrors;
				}

				if (empty ( $errors )) {
					$dataSource->commit ();
				} else {
					$dataSource->rollback ();
				}
			} else {
			}

			$data = $this->TestHasManyA->find ( 'all' );

			$this->set ( array (
					'data' => $data
			) );
		}

	}
	

モデル:/Model/TestHasManyA
	App::uses('Model', 'Model');
	class TestHasManyA extends Model {

		public $name = 'TestHasManyA';

		//アソシエーション(モデル連結)hasManyは1:多の関係を表す。
		public $hasMany = array(
				'TestHasManyB' => array(
						'className'     => 'TestHasManyB',
						'foreignKey'    => 'test_has_many_a_id',
						'dependent'     => true //連動削除
				),
				'TestHasManyC' => array(
						'className'     => 'TestHasManyC',
						'foreignKey'    => 'test_has_many_a_id',
						'dependent'     => true //連動削除
				),
		);

		//バリデーション情報
		public $validate = array(
				'name' => array(

					'maxLength'=>array(
						'rule' => array('maxLength', 5),
						'message' => '一般名は5文字以内です。'
					)
				),

		);


	}
	


モデル:/Model/TestHasManyB
	App::uses('Model', 'Model');
	class TestHasManyB extends Model {

		public $name = 'TestHasManyB';

		public $validate = array(
				'kind' => array(

					'maxLength'=>array(
						'rule' => array('maxLength', 8),
						'message' => '種類は8文字以内です。'
					)
				),

		);


	}
	


モデル:/Model/TestHasManyC
	App::uses('Model', 'Model');
	class TestHasManyC extends Model {

		public $name = 'TestHasManyC';

		public $validate = array(
				'note' => array(

					'maxLength'=>array(
						'rule' => array('maxLength', 16),
						'message' => 'ノートは16文字以内です。'
					)
				),

		);
	}
	



ビュー:/View/TestHasMany/index.ctp
	<h1>結合モデルのデータ表示と更新 | hasMany</h1>

	<table border="1">

		<thead>
			<tr><th>A_ID</th><th>一般名</th><th>テーブルBデータ</th><th>テーブルCデータ</th></tr>
		</thead>

		<tbory>
		<?php
		echo $this->Form->create('DataX', array('url' => '#'));
		foreach($data as $i=> $ary){

			echo "<tr>";
			$entA=$ary['TestHasManyA'];
			$id=$entA['id'];
			echo "<td>";
			echo $entA['id'];
			echo $this->Form->input("TestHasManyA.{$id}.id", array('type' => 'hidden','value' => $id,'label' => false,));
			echo "</td>\n";

			echo "<td>";
			echo $this->Form->input("TestHasManyA.{$id}.name", array('type' => 'text','value' => $entA['name'],'label' => false,));
			echo "</td>";

			//Bデータを表示
			$dataB=$ary['TestHasManyB'];
			echo "<td>\n<ul>";
			foreach($dataB as $i_b=> $entB){
				$id=$entB['id'];
				echo "<li>";
				echo $this->Form->input("TestHasManyB.{$id}.kind", array('type' => 'text','value' => $entB['kind'],'label' => false,));
				echo $this->Form->input("TestHasManyB.{$id}.id", array('type' => 'hidden','value' => $entB['id'],'label' => false,));
				echo $this->Form->input("TestHasManyB.{$id}.test_has_many_a_id", array('type' => 'hidden','value' => $entB['test_has_many_a_id'],'label' => false,));
				echo "</li>\n";
			}
			echo "</ul></td>\n";

			//Cデータを表示
			$dataC=$ary['TestHasManyC'];
			echo "<td>\n<ul>";
			foreach($dataC as $i_c=>$entC){
				$id=$entC['id'];
				echo "<li>";
				echo $this->Form->input("TestHasManyC.{$id}.note", array('type' => 'text','value' => $entC['note'],'label' => false,));
				echo $this->Form->input("TestHasManyC.{$id}.id", array('type' => 'hidden','value' => $entC['id'],'label' => false,));
				echo $this->Form->input("TestHasManyC.{$id}.test_has_many_a_id", array('type' => 'hidden','value' => $entC['test_has_many_a_id'],'label' => false,));

				echo "</li>\n";
			}
			echo "</ul></td>\n";
			echo "</tr>\n";

		}


		?>
		</tbory>
	</table>


	<?php
	echo $this->Form->submit('DB更新');
	echo $this->Form->end();
	?>
	

DBテーブル
test_has_many_as
idname
1 ネコX7
2 カエル

test_has_many_bs
idtest_has_many_a_idkind
1 1 ペルシャネコ
2 1 スフィンクス

test_has_many_cs
idtest_has_many_a_idnote
1 1 いろは
2 2 にほへと
3 2 ちりぬのを


アソシエーションの考え方 | hasOne,belongsTo,hasMany,hasAndBelongsToMany

関係英語和訳説明外部キー
hasOne 1 対 1 A has one Bの A(当モデル)は1つのB(外部モデル)を有する。 Aは1つ、Bも1つ。Aは兄、Bは弟という感じ。Bが兄であるAに配慮しているイメージ。 BにAへの外部キーあり
belongsTo 多 対 1 A belongs to X A(当モデル)はXに属します 1つのXに複数のAが結びつく。Aから見てXは親のようなものである。 A(当モデル)にXへの外部キーあり
hasMany 1 対 多 A has many C Aは多くのCを有してます Aは1つ、Cは複数である。Aから見てCは子のようなもの。 CにAへの外部キーあり
hasAndBelongsToMany 多 対 多 A has and belongs to many D Aは多くのDに属します AとDを連結した第3のテーブルあり。AとDは基本他人同士だが、会社(第3のテーブル)によって関係を結び付けられているイメージ。 Aは外部キーなし、Dも外部キーなし

補足資料

A hasOne B

A
ida_name
2那覇市
3国頭村

B
ida_idb_name
102カニ

那覇市は1つのカニを有してます。
国頭村は有してません。

A LEFT JOIN B


B belongsTo A

A
idb_ida_name
210那覇市
310国頭村

B
idb_name
10カニ
11ハゼ

カニは那覇市と国頭村に属する
ハゼはどこにも属さない

A LEFT JOIN B


A hasMany B

A
ida_name
2那覇市
3国頭村

B
ida_idb_name
102カニ
112ハゼ
123イカ
133エイ
那覇市はカニとハゼを持っている
国頭村はイカとエイを持っている

※ 重複で有することはできない。つまりカニは那覇市と国頭村の両方で持たせることはできない。多 対 多という関係になるため。

B LEFT JOIN A


A hasAndBelongsToMany B

A
ida_name
2那覇市
3国頭村

B
idb_name
10カニ
11ハゼ
12イカ
13エイ

AB
a_idb_id
210
211
310
312

那覇市はカニとハゼを持っている
国頭村はカニとイカを持っている
カニは那覇市と国頭村に属する
ハゼは那覇に属する
イカは国頭村に属する
エイはどこにも属さない

AB LEFT JOIN A
AB LEFT JOIN B