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

はじめに
以前「CakePHP 3 を使うなら「カスタム Finder メソッド」は知らなきゃ損!」でカスタム Finder メソッドをご紹介しましたが、CakePHP 4 でも同様に使うことが出来ます。
今日はカスタム Finder メソッドと動的な Finder を組み合わせて、効率的にコードを書く方法をご紹介します。
動作確認に使用したのは CakePHP 4.0.6 です。
1. カスタム Finder メソッド(Custom Finder Methods)
Finder メソッドとは find(●●●) で呼び出す処理のことで、個人的には find('all') や find('list') をよく使います。
find('all') の場合は、引数を省略して find() と書くことができます。僕は all を省略しても分かり辛くはならないと思うので find() を使っています。
カスタム Finder メソッドとはその名の通り、自作した find(●●●) のことです。
以下のように自分の好きな条件で Finder メソッドを実装できます。
// 削除されていない記事を取得するカスタム Finder メソッド
public function findNotDeleted(Query $query, array $options)
{
return $query->where(['Articles.deleted' => 0]);
}
$articles = $this->Articles->find('notDeleted')
->all();
上記例では deleted カラムの型が tinyint(1) で、値が 1 の場合は削除済、というケースを想定していますが、deleted を datetime 型に変更するなどの場合に役立ちます。
もし where(['deleted' => 0]) を複数個所に記述していると変更の手間がかかりますよね。
また下記のように複雑なコードになる場合は、ソースの可読性も上がります。
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']);
});
}
$articles = $this->Articles->find('postedMonth', ['date' => '2020-04'])
->all();
- Custom Finder Methods (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html#custom-finder-methods
2. 動的な Finder (Dynamic Finder)
動的な Finderとは何かというと、find●●●By▲▲▲() のように書くことで、任意のファインダー、取得条件を短いクエリで表現することが出来ます。
●●● には Finder メソッド名を、
▲▲▲ には条件のカラム名を、
どちらもアッパーキャメルケースで書きます。
// 下記2つは同じ
$articleList = $this->Articles->findListByDate($date)
->toArray();
$articleList = $this->Articles->find('list')
->where(['Articles.date' => $date])
->toArray();
By▲▲▲ の部分には複数の条件を指定することもできます。
// 下記2つは同じ
$articleList = $this->Articles->findListByDateAndCategoryId($date, $categoryId)
->toArray();
$articleList = $this->Articles->find('list')
->where([
'Articles.date' => $date,
'Articles.category_id' => $categoryId,
])
->toArray();
また finder を示す ●●● の部分を省略すると All になります。
// 下記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();
- Dynamic Finders (CakePHP 4.x Cookbook)
- https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html#dynamic-finders
3. 組み合わせが強力
動的な Finderでは、カスタム Finder メソッドを使用することもできます。
複数の条件を指定する場合でも下記のように書けます。
$articles = $this->Articles->findPublishedByCategoryId($categoryId)
->find('notDeleted')
->find('postedMonth', ['date' => $date])
->all();
このように組み合わせるを利用することで、コードの記述量や行数を節約することができ、可読性と保守性を高めることが出来ます。
今回は where() を使ったカスタム Finder メソッドしか紹介していませが、select()、contain()、order() なども同様に使えます。
4. おわりに
カスタム Finder メソッドと動的なメソッドをうまく使うコツは、なるべく単純なファインダーをつくるかなと思います。
コードを短くすることを重視すると、1つのカスタムファインダーに複数の条件を含めたくなりますが、とくに拡張する予定があるシステムではあまりおススメしません。その複数条件の一部を変更するときに手間が発生しますし、そこで時間短縮のためなどの理由であまりよくない対応をすると、保守しにくいコードになるリスクもあります。
とくに最初のうちは、なるべく1条件=1ファインダーで実装し、その組み合わせで作るようにすると、速度と品質が上がるのではないかと思います。