CakePHP2 を勉強してきて、1つのテーブルにデータを入れるには Model::save を使えばよいし、やってみた記録を残してくださっている方々のページも見つかって心強いのです。
けれどもじゃあ、表示したページのフォームにいろいろデータを入力して、登録!とかクリックしまして処理を走らせたときに、2つのテーブルに1度に綺麗にスマートにデータベースに Insert する方法は、意外と見つかりません。
ですので、やってみました。記録を残します。
ポイント
- アソシエーション設定済みの複数モデルのテーブルに1度にデータを保存するには、Model::saveAssociated を使う。
- ちなみに、Model::saveMany はひとつのモデルに複数行を登録するときに使う、らしい(未検証)。
- ちなみに、Model::saveAll は Model::saveMany または、Model::saveAssociated が実行される。慣れないうちは明確に saveMany、saveAssociated を使い分けたほうが無難かも。
データベース
こんな感じに users テーブル、ninjas テーブル、geisyas テーブルでやってみました。一応実際にやりそうなシチュエーションを想定しておりまして、
- users にログイン情報のユーザ名、パスワード
- ninjas、geisyas にニンジャ、ゲイシャの各パラメータ情報
を入れます。
つまり、ニンジャ、ゲイシャが同じシステムにユーザ登録するけれども、入力する情報はそれぞれ異なる、シチュエーションですね。
そして SQL はこちらです。データベースを作るところからはじめましょう。
# データベース、ユーザ、の作成。パスワードの設定 GRANT ALL PRIVILEGES ON sampledb10.* TO sampleuser10@localhost IDENTIFIED BY 'samplepassword10'; FLUSH PRIVILEGES; CREATE DATABASE sampledb10 CHARACTER SET utf8; # テーブル作成 CREATE TABLE users ( id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, password CHAR(40) NOT NULL, created DATETIME, modified DATETIME ); CREATE TABLE ninjas ( id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id int(11) NOT NULL, jitsu text, tool text, created DATETIME, modified DATETIME ); CREATE TABLE geisyas ( id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id int(11) NOT NULL, oiroke text, face text, created DATETIME, modified DATETIME );
ソース
まずは一覧です。
- app/Controller/UsersController.php
 index アクション
 add_user_ninja アクション
 add_user_geisya アクション
 _addAssociated 関数
- app/Model/User.php
 アソシエーション User hasOne Geisya
 アソシエーション User hasOne Ninja
- app/View/Users/index.ctp
 登録データの一覧を見るページ
- app/View/Users/add_user_ninja.ctp
 ニンジャ登録用ページ
- app/View/Users/add_user_geisya.ctp
 ゲイシャ登録用ページ
app/Controller/UsersController.php
<?php
App::uses('AppController', 'Controller');
/**
 * Users Controller
 *
 * @property User $User
 */
class UsersController extends AppController {
/**
 * index method
 *
 * @return void
 */
	public function index() {
		$this->User->recursive = 0;
		$this->set('users', $this->paginate());
	}
/**
 * add_user_ninja method
 *
 * @return void
 */
	public function add_user_ninja() {
		$this->_addAssociated();
	}
/**
 * add_user_geisya method
 *
 * @return void
 */
	public function add_user_geisya() {
		$this->_addAssociated();
	}
/**
 * _addAssociated method
 *
 * @return void
 */
	private function _addAssociated() {
		if ($this->request->is('post')) {
			$this->User->create();
			$this->log($this->request->data, 'debug');
			$result = $this->User->saveAssociated($this->request->data);
			$this->log($result, 'debug');
			if ($result) {
				$this->Session->setFlash(__('The user has been saved'));
				$this->redirect(array('action' => 'index'));
			} else {
				$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
			}
		}
	}
}
$this->User->saveAssociated($this->request->data); を使っているだけですね。コントローラでは特別なことはしていません。
むしろ注目したいのは、ニンジャの登録も、ゲイシャの登録も、まったく同じロジック、プログラムを使っているという点です。コーディング量が減って、開発が楽になりますね♪
app/Model/User.php
<?php
App::uses('AppModel', 'Model');
/**
 * User Model
 *
 * @property Geisya $Geisya
 * @property Ninja $Ninja
 */
