Project hasMany State なモデルにて、
新規に ProjectsController::add をした時、State の初期値を持たせたい。
- model::saveAll を使う
- バリデーションとトランザクションを同時に行うことが出来ます。
- State を保存する際、「project_id」が必要になりますが、アソシエーションを設定していれば、自動的にセットしてくれます。
下準備
データベーステーブル
-- -- テーブルの構造 `projects` -- CREATE TABLE IF NOT EXISTS `projects` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `description` text COLLATE utf8_unicode_ci NOT NULL, `type` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `license` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `user_id` int(11) NOT NULL, `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=6 ; -- -- テーブルの構造 `states` -- CREATE TABLE IF NOT EXISTS `states` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `hex` varchar(6) COLLATE utf8_unicode_ci NOT NULL, `position` int(11) NOT NULL, `type` int(1) NOT NULL, `project_id` int(11) NOT NULL, `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=41 ;
モデル
<?php // [app/app_model.php] class AppModel extends Model { public function setInitData(&$data, $options = array()) { $ids = array( // project.id を取得する、なければ、「null」 'project_id' => self::getProjectId(), // user.id を取得する、なければ、「null」 'user_id' => self::getUserId(), ); // スキーマになければ削除 foreach (array_keys($ids) as $n => $v) { if (!array_key_exists($v, $this->_schema)) unset($ids[$v]); } $default = array_merge($ids, $options); $data[$this->alias] = array_merge($data[$this->alias], $default); return $data; } } // [app/models/project.php] class Project extends AppModel { var $name = 'Project'; var $alias = 'Project'; /** * @var State */ var $State; var $validate = array( // 略 ); var $belongsTo = array( 'User' => array( 'className' => 'User', 'foreignKey' => 'user_id', 'conditions' => '', 'fields' => '', 'order' => '' ) ); var $hasMany = array( 'State' => array( 'className' => 'State', 'foreignKey' => 'project_id', 'dependent' => true, 'conditions' => '', 'fields' => '', 'order' => array('State.position', 'State.type', 'State.id'), 'limit' => '', 'offset' => '', 'exclusive' => '', 'finderQuery' => '', 'counterQuery' => '' ) ); public function setInitData(&$data, $options = array()) { parent::setInitData($data, $options); $this->State->setDefaultData($data); return $data; } } // [app/models/state.php] class State extends AppModel { var $name = 'State'; var $validate = array( // 略 ); var $order = array('State.position', 'State.type', 'State.id'); //The Associations below have been created with all possible keys, those that are not needed can be removed var $belongsTo = array( 'Project' => array( 'className' => 'Project', 'foreignKey' => 'project_id', 'conditions' => '', 'fields' => '', 'order' => '' ) ); public function setDefaultData(&$data) { $stateData = array( array('name' => 'new', 'hex' => 'ff1177', 'position' => '0', 'type' => '0'), array('name' => 'open', 'hex' => 'aaaaaa', 'position' => '1', 'type' => '0'), array('name' => 'hold', 'hex' => 'EEBB00', 'position' => '2', 'type' => '0'), array('name' => 'resolved', 'hex' => '66AA00', 'position' => '3', 'type' => '1'), array('name' => 'duplicate', 'hex' => 'AA3300', 'position' => '4', 'type' => '1'), array('name' => 'wont-fix', 'hex' => 'AA3300', 'position' => '5', 'type' => '1'), array('name' => 'invalid', 'hex' => 'AA3300', 'position' => '6', 'type' => '1'), ); $data['State'] = $stateData; return $data; } }
コントローラー
まずは、全体。
<?php // [app/controllers/projects_controller.php] class ProjectsController extends AppController { var $name = 'Projects'; /** * @var Project */ var $Project; function add() { if (!empty($this->data)) { $this->Project->create(); $this->Project->setInitData($this->data); if ($this->Project->saveAll($this->data)) { $this->Session->setFlash(__('The Project has been saved', true)); $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash(__('The Project could not be saved. Please, try again.', true)); } } $users = $this->Project->User->find('list'); $this->set(compact('users')); } }
解説
以下の値がPOSTされたとして解説します。
<?php $this->data = array('Project' => array( 'name' => 'name', 'description' => 'description', 'type' => 'type', 'license' => 'license', ));
<?php function add() { if (!empty($this->data)) { $this->Project->create();
新規保存する際のおまじないです。
Project モデル内の $this->id 、$this->data、$this->validationErrors を初期化します。
- $this->id はプライマリーキー の値です。これの有無で、INSERT か UPDATE を判別します。
- $this->data は、保存するデータの配列です。
- $this->validationErrors は、バリデートに失敗した際のエラーが記録されます。
<?php $this->Project->setInitData($this->data);
独自関数です。2つの役割があります。
(1) POSTされたデータにユーザIDを付加します。
$this->data['user_id'] = $this->Session->read('Auth.User.id') と同じ結果です。
- >
<?php $this->data = array( 'Project' => array( 'name' => 'name', 'description' => 'description', 'type' => 'type', 'license' => 'license', 'user_id' => '1', // これが追加されます。 ) );
(2) POSTされたデータにState の初期値を付加します。
- >
<?php $this->data = array( 'Project' => array( 'name' => 'name', 'description' => 'description', 'type' => 'type', 'license' => 'license', 'user_id' => '1', ), 'State' => array( // これらが追加されます。 array('name' => 'new', 'hex' => 'ff1177', 'position' => '0', 'type' => '0'), array('name' => 'open', 'hex' => 'aaaaaa', 'position' => '1', 'type' => '0'), array('name' => 'hold', 'hex' => 'EEBB00', 'position' => '2', 'type' => '0'), array('name' => 'resolved', 'hex' => '66AA00', 'position' => '3', 'type' => '1'), array('name' => 'duplicate', 'hex' => 'AA3300', 'position' => '4', 'type' => '1'), array('name' => 'wont-fix', 'hex' => 'AA3300', 'position' => '5', 'type' => '1'), array('name' => 'invalid', 'hex' => 'AA3300', 'position' => '6', 'type' => '1'), ) );
<?php if ($this->Project->saveAll($this->data)) { // 保存が成功 $this->Session->setFlash(__('The Project has been saved', true)); $this->redirect(array('action' => 'index')); } else { // 保存が失敗 $this->Session->setFlash(__('The Project could not be saved. Please, try again.', true)); }
saveAll の用法
cakebook と同じことを書いています。
http://book.cakephp.org/ja/view/1031/Saving-Your-Data
セットされたデータを使って、
(a) 単一のモデルに、個別のレコードを複数記録する。
(b) あるレコードと同様に、関連したレコードも全て記録する。
のどちらかを行います。
ここでは、(b)の用法で呼ばれ、boolean を返します。
(内部では、(a)の用法でも呼ばれています。)
saveAll(array $data = null, array $options = array())
$options は、内部では、save メソッドに引き継がれますが、
saveAll独自のパラメータとして、
array('validate' => 'first', 'atomic' => true)
の初期値が与えられます。
validate:
false をセットすると、バリデーションが行われません。
true をセットすると各レコードが保存される前にバリデーションが行われます。
'first' をセットすると、レコードの保存が行われる前に、全てのレコードにバリデーションが行われます。
'only' をセットすると、バリデートだけを行い、保存は行われません。atomic:
true(デフォルト)をセットすると、全てのレコードの保存を単一のトランザクションとして行うよう試みます。データベースやテーブルがトランザクションをサポートしていない場合は、false にセットするようにしましょう。
もし false にセットされているなら、渡した $data 配列に似た形式のものが、配列として返されます。ただし、値は各レコードが保存されたかどうかを表す true または false がセットされます。
ここでは、
第二引数になにも設定していないため、
- validate: 'first' をセットすると、レコードの保存が行われる前に、全てのレコードにバリデーションが行われます。
- atomic: true(デフォルト)をセットすると、全てのレコードの保存を単一のトランザクションとして行うよう試みます。
を行います。
保存を行う順番は、
'belongsTo' -> 自分自身のモデルデータ -> 'hasOne' -> 'hasMany'
です。
ここでは、
$data['Project'] の保存がまず試みられます。
次に、
$data['State'] の保存が試みられます。
先に、Project モデルにて保存が行われたため、挿入したIDが判明します。
(ここでは、2 とします)
よって、
$data['State'] のデータに、アソシエーションで設定した外部キー「foreignKey」('project_id')に判明したIDが付加されます。
<?php array('State' => array( // 「'project_id' => '2'」が追加されます。 array('name' => 'new', 'hex' => 'ff1177', 'position' => '0', 'type' => '0', 'project_id' => '2'), array('name' => 'open', 'hex' => 'aaaaaa', 'position' => '1', 'type' => '0', 'project_id' => '2'), array('name' => 'hold', 'hex' => 'EEBB00', 'position' => '2', 'type' => '0', 'project_id' => '2'), array('name' => 'resolved', 'hex' => '66AA00', 'position' => '3', 'type' => '1', 'project_id' => '2'), array('name' => 'duplicate', 'hex' => 'AA3300', 'position' => '4', 'type' => '1', 'project_id' => '2'), array('name' => 'wont-fix', 'hex' => 'AA3300', 'position' => '5', 'type' => '1', 'project_id' => '2'), array('name' => 'invalid', 'hex' => 'AA3300', 'position' => '6', 'type' => '1', 'project_id' => '2'), ))
また、
上記のデータ形式は、
saveAll の用法 (a) の「単一のモデルに、個別のレコードを複数記録する。」に該当するため、
State モデル の saveAll を内部で呼びます。
(save メソッドをループで回した回数分呼び出します。)
State::saveAll の結果と validationErrors プロパティが空であることを確認して、
最終的に、boolean を返します。
保存したモデルの結果です。
(project_id など細かい部分は異なります)
array
'Project' =>
array
'id' => string '6' (length=1)
'name' => string 'Name' (length=4)
'description' => string 'Description' (length=11)
'type' => string 'Type' (length=4)
'license' => string 'License' (length=7)
'user_id' => string '41' (length=2)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)
'User' =>
array
'id' => string '41' (length=2)
'created' => string '2010-06-13 16:19:57' (length=19)
'modified' => string '2010-06-13 16:19:57' (length=19)
'username' => string 'aaaa' (length=4)
'password' => string '886cda4198f50ec0167c8eb38edf7036c838c3aa' (length=40)
'State' =>
array
0 =>
array
'id' => string '41' (length=2)
'name' => string 'new' (length=3)
'hex' => string 'ff1177' (length=6)
'position' => string '0' (length=1)
'type' => string '0' (length=1)
'project_id' => string '6' (length=1)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)
1 =>
array
'id' => string '42' (length=2)
'name' => string 'open' (length=4)
'hex' => string 'aaaaaa' (length=6)
'position' => string '1' (length=1)
'type' => string '0' (length=1)
'project_id' => string '6' (length=1)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)
2 =>
array
'id' => string '43' (length=2)
'name' => string 'hold' (length=4)
'hex' => string 'EEBB00' (length=6)
'position' => string '2' (length=1)
'type' => string '0' (length=1)
'project_id' => string '6' (length=1)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)
3 =>
array
'id' => string '44' (length=2)
'name' => string 'resolved' (length=8)
'hex' => string '66AA00' (length=6)
'position' => string '3' (length=1)
'type' => string '1' (length=1)
'project_id' => string '6' (length=1)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)
4 =>
array
'id' => string '45' (length=2)
'name' => string 'duplicate' (length=9)
'hex' => string 'AA3300' (length=6)
'position' => string '4' (length=1)
'type' => string '1' (length=1)
'project_id' => string '6' (length=1)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)
5 =>
array
'id' => string '46' (length=2)
'name' => string 'wont-fix' (length=8)
'hex' => string 'AA3300' (length=6)
'position' => string '5' (length=1)
'type' => string '1' (length=1)
'project_id' => string '6' (length=1)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)
6 =>
array
'id' => string '47' (length=2)
'name' => string 'invalid' (length=7)
'hex' => string 'AA3300' (length=6)
'position' => string '6' (length=1)
'type' => string '1' (length=1)
'project_id' => string '6' (length=1)
'created' => string '2010-06-23 00:35:17' (length=19)
'modified' => string '2010-06-23 00:35:17' (length=19)