【CakePHP】Model::create() の使い方と注意点

【CakePHP】Model::create() の使い方と注意点

去年のダイエットの成果が、今年 1/4 程戻ってしまった kimoto です。 よーし、また頑張るぞ!来年から!

さて、本日は CakePHP の小ネタ。
Model::create() を使う場合の注意点です。

Model::create() とは?

Model::create() は、一旦モデルの中身をリセットしてくれるメソッドです。

例えば、配列にデータを複数持ち、それをループで回して全て INSERT しようとした際に、ループ内で Model::save() をすると、2回目以降が UPDATE になってしまう、という事があります。
その場合、Model::save() の前に Model::create() を呼び出してやると、モデルの中身がリセットされ無事に全て INSERT される、という物です。

例えばこんな感じ。例では、DB にはプライマリーキーである id というフィールドがあり、それを指定しないことで INSERT をしようとしているとします。

// ユーザ配列
$users = array(
    array('user_id' => 'yamada', 'name' => '山田', 'age' => '20'),
    array('user_id' => 'suzuki', 'name' => '鈴木', 'age' => '25'),
    array('user_id' => 'sato',   'name' => '佐藤', 'age' => '40'),
);

foreach ($users as $user) {
    $this->User->save($user);
    // id の無いデータを save する事で INSERT をしたいのだが、
    // これだと2回目以降が UPDATE になってしまい、
    // できるデータは1つだけとなってしまう
}

foreach ($users as $user) {
    $this->User->create();
    $this->User->save($user);
    // 毎回 save の前に create するときちんと INSERT が発行され、データは3つできる
}

Model::create() をおまじない的に使ってはいけない

一度ループでのデータ保存でうまくいかなくて、その時にこの Model::create() に出会うと救世主のように思えてしまい、「Model::save() の前にはとりあえず Model::create() つけとけ!」みたいな思考に陥りがちです。ていうか以前陥りました。
が、おまじない的にとりあえず、で Model::create() を使ってしまうと、UPDATE したい場合に痛い目に会う事があるので注意が必要です。

それは、DB にはあるのに更新用のデータ配列に入ってないフィールドがある場合、DB からデフォルトの値を持ってきてセットしてしまうからです。

わかりづらい日本語ですみません、例えばこういうことです。
先ほどの user データにプレミアム会員である事を示す premium_flag というフィールドがあったとします。
そしてその値を変更できるのはサイト管理画面からのみで、ユーザ自身が触れるプロフィール編集画面からは変更できない値だとします。うんうん、よくありますよね、こういうの。
で、その premium_flag が DB 上でデフォルト false となっていた場合、むやみに Model::create をしてしまうとプレミアム会員でなくなってしまう事がある、という事です。

以下がユーザのプロフィール編集画面のコードだとします。このままでは、ユーザがプロフィールを編集保存すると、強制的にプレミアム会員ではなくなってしまいます。

// premium_flag のフィールドが無い配列
$users = array(
    'id'      => $id, 
    'user_id' => $user_id, 
    'name'    => $name, 
    'age'     => $age,
);

$this->User->create();
// この create() で premium_flag が DB デフォルトの false にセットされる

$this->User->save($user);
// premium_flag が false のまま UPDATE されてしまう!

事故を起こさないために

対策としては、「Model::save にちゃんと更新対象のフィールドである fieldList を渡してやる」というのがあります。とはいえ様々な事情からそれが難しい場合もあるかもしれません。

やはり、「そもそも INSERT を想定している場合のみ Model::create() する」というのが一番です。
ただ、INSERT と UPDATE を共通化している場合などもあるかと思います。その際は、プライマリーキーがあるかどうかを判断して、その結果により Model::create() をするようにすれば OK です。

先ほどのコードを例にすると、こんな感じにするといいかと。

$users = array(
    'id'      => $id, 
    'user_id' => $user_id, 
    'name'    => $name, 
    'age'     => $age,
);

if (!$user['id']) {
    // プライマリーキーである id がない場合は INSERT なので create する
    $this->User->create();
}

$this->User->save($user);
  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