CakePHP3を触ってみました 〜キミは鎖につながれた生き方を選ぶか?〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
瞬のネビュラチェーン

この記事を三行にまとめると

find()、update()、delete()を使ってみたいと思います
聖闘士星矢も、瞬よりお兄さんの一輝の方が好きだし?
鎖につながれた家畜のような扱いを受けるつもりはねえ
SQLの基本的な四つの構文(INSERT、SELECT、UPDATE、DELETE)をまとめて「CRUD」と言うらしいんですが、今日はそのCRUDの内のRUDに該当する、find()、update()、delete()を使ってみたいと思います。

もっとも、そんな複雑なことはやらないです。基本的な使い方だけさら〜っと見ていきましょう。

ちなみに何でINSERTだけやらないのかってーと、それだけちょっと長くなりそうな予感がしたので、次回に回すことにしました。



find()

CakePHP2の頃と、ある意味では大きく変わったんじゃないかと思われる、find()メソッド。

CakePHP2の頃は、こうでした。

$data = $this->Post->find('all');

これと同じことをCakePHP3でやろうとすると、こうなります。

$data = $this->Posts->find()->all();

CakePHP3では、find()メソッドはSELECT文の発行ではなく、クエリーオブジェクトを生成するメソッドのようです。「$this->Posts->find()」でオブジェクトが生成され、あとは必要な条件をいろいろとセットした後にall()メソッドでSQLを実行すると、そんな感じです。

その条件のセットの仕方も、CakePHP2とは変わりました。

$params = array(
  'conditions' => array('year' => '2015'),
  'fields' => array('id', 'title'),
  'order' => array('id' => 'ASC'),
  'limit' => 10,
  'contain' => array('Tag'),
);
$data = $this->Post->find('all', $params);

CakePHP2だと、例えばこんな書き方になりますね。これは、2015年に作成されたデータをidの若い順に10件取得する、みたいなものだと思ってください。yearというカラムに作成した年が入っているものとします。ついでに紐づくタグを、アソシエーションによって取得しています。

これと同じことをCakePHP3でやると、こんな風になります。

$data = $this->Posts->find()
        ->where(['year' => '2015'])
        ->select(['id', 'title'])
        ->order(['id' => 'ASC'])
        ->limit(10)
        ->contain(['Tag'])
        ->all();

こういうのをメソッドチェーンとか言うらしいですが、CakePHP3ではwhereやらlimitやらを、それぞれに対応するメソッドで追加していきます。conditionsやfieldsに対応するものが、それぞれwhere()とselect()に変わっていますが、SQLのWHERE句をwhere()に入れれば良いって考えると、こっちの方が直感的に分かりやすいかもしれませんね。

conditionsやfieldsも、使えなくなったわけではないです。どうしても使いたいって言うのであれば、こんな書き方もできる。

$data = $this->Posts->find()
        ->applyOptions([
          'conditions' => ['year' => '2015'],
          'fields' => ['id', 'title'],
        ])
        ->order(['id' => 'ASC'])
        ->limit(10)
        ->contain(['Tag'])
        ->all();

「applyOptions()」っていうメソッドを使えば、CakePHP2と同じような書き方ができます。今回はconditionsとfieldsだけ中に入れましたが、orderとかlimitとか、他のもapplyOptionsの中に書くことは可能です。groupとかoffsetなんかも同様です。

条件は、後から追加することも可能です。

$data = $this->Posts->find()
         ->where(['year' => '2015'])
         ->where(['month' => '12'])
         ->all();

これだと、先に書いたyearの方が上書きされるわけではなく、「WHERE year = ‘2015’ AND month = ’12’」になります。

また、find(‘all’)がfind()->all()に変わったように、find(‘first’)やfind(‘count’)も、first()やcount()によって実行できます。

$data = $this->Posts->find()->first();
$data = $this->Posts->find()->count();

ちなみに、無理にメソッドチェーンで一本につながなくても良いです。僕はつながない方が見慣れてるから、↓のように書くことが多いかも。

$query = $this->Posts->find();
$query->where(['year' => '2015']);
$query->select(['id', 'title']);
$query->order(['id' => 'ASC']);
$query->limit(10);
$query->contain(['Tag']);
$data = $query->all();

ほら、俺って根っからの風来坊だからさ。鎖につながれた人生なんて、まっぴら御免なわけよ。だからチェーンとか苦手なわけよ。自転車もシャフトドライブっていう、チェーンついてないやつ乗ってたし。聖闘士星矢も、瞬よりお兄さんの一輝の方が好きだし?

$dataに入って来る値は同じなので、どっちの書き方にするかは、その人の好みによる……のかな? コーディング規約でどっちかの書き方に決まってたりとかすれば、従うしかないんだろうけど。

ただまあ、実際に自分で検証したわけではないのですが、いろんな人の情報を参考にさせていただく限り、メソッドチェーンにした方が、パフォーマンスは悪くなるみたいです。戻り値を使うために一時変数の確保がどうたらこうたらってことらしいんですが、詳しいことはよく分かりません。

ともあれ、書き方に特にこだわりや縛りがないのであれば、メソッドチェーンはやめておいた方が無難なのかもしれません。我々プログラマーは、たとえ社畜に成り果てようとも、鎖につながれた家畜にはならねえぞっていう、そんな強い意志でコードを書いた方が良いってことですね。ミスターアンチェインの名の下に、チェーンとはおさらばしたコーディング人生を送る方が良いってことですね。



