CakePHP3を触ってみました 〜createもsetもできないんですけど?〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
小麦粉ってすげー美味いんだなww

おまとめ三行

ポテトがついてこなかったくらいの衝撃ですよ奥さん!
この時代遅れのロートル野郎が!
便利かもしれないなーと思いました

CakePHP2を使ってDBにデータを登録するとき、create()やset()というメソッドを使って、モデルにデータをセットすることができます。save()メソッドを使っている場所には、たいていこれらのメソッドも一緒にいると思います。ハンバーガーと一緒についてくるポテトのように。

これらは当たり前のように使っていたメソッドだから、CakePHP3になっても当たり前のように使えば良いんだろうと思ったのに……まさか二つともなくなっているなんて! バリューセットを頼んだのにポテトがついてこなかったくらいの衝撃ですよ奥さん!

何かね、CakePHP2の頃のソースをコピって使おうとしたら「もうcreate()やset()の時代は終わったんだよ。いつまでも過去にしがみついてんじゃねえ。この時代遅れのロートル野郎が!」って感じのエラーが出ました。うっかりではなく、意図的にポテトをつけてもらえなかったかのように。

ポテトがついて来なかったらクレームの一つもつけたいところですが、create()やset()に関しては代わりのメソッドがちゃんとあるので、CakePHP3の開発者に文句を言う前に、それ使ってみましょう。

それにしても……今回グラコロに関するとある画像を拝借してきましたが、この画像を初めて見たときは笑った。小麦粉ちょーうめーじゃんww

最後にマック行ったの、もう何年前かなぁ……昔はてりたまやグラコロの時期が来たら、必ずと言って良いほど行ってたのに。



create()の代わり

create()メソッドを使うのは、データを新規に登録するときですね。

CakePHP3では、新規登録時にはnewEntity()というメソッドを使って、まずエンティティを作成します。

例えば、postsテーブルにデータを登録する場合。

//CakePHP2
$this->Post->create($this->request->data);
$this->Post->save();

//CakePHP3
$post = $this->Posts->newEntity($this->request->data);
$this->Posts->save($post);

ここでは、$this->Postsにすでにインスタンスが生成されていると仮定してください。TableRegistryを使って新たにインスタンスを作成するとこは省略。

create()がnewEntity()に変わっているところもそうですが、その後のsave()メソッドも、CakePHP2のときとは少し仕様が変わってます。

CakePHP3では、エンティティデータを渡すのが必須のようです。

CakePHP2の場合、create()した時点でモデル側にデータが渡っているので、save()のときにデータを渡さなくてもエラーにはなりませんでした。save()メソッドを呼び出したとき、すでにモデルにセットされているデータを使ってINSERTしてくれるからです。

でもCakePHP3の方は、save()のときにデータを渡さないとエラーになる。newEntity()ってのは、読んで字のごとく、エンティティを新しく作成するためのメソッドらしいんですね。だから従来のように、モデル……CakePHP3の場合はテーブルですが、テーブルにデータがセットされるような動きはしないっぽいです。

CakePHP3では、POSTされたデータを元にエンティティを作成して、それをsave()メソッドのときにテーブルに渡すってのが、一連の流れみたいですね。



set()の代わり

厳密にset()の代わりと言えるのかは分かりませんが、CakePHP3では、データの編集時にはpatchEntity()というメソッドを使います。newEntity()でも編集できなくはないけど、patchEntity()を使った方が良い。何でかってのは、ここでは置いときましょう。まあ、CakePHP2で編集時にcreate()を使わない方が良いのと、似たようなもんだと思います。

//CakePHP2
$this->Post->set($this->request->data);
$this->Post->save();

//CakePHP3
$data = $this->find()->first();
$post = $this->Posts->patchEntity($data, $this->request->data);
$this->Posts->save($post);

こちらの場合も、やはりsave()メソッドで引数を省略するとエラーになります。

それからnewEntity()のときと違い、patchEntity()の場合は、POSTされたデータと一緒に、既存のデータも渡してあげる必要があります。上記の例では適当に一件データを取得していますが、更新したいデータをDBから取って来て一緒に渡すことで、既存のデータとPOSTされたデータを元に、上手いこと登録用のデータを作ってくれます。

patchEntity()を使用した場合、変更前のデータと変更後のデータの両方がエンティティに入ってきます。

例えば、idが1の既存データとPOSTされたデータが以下のようになっているとしましょう。

//idが1のデータ
array(
  [id] => 1
  [title] => テスト
  [text] => こんにちは
)

//POSTされたデータ
array(
  [title] => テスト
  [text] => hello world
)

この場合、textフィールドの中身が変更されることになりますね。titleはそのまま。

このデータを使ってpatchEntity()でエンティティを作成すると、中身がこんな感じになります。

