Laravel Advent Calendar 2022 10日目の記事です。
Fat Controllerとは何か?
アプリケーションのアーキテクチャを、エンジニアのメンタルモデルに合致させるべく提唱されたものがMVCモデルだが、実際に使ってみるとControllerに処理が集中しやすく、結果Controllerが冗長になってしまってメンテナンス性を著しく毀損した状態となる。この状態をFat Controllerと呼ぶ。
Fat ControllerのLaravel的回避手段
LaravelではMiddlewareやFormRequestが用意されており、Modelではミューテターやアクセサー、グローバルクエリスコープ、ローカルクエリスコープ、イベントフックといった仕組みが提供されている。これに、PHPが提供しているTraitを組み合わせれば、Fat Controllerを回避することはさほど難しくはない。
しかし、今回はさらにスッキリしたControllerを書くための「戦略」を紹介したいと思う。それが、タイトルにある「Cruddy by Design」である。
DHH流のコントローラーの書き方
まず「Cruddy by Design」を語る前に、Railsの生みの親であるDHH氏の以下の記事を紹介したい。
DHHはどのようにRailsのコントローラを書くのか
https://postd.cc/how-dhh-organizes-his-rails-controllers/
この書き方は、2016年以降Laravelコミュニティでも広く受け入れられており、Taylor氏も以前のラジオのインタビューで「この考え方を取り入れているので自身が書くControllerはとてもスッキリしている(意訳)」と述べている。
要約すると、「Controllerでは原則としてデフォルトのCRUDアクションのみを使用し、その範疇に収まらないアクションが出てきた場合、それに即したコントローラーを別途切り出し、その中でまた同様にデフォルトのCRUDアクションのみを使用する」ということだ。
Cruddy By Design
基本的な考え方は、上で紹介したDHH氏が話した内容と類似しており、よりLaravelの事情に則ったものが「Cruddy By Design」である。これは2017年のLaraconにおいて、今ではTailwind CSSの生みの親として知られるAdam Wathan氏が提唱したものとなる。
当時の発表内容はGithubに残っているので、ざっと読み解いていきたい。
https://github.com/adamwathan/laracon2017
まず、Cruddy By Design
とは、I shared some strategies I use to split large controllers into multiple small controllers.
と述べている通り、大きなコントローラーを複数の小さなコントローラーに分割するためのいくつかの戦略をまとめたものである。
それら戦略の根底にあるアイデアは、「Laravelのリソースコントローラーが提供する標準のアクションのみを使用する」というものであり、以下の4つの具体的な戦略が提唱されている。
- Give nested resources a dedicated controller
- Treat properties edited independently as separate resources
- Treat pivot models as their own resource
- Think of different states as different resources
Adam氏はその4つそれぞれを掘り下げて以下のように説明している。
Give nested resources a dedicated controller
/podcasts/{id}/episodes
のようなネストしたリソースの場合、EpisodesController@indexでは別のコンテキスト(全エピソード一覧の取得)になってしまうため、ネストした一覧を取得するための専用のコントローラーに切り出す。
Treat properties edited independently as separate resources
例えば対象のレコードのうち画像のような個別のプロパティのみを編集するケースでは、そのレコードを扱うリソースの中で扱わずに、別のリソースとして扱う。
Treat pivot models as their own resource
Laravelは、N:Nのリレーションを表現したときに2つのモデルを交差させた部分をpivotと呼んでいる。そのpivotのモデルは、それ自体を1つのリソースとして扱う。
Think of different states as different resources
単純に「保存」する場合でも、様々な状態があるので、それらの状態ごとの違いでリソースを分けて扱う。例では、記事の公開/非公開の状態にちなんだstoreとdestroyをその状態を示すコントローラー名で切り出している。
最後に
以上がCruddy by Designという戦略の概要である。Githubリポジトリを見ていただけばコードも一緒に掲載されているので、より深く理解できると思う。
なお私の場合は、このCruddy By Designに加えてartisanコマンドでも提供しているSingle Action Controller(make:controller HogeController --invokable
)を使うことも多い。
いずれにせよこのような戦略を採用すると、Controllerに意味不明な名前のメソッドや、大量のpublicメソッドを抱えるControllerの爆誕を抑制できるので、個人・チームを問わず有効な戦略と言える。