CakePHP2 で Model::saveAssociated を使って1回で複数のテーブルにデータを保存します♪

CakePHP2 を勉強してきて、1つのテーブルにデータを入れるには Model::save を使えばよいし、やってみた記録を残してくださっている方々のページも見つかって心強いのです。

けれどもじゃあ、表示したページのフォームにいろいろデータを入力して、登録!とかクリックしまして処理を走らせたときに、2つのテーブルに1度に綺麗にスマートにデータベースに Insert する方法は、意外と見つかりません。

ですので、やってみました。記録を残します。

ポイント

  • アソシエーション設定済みの複数モデルのテーブルに1度にデータを保存するには、Model::saveAssociated を使う。
  • ちなみに、Model::saveMany はひとつのモデルに複数行を登録するときに使う、らしい(未検証)。
  • ちなみに、Model::saveAll は Model::saveMany または、Model::saveAssociated が実行される。慣れないうちは明確に saveMany、saveAssociated を使い分けたほうが無難かも。

データベース

localhost - localhost - sampledb10 - phpMyAdmin 3.4.5.jpg

こんな感じに 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 が一致しています♪

CakePHP- the rapid development php framework- Users.jpg

おわりに

なんやかんやで本家のドキュメントが一番参考になりました。当たり前ですけれどもね。英語なのでとっつきにくいですけれども、CakePHP2 に慣れてきたせいか、少しずつわかるようになってきました。

ディスカッションに参加

2件のコメント

  1. 突然失礼致します。
    今回、cakePHPを使った制作をしており、悩んでいるのですが、
    唯一、本サイトが、私のやりたいことに似たことをしており、
    参考にさせていただいております。

    不躾で恐縮なのですが、
    下記URLの内容でなにかアドバイス頂戴できませんでしょうか?

    ご連絡お待ちしております。

    宜しくお願い致します。

    http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13130063598

コメントを残す

コメントを残す