CakePHP 4 と 3 でカスタム Finder メソッドと動的な Finder を使って作業効率を上げる

はじめに

以前「CakePHP 3 を使うなら「カスタム Finder メソッド」は知らなきゃ損!」でカスタム Finder メソッドをご紹介しましたが、CakePHP 4 でも同様に使うことが出来ます。

今日はカスタム Finder メソッドと動的な Finder を組み合わせて、効率的にコードを書く方法をご紹介します。

動作確認に使用したのは CakePHP 4.0.6 です。

目次
  1. カスタム Finder メソッド(Custom Finder Methods)
  2. 動的な Finder(Dynamic Finder)
  3. 組み合わせが強力
  4. おわりに

1. カスタム Finder メソッド(Custom Finder Methods)

Finder メソッドとは find(●●●) で呼び出す処理のことで、個人的には find('all') や find('list') をよく使います。

find('all') の場合は、引数を省略して find() と書くことができます。僕は all を省略しても分かり辛くはならないと思うので find() を使っています。

カスタム Finder メソッドとはその名の通り、自作した find(●●●) のことです。
以下のように自分の好きな条件で Finder メソッドを実装できます。

/src/Model/Table/ArticlesTable.php
// 削除されていない記事を取得するカスタム Finder メソッド
public function findNotDeleted(Query $query, array $options)
{
    return $query->where(['Articles.deleted' => 0]);
}
/src/Controller/ArticlesController.php
$articles = $this->Articles->find('notDeleted')
    ->all();

上記例では deleted カラムの型が tinyint(1) で、値が 1 の場合は削除済、というケースを想定していますが、deleted を datetime 型に変更するなどの場合に役立ちます。

もし where(['deleted' => 0]) を複数個所に記述していると変更の手間がかかりますよね。

また下記のように複雑なコードになる場合は、ソースの可読性も上がります。

/src/Model/Table/ArticlesTable.php
use Cake\I18n\Date; // ← 追加
...
public function findPostedMonth(Query $query, array $options)
{
    // ↓ これは DATE_FORMAT(created, '%Y-%m') です
    $created = $query->func()->date_format([
        'Articles.created' => 'identifier',
        "'%Y-%m'" => 'literal'
    ]);

    return $query->where(function ($exp) use ($created, $options) {
        return $exp->eq($created, $options['date']);
    });
}
/src/Controller/ArticlesController.php
$articles = $this->Articles->find('postedMonth', ['date' => '2020-04'])
    ->all();

2. 動的な Finder (Dynamic Finder)

動的な Finderとは何かというと、find●●●By▲▲▲() のように書くことで、任意のファインダー、取得条件を短いクエリで表現することが出来ます。

●●● には Finder メソッド名を、
▲▲▲ には条件のカラム名を、
どちらもアッパーキャメルケースで書きます。

/src/Controller/ArticlesController.php
// 下記2つは同じ
$articleList = $this->Articles->findListByDate($date)
    ->toArray();

$articleList = $this->Articles->find('list')
    ->where(['Articles.date' => $date])
    ->toArray();

By▲▲▲ の部分には複数の条件を指定することもできます。

/src/Controller/ArticlesController.php
// 下記2つは同じ
$articleList = $this->Articles->findListByDateAndCategoryId($date, $categoryId)
    ->toArray();

$articleList = $this->Articles->find('list')
    ->where([
        'Articles.date' => $date,
        'Articles.category_id' => $categoryId,
    ])
    ->toArray();

また finder を示す ●●● の部分を省略すると All になります。

/src/Controller/ArticlesController.php
// 下記4つは同じ
$articles = $this->Articles->findByDate($date)
    ->all();

$articles = $this->Articles->findAllByDate($date)
    ->all();

$articles = $this->Articles->find()
    ->where(['Articles.date' => $date])
    ->all();

$articles = $this->Articles->find('all')
    ->where(['Articles.date' => $date])
    ->all();

3. 組み合わせが強力

動的な Finderでは、カスタム Finder メソッドを使用することもできます。

複数の条件を指定する場合でも下記のように書けます。

/src/Controller/ArticlesController.php

$articles = $this->Articles->findPublishedByCategoryId($categoryId)
    ->find('notDeleted')
    ->find('postedMonth', ['date' => $date])
    ->all();

このように組み合わせるを利用することで、コードの記述量や行数を節約することができ、可読性と保守性を高めることが出来ます。

今回は where() を使ったカスタム Finder メソッドしか紹介していませが、select()、contain()、order() なども同様に使えます。

4. おわりに

カスタム Finder メソッドと動的なメソッドをうまく使うコツは、なるべく単純なファインダーをつくるかなと思います。

コードを短くすることを重視すると、1つのカスタムファインダーに複数の条件を含めたくなりますが、とくに拡張する予定があるシステムではあまりおススメしません。その複数条件の一部を変更するときに手間が発生しますし、そこで時間短縮のためなどの理由であまりよくない対応をすると、保守しにくいコードになるリスクもあります。

とくに最初のうちは、なるべく1条件=1ファインダーで実装し、その組み合わせで作るようにすると、速度と品質が上がるのではないかと思います。