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