Object(
  [_properties:protected] => Array(
    [id] => 1
    [title] => テスト
    [text] => hello world
  )
  [_original:protected] => Array(
    [text] => こんにちは
  )
  [_dirty:protected] => Array(
    [text] => 1
  )
)

作成されたエンティティの中身を見てみると、実際はもう少しいろんな項目があるんですが、今は省略。

POSTされたデータと元のデータが違うと、「_original」に変更前のデータが入り、「_dirty」の方に変更フラグみたいなのが入ります。これのおかげで、変更前のデータや変更フラグの取得も簡単に行えるので、エラーチェックなどに活用できそうです。

$data = $this->find()->where(['id' => 1])->first();
$post = $this->Posts->patchEntity($data, $this->request->data);

//変更前のデータを取得
$post->getOriginal('text');

//変更フラグの取得
$post->dirty('text');

他にもいろいろできることはありそうなんですが、正直、まだほとんど使ったことないので、今日はパスで。

ちなみに、たぶんなんですけど、変更されたデータが何もない場合、dirtyの中身が空になるような場合は、save()メソッドを使っても、データが更新されないっぽいです。僕がやってみた限り、データ更新時の時間を自動で登録するカラム(僕はmodifiedにしてます)の値が変わってなかった。



エラーチェックについて

newEntity()やpatchEntity()は、エンティティを作成するだけではなくて、エラーチェック……バリデーションも同時にやってくれます。それもまた、create()やset()と違うところですね。CakePHP2では、validates()というメソッドで、エラーチェックをしていました。

//CakePHP2
$this->Post->create($this->request->data);
if($this->Post->validates()) {
  $this->Post->save();
}

//CakePHP3
$post = $this->Posts->newEntity($this->request->data);
if(!$post->errors()) {
  $this->Posts->save($post);
}

エラーがあると、エンティティデータの中にエラーメッセージが入ります。これはpatchEntity()も一緒。

//POSTされたデータ
array(
  [title] => 
  [text] => hello world
)

//$postエンティティ
Object(
  [_properties:protected] => Array(
    [text] => hello world
  )
  [_dirty:protected] => Array(
    [text] => 1
  )
  [_errors:protected] => Array(
    [title] => Array(
      [_empty] => 必須入力です
    )
  )
)

ここでは、titleフィールドが空の値を許可しないようになっているという想定です。エラーがあると、[_errors]にエラーメッセージが入ります。



ビューでエラーメッセージを表示する方法も、CakePHP2と3ではちょっと変わったっぽいです。

さっきと同様に、タイトルが空の値だった場合に「必須入力です」というメッセージが出るように設定されていて、実際に空の値がPOSTされたとしましょう。

まずはCakePHP2の場合。

//コントローラー
$this->Post->create($this->request->data);
if($this->Post->validates()) {
  $this->Post->save();
}

//ビュー
pr($this->validationErrors['Post']);

//出力結果
Array(
  [title] => Array(
    [0] => 必須入力です
  )
)

続いてCakePHP3。

//コントローラー
$post = $this->Posts->newEntity($this->request->data);
if(!$post->errors()) {
  $this->Posts->save($post);
} else {
  $this->set('errors', $post->errors());
}

//ビュー
pr($errors);

//出力結果
Array(
  [title] => Array(
    [_empty] => 必須入力です
  )
)

CakePHP2の場合は、validates()メソッドを使うと、自動的にビューの「validationErrors」という変数にエラーメッセージをセットしてくれます。

でもCakePHP3の場合は、たぶんですが自動的にビューに渡してはくれないっぽいので、自分でセットしてあげる必要があります。上記のように「$post->errors()」でエラーメッセージを取得できるので、それを自分でビューに渡せばオッケーです。

エラーがなければ「$post->errors()」の中身は空なので、それを判定材料にしてsave()メソッドにつなげると良いと思います。



ちなみに、もしnewEntity()やpatchEntity()でエラーチェックを行いたくない場合。

$post = $this->Posts->newEntity($this->request->data, ['validate' => false]);
$post = $this->Posts->patchEntity($data, $this->request->data, ['validate' => false]);

こんな感じでvalidateをfalseにすると、エラーチェックを行いません。






おおむねこんなところでしょうか。エラーメッセージをエンティティからいつでも取り出せるってのは、便利かもしれないなーと思いました。僕の経験上では、コントローラーやシェルでもエラーメッセージを使いたいことって、たまーにあるのよね。そんなときは、こっちの方が使いやすそうです。

バリデーションの設定については具体的なことを何も書きませんでしたが、それはまたいずれやります。たぶん。

ぶっちゃけ、バリデーションの設定が今んとこ一番苦労してます。何かね……思うようにやりたいことができないというか、とにかく、言いたいことがいろいろあるのですよ。ここでは書ききれぬ。



その他のCakePHP3を触ってみましたの記事はこちら
まとめという名の箸休め

まだコメントはいただけてないみたい……
もしかしたら何か関連しているかも?