class User extends AppModel {
/**
 * Validation rules
 *
 * @var array
 */
	public $validate = array(
		'username' => array(
			'notempty' => array(
				'rule' => array('notempty'),
				//'message' => 'Your custom message here',
				//'allowEmpty' => false,
				//'required' => false,
				//'last' => false, // Stop validation after this rule
				//'on' => 'create', // Limit validation to 'create' or 'update' operations
			),
			'isUnique' => array(
				'rule' => array('isUnique'),
			),
		),
		'password' => array(
			'notempty' => array(
				'rule' => array('notempty'),
				//'message' => 'Your custom message here',
				//'allowEmpty' => false,
				//'required' => false,
				//'last' => false, // Stop validation after this rule
				//'on' => 'create', // Limit validation to 'create' or 'update' operations
			),
		),
	);
	//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
 * hasOne associations
 *
 * @var array
 */
	public $hasOne = array(
		'Geisya' => array(
			'className' => 'Geisya',
			'foreignKey' => 'user_id',
			'conditions' => '',
			'fields' => '',
			'order' => '',
			'dependent' => false
		),
		'Ninja' => array(
			'className' => 'Ninja',
			'foreignKey' => 'user_id',
			'conditions' => '',
			'fields' => '',
			'order' => '',
			'dependent' => false
		)
	);
}
コントローラで saveAssociated を使いましたが、どのテーブルに同時に保存するかはモデルのアソシエーション設定で決まります。$hasOne フィールドですね。
app/View/Users/index.ctp
<div class="users index">
	<h2><?php echo __('Users'); ?></h2>
	<table cellpadding="0" cellspacing="0">
	<tr>
			<th><?php echo $this->Paginator->sort('User.id' ,'ユーザID'); ?></th>
			<th><?php echo $this->Paginator->sort('User.username' ,'ユーザネーム'); ?></th>
			<th><?php echo $this->Paginator->sort('User.password' ,'ユーザパスワード'); ?></th>
			<th><?php echo $this->Paginator->sort('Ninja.id' ,'ニンジャID'); ?></th>
			<th><?php echo $this->Paginator->sort('Ninja.user_id' ,'ニンジャ・ユーザID'); ?></th>
			<th><?php echo $this->Paginator->sort('Ninja.jitsu' ,'ニンジャジツ'); ?></th>
			<th><?php echo $this->Paginator->sort('Ninja.tool' ,'ニンジャ便利ツール'); ?></th>
			<th><?php echo $this->Paginator->sort('Geisya.id' ,'ゲイシャID'); ?></th>
			<th><?php echo $this->Paginator->sort('Geisya.user_id' ,'ゲイシャ・ユーザID'); ?></th>
			<th><?php echo $this->Paginator->sort('Geisya.oiroke' ,'ゲイシャオイロケ'); ?></th>
			<th><?php echo $this->Paginator->sort('Geisya.face' ,'ゲイシャ化粧'); ?></th>
			<th><?php echo $this->Paginator->sort('User.created'); ?></th>
			<th><?php echo $this->Paginator->sort('User.modified'); ?></th>
			<th><?php echo $this->Paginator->sort('Ninja.created'); ?></th>
			<th><?php echo $this->Paginator->sort('Ninja.modified'); ?></th>
			<th><?php echo $this->Paginator->sort('Geisya.created'); ?></th>
			<th><?php echo $this->Paginator->sort('Geisya.modified'); ?></th>
		</tr>
	<?php
	foreach ($users as $user): ?>
	<tr>
		<td><?php echo h($user['User']['id']); ?> </td>
		<td><?php echo h($user['User']['username']); ?> </td>
		<td><?php echo h($user['User']['password']); ?> </td>
		<td><?php echo h($user['Ninja']['id']); ?> </td>
		<td><?php echo h($user['Ninja']['user_id']); ?> </td>
		<td><?php echo h($user['Ninja']['jitsu']); ?> </td>
		<td><?php echo h($user['Ninja']['tool']); ?> </td>
		<td><?php echo h($user['Geisya']['id']); ?> </td>
		<td><?php echo h($user['Geisya']['user_id']); ?> </td>
		<td><?php echo h($user['Geisya']['oiroke']); ?> </td>
		<td><?php echo h($user['Geisya']['face']); ?> </td>
		<td><?php echo h($user['User']['created']); ?> </td>
		<td><?php echo h($user['User']['modified']); ?> </td>
		<td><?php echo h($user['Ninja']['created']); ?> </td>
		<td><?php echo h($user['Ninja']['modified']); ?> </td>
		<td><?php echo h($user['Geisya']['created']); ?> </td>
		<td><?php echo h($user['Geisya']['modified']); ?> </td>
	</tr>
<?php endforeach; ?>
	</table>
	<p>
	<?php
	echo $this->Paginator->counter(array(
	'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
	));
	?>	</p>
	<div class="paging">
	<?php
		echo $this->Paginator->prev('< ' . __('previous'), array(), null, array('class' => 'prev disabled'));
		echo $this->Paginator->numbers(array('separator' => ''));
		echo $this->Paginator->next(__('next') . ' >', array(), null, array('class' => 'next disabled'));
	?>
	</div>
</div>
<div class="actions">
	<h3><?php echo __('Actions!イヨォー!!!'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('New Geisya'), array('controller' => 'users', 'action' => 'add_user_geisya')); ?> </li>
		<li><?php echo $this->Html->link(__('New Ninja'), array('controller' => 'users', 'action' => 'add_user_ninja')); ?> </li>
	</ul>
</div>
app/View/Users/add_user_ninja.ctp
<div class="users form">
<?php echo $this->Form->create('User', array('action' => 'add_user_ninja')); ?>
	<fieldset>
		<legend><?php echo __('ログイン用 User レコードと Ninja の内容を同時に登録'); ?></legend>
	<?php
		echo $this->Form->input('User.username', array('label' => 'User モデルの username フィールド'));
		echo $this->Form->input('User.password', array('label' => 'User モデルの password フィールド'));
		echo $this->Form->input('Ninja.jitsu', array('label' => 'Ninja モデルの jitsu フィールド'));
		echo $this->Form->input('Ninja.tool', array('label' => 'Ninja モデルの tool フィールド'));
		?>
	</fieldset>
<?php echo $this->Form->end(__('登録')); ?>
</div>
ビューでの特徴は、モデルとそのフィールドを、複数モデル分記述していることでしょうか、次のビューでも同じですが、User.username と Ninja.jitsu などと複数モデルのフィールドを書いています。
app/View/Users/add_user_geisya.ctp
<div class="users form">
<?php echo $this->Form->create('User', array('action' => 'add_user_geisya')); ?>
	<fieldset>
		<legend><?php echo __('ログイン用 User レコードと geisya の内容を同時に登録'); ?></legend>
	<?php
		echo $this->Form->input('User.username', array('label' => 'User モデルの username フィールド'));
		echo $this->Form->input('User.password', array('label' => 'User モデルの password フィールド'));
		echo $this->Form->input('Geisya.oiroke', array('label' => 'geisya モデルの oiroke フィールド'));
		echo $this->Form->input('Geisya.face', array('label' => 'geisya モデルの face フィールド'));
		?>
	</fieldset>
<?php echo $this->Form->end(__('登録')); ?>
</div>
動き
適当に登録してみました。その結果を確認してみますと、いい感じです。
users.id と ninjas.user_id または geisyas.user_id が一致しています♪
おわりに
なんやかんやで本家のドキュメントが一番参考になりました。当たり前ですけれどもね。英語なのでとっつきにくいですけれども、CakePHP2 に慣れてきたせいか、少しずつわかるようになってきました。



「CakePHP2 で Model::saveAssociated を使って1回で複数のテーブルにデータを保存します♪」への2件の返信
[…] http://d.hatena.ne.jp/g2_girichan/20090327/1238154524 http://d.hatena.ne.jp/sutara_lumpur/20100702/1278029850 http://arkmemo.blogspot.jp/2012/10/cakephpsaveall.html https://oki2a24.com/2012/08/15/how-to-save-multiple-model-associations-at-once-with-model-saveassocia… […]
突然失礼致します。
今回、cakePHPを使った制作をしており、悩んでいるのですが、
唯一、本サイトが、私のやりたいことに似たことをしており、
参考にさせていただいております。
不躾で恐縮なのですが、
下記URLの内容でなにかアドバイス頂戴できませんでしょうか?
ご連絡お待ちしております。
宜しくお願い致します。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13130063598