ちなみにcontainについてですが、CakePHP2でcontainを使うには、ContainableというBehaviorを読み込む必要がありました。でもCakePHP3では特に何かを読み込む必要はないです。ていうか、ContainableBehavior自体、なくなったっぽい。

CakePHP2のContainableに使い方については、以前にちょこっと触れたことがあるので、もし参考になるようならば。

CakePHPでLEFT以外もJOINしたい

この記事の中に「Containableについて」って項目があります。下の方だけど。



update()

save()メソッドを使った場合でもUPDATE文を発行することができますが、CakePHP3では、まんまupdate()というメソッドも存在します。

$update = $this->Posts->query()->update();
$update->set(['title' => 'テスト', 'text' => 'こんにちは']);
$update->where(['id' => 1]);
$update->execute();

これで、idが1のデータをUPDATEできます。save()メソッドを使ったときとの違いは、タイムスタンプのcreatedやmodifiedが自動では入力されないってことですかね。こっちの場合は、set()で入力した値だけが上書きされます。

where()で条件を指定しているので、updateAll()もこれでできることになりますね。実際、CakePHP3にもupdateAll()はありますが、やっていることはupdate()を実行しているだけのようです。updateAll()の使い方は、CakePHP2の頃と特に変わってないです。



delete()

CakePHP2と3では、delete()メソッドに渡す内容が少し変わりました。

例えば、idが1のデータを削除したい場合。

//CakePHP2
$this->Post->delete(1);

//CakePHP3
$data = $this->find()->where(['id' => 1])->first();
$this->Posts->delete($data);

CakePHP3ではエンティティデータを渡さないといけないようなので、まずfind()メソッドでデータを取って来て、そのデータを使ってdelete()メソッドを実行します。

deleteAll()もありますが、さっきのupdateAll()と同様に、delete()メソッドで代替できます。ただしこのdelete()は、上で使っているのとは別のメソッドです。

$delete = $this->Posts->query()->delete();
$delete->where(['id' => 1]);
$delete->execute();

こんな感じで、WHERE句で条件を指定したDELETE文を発行できます。deleteAll()自体は、CakePHP2と同じように使えます。



おまけ

find()->first()と同じような動きをするものに、get()ってのがあるんですよ。

$data = $this->Posts->get(1);

これでidが1のデータを取って来れます。

確かに便利は便利なんですが、でもこのget()でデータを取得する場合、DBに存在しない値を入れてしまうと、空の値が返ってくるんじゃなくて、エラーになっちゃうんですよね。データが存在しない場合、Exceptionを投げるような仕様になってるみたいです。何かtryでも上手く処理できなかったんだけど……やり方間違えたかな?

ユーザーが入力した値を使ってget()する場合だと、存在しないデータを参照されるようなことってわりと起こり得ると思うので、あまり使わない方が良さそうな気がするんですけど……どうなんだろう?



おまけその2

whereで条件を指定するときにIN句を使うことがあるかと思うんですが、CakePHP3では、明示的にIN句にしないといけないみたいです。

//CakePHP2(自動的にIN句になる)
$params = array(
  'conditions' => array(
    'id' => array('1,2,3'),
  ),
);
$data = $this->Post->find('all', $params);

//CakePHP3(id=1のデータを取ってきてしまう)
$query = $this->Posts->find();
$query->where(['id' => [1,2,3]);
$data = $this->Posts->all();

CakePHP2のときは、上記のように書けば、自動でIN句を作ってくれました。でもCakePHP3では、配列で書くとtrueか何かがセットされてしまうみたいで、強制的に「id=1」になってしまうようです。

$query = $this->Posts->find();
$query->where(['id IN' => [1,2,3]);
$data = $this->Posts->all();

こんな感じで、自分でINを入れる必要があります。update()やdelete()のときも同様です。



おまけその3

「find()->all()」でデータを取得したときなんですが、CakePHP2のときはデータだけを取得していたので、件数が0件だと返り値は空でした。でもCakePHP3ではデータ以外のものもごちゃごちゃと入っているので、たとえ件数が0件でも、中身は空じゃないです。

$data = $this->Posts->find()->all();

if(empty($data)) {
  echo 'データがありません';
}

従って、例えばこんな風にempty()で判定しようとすると、いつでもfalseが返って来て、データがありませんのメッセージが出せない。

なのでここは、データの件数が0件かどうかで判定することになると思います。

$data = $this->Posts->find()->all();

if($data->count() == 0) {
  echo 'データがありません';
}

find()で取得した件数を取得するための「count()」というメソッドがあるので、それを使えば件数が分かります。CakePHP3のメソッドじゃなくて、phpのcount()を使って、「count($data)」でも取れますけどね。






基本的な使い方はこんなところだと思います。

CakePHP3になってから、全体的にSQLの地の文に近い感じで使えるようになった気がします。INSERT文に関しても、save()メソッドを使えば良いんですが、一応insert()というメソッドも用意されてるみたいですし。まだ使ったことないけど。

冒頭でも言いましたが、データのINSERTに使うsave()メソッドについては今日は触れなかったので、次回はそれやりましょう。まあ、save()メソッドそのものの説明っていうよりは、save()のときに一緒に使うnewEntity()とかの話がメインになりそうですが、よろしく頼んます。

ではそういうことで。次回もまた見てくれよな!



その他のCakePHP3を触ってみましたの記事はこちら
まとめという名の箸休め
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください