CakePHPのモデル内でループ中にsaveメソッドを繰り返すとcreatedとmodifiedが”0000-00-00 00:00:00″になる件

しばらくぶりに CakePHP のおはなし。

日時が入らない

複数のデータを INSERT しようと思って
モデルのループ内で save メソッドを繰り返すと
2週目からはモデルが同じ id をひきずることになり
INSERT じゃなくて UPDATE になってしまうので
save の前に create をしましょうね、というのは
以前ここに書いて教えてもらったとおり。

ところがこれをやるともうひとつ困ったことが起きる。

CakePHP で save メソッドを使うと
データベースのテーブルの `created` フィールドに挿入の日時、
`modified` フィールドに更新の日時が自動で入るはずですね。

ところが前述の create メソッドを使うと
これらがいずれも ‘0000-00-00 00:00:00’ になってしまう。
要するに DATETIME 型の空の値ですか。
(「空の値」という表現が適当でなかったら教えてください。)

原因

The Cookbook によると、

Saving Your Data :: Models :: Developing with CakePHP :: The Manual :: 1.2 Collection :: The Cookbook

create(array $data = array())

This method resets the model state for saving new information.

If the $data parameter (using the array format outlined above) is passed, the model instance will be ready to save with that data (accessible at $this->data).

create メソッドはモデルの状態をリセットして新しい情報を save できるようにするよ。
$data パラメータを渡すと、モデルのインスタンスはそのデータを save するようにセットするよ。セットされたデータには $this->data でアクセスできるよ。

(英語版を持ってくる理由は後述。薄字部分は引用者超訳。)

とあり、create メソッドに引数としてデータの配列を渡すと
その配列が save() の対象としてセットされることになっている。

これのデフォルト値は array() なので
何も渡さなかったら何もセットされない。
だから `created` と `modified` が空の値になったのかな。

でも、create() をしなかった場合でも
モデルには何もセットされないけど、
ちゃんと `created` と `modified` には日時が入る。
これはどうしたことか。

というわけで試しにループ内で create() をやって
データがセットされている $this->data を確認してみた。

$this->create();
debug($this->data);

これで原因がわかった。

モデルのフィールド名はまあ適当に流してもらうとして、
こんな値がセットされてた。

Array
(
	[User] => Array
	(
		[group_id] => 0
		[created] => 0000-00-00 00:00:00
		[modified] => 0000-00-00 00:00:00
	)
)

これ、おそらくモデルの _schema プロパティに入ってる
各フィールドのデフォルト値がセットされてる。

文字列が入るフィールドなんかはデフォルトが空なので
何もセットされないんだけど
例えば integer は 0 が、
datetime は 0000-00-00 がデフォルト値。

これは困った。
もちろんこちらで created と modified をセットすればいけるけど
そんなのめんどくさい。
自動でやってくれるのがいいところなのに。

解決方法

この記事を書いている時点で
日本語版のには載ってないんだけど、
英語版の The Cookbook にはこんな記述があった。

Saving Your Data :: Models :: Developing with CakePHP :: The Manual :: 1.2 Collection :: The Cookbook

If false is passed instead of an array, the model instance will not initialize fields from the model schema that are not already set, it will only reset fields that have already been set, and leave the rest unset. Use this to avoid updating fields in the database that were already set and are intended to be updated.

配列じゃなくて false を渡すと、モデルのインスタンスはモデルのスキーマにあるフィールドを初期化しないよ。セットされてるフィールドだけをリセットして、それ以外のは未設定の状態にするよ。

どうもここの表現がわかりにくかったんだけど、
ものすごくかいつまんで言うと
「false を渡せば何も設定しない」ということでいいですか。

ソースを見ればもっとわかりやすいか。

cake/libs/model/model.php

function create($data = array(), $filterKey = false) {
	$defaults = array();
	$this->id = false;
	$this->data = array();
	$this->__exists = null;
	$this->validationErrors = array();
	if ($data !== null && $data !== false) {
		foreach ($this->schema() as $field => $properties) {
			if ($this->primaryKey !== $field && isset($properties['default'])) {
				$defaults[$field] = $properties['default'];
			}
		}
		$this->set(Set::filter($defaults));
		$this->set($data);
	}
	if ($filterKey) {
		$this->set($this->primaryKey, false);
	}
	return $this->data;
}

$data が null か false のときは、
id も data も validationErrors も空にして
それ以外何もせずに返してる。

というわけで、解決方法としては

$this->create();

としてたところを

$this->create(false);

でいいですか。

何か違うよとかもっとスマートな方法あるよっていう方は
ぜひ教えてください。

関連エントリ

  • このエントリーをはてなブックマークに追加