導入
はじめまして、僕は新卒エンジニアです。
学生時代、とあるベンチャー企業にインターン活動で1年半ほどお世話になっていました。
そのため、Laravel自体は2年ほど触ってきました。
個人的には、もう初心者ではないかなと思っています(思いたい!)
本記事は、Laravel初学者が必ず通る、Fat Controllerについて話していきます。
僕もかなり長い間、これを続けていました。
昔のリポジトリなんかを見返すと、目を背けたくなります。
なぜこれに陥りがちかというと、脱するには視座を上げる必要があるためです。
Laravel強いてはMVCパターンの解釈を正しく、明確に、細分化しなければなりません。
より抽象度の高い話になりそうですが、なるべく具体も交えていきます。
- Fat Controllerって何か、気になる
- ある程度勉強して、そろそろ1つレベルを上げたい
- Controllerがどこか、不細工なんだよな
- 効率的な書き方ってなんだろうね
こんなことを考えている方に読んでほしいです!
Fat Controllerについて
そもそも「Fat Controller」とはなんぞや、という方に向けて、簡単に説明していきます。
一言で、「コントローラが肥大化している」ということです。
ほとんどの場合、リクエストを受け取ってからレスポンスを返すまでの全ての処理を、コントローラ内で記述しているためでしょう。
以下は例です。
class SearchController extends Controller
{
public function searchScope(Request $request){
$areas = Area::all();
$genres = Genre::all();
$favorites = Favorite::where('user_id',Auth::id())->get('salon_id');
$salons = Salon::AreaSearch($request->salon_area)->GenreSearch($request->salon_genre)
->KeywordSearch($request->salon_name)->get();
$counts = $array->count();
$salonIdArray = [];
for($start=0;$start<$counts;$start++){
array_push($salonIdArray,$array[$start]["salon_id"]);
}
return view('salons',compact('salons','areas','genres','salonIdArray'));
}
}
SearchControllerにsearchScopeというメソッドがあります。
このメソッドで行いたいことは、
「ジャンルや地域、店舗名によって検索された場合、検索結果を表示したい」
です。
このメソッド内が "肥大化" しています。
このメソッドが今現在行っていることは、以下です。
- Area、Genreクラスからall()メソッドを呼び出し、変数に格納
- ログインユーザのお気に入り店舗を取得、変数に格納
- エリア、ジャンル、キーワードなどをもとに検索処理、変数に格納
- ログインユーザのお気に入り店舗のIDを配列に格納
- 変数と共に、viewを返す
これは良くありません!
コントローラの責務を逸脱するためです。
どのような部分が逸脱しているのか、または好ましいのか、見ていきましょう。
果たすべき責務
この「責務」という考え方を理解できると、プログラミング学習において、1つ視座が上がるでしょう。
Laravelというフレームワークというよりも、オブジェクト指向という1つの概念について勉強されると、理解しやすいと思います。
簡単に説明すると、「1つのクラスは必ず1つの責務を果たすべき」 という考え方です。
ではコントローラにはどんな責務があるのでしょうか。
Laravelが採用しているデザインパターンは
MVC(Model - View - Controller)です。
それぞれに果たすべき責務があります。
以下の記事にとても良いまとめ方がされていたため、引用させていただきます。
- モデル
取り扱うデータの構造と、それに紐づくビジネスロジックを管理する
- ビュー
HTTPリクエストにおける、エンドポイントの見え方の構造を定義する
- コントローラ
ビューとモデルの媒介を行い、必要に応じて横断的なアプリケーションのビジネスロジックの呼び出しを定義する
ものすごく良い書き方をされているなと、感銘を受けました。
コントローラには、以下のように書かれています。
「横断的なアプリケーションのビジネスロジックの "呼び出し" を定義する」
あくまで、コントローラで定義するべきは呼び出しなのです。
処理そのものをコントローラで行うべきではありません。
それらはモデルなどの他クラスに委譲すべきなのです。
先ほどの言い回し風に、
「呼び出しを定義することは責務範囲内であるが、処理そのものを定義することは責務範囲外である」
ということです。
オブジェクト指向において、煉獄さんこそ正義ということです!
素晴らしいアプリケーションは、すべてのオブジェクトが煉獄さんということです。
全てのオブジェクト:「俺は俺の責務を全うする!」
責務を全うしたコントローラ
好ましい書き方をすると、以下の感じに
class SearchController extends Controller
{
public function SearchScope(Request $request){
$areas = Area::all();
$genres = Genre::all();
$favorites = Favorite::findById(Auth::id());
$salons = Salon::generalSearch();
$favorites_array = FavSalonService::createFavoritesIdsArray($favorites);
return view('salons',compact('salons','areas','genres','favorites_array'));
}
}
不細工なコードは、少しスマートになりました。
※IDの配列を返すだけなのであれば、以下の書き方をすれば、もう1行減らせます。
$favorites = Favorite::findById(Auth::id())->pluck('id');
削除:$favorites_array = FavSalonService::createFavoritesIdsArray($favorites);
pluckメソッドを用いると、指定したプロパティのみの配列を返すことができます。
記事をより良いものにするために、筆者の過去の過ちをそのまま載せています(笑)
以下3つの処理を他クラスに委譲しました
- ログインユーザのお気に入り店舗を取得
- エリア、ジャンル、キーワードなどをもとに検索処理
- ログインユーザのお気に入り店舗のIDを配列に格納
他クラスで定義し、それを呼び出すだけの構造にしています。
1と2はモデルに委譲し、3はサービスクラスに委譲しています。
※3だけ異なる理由は、以下のコラムで
コラム
気になる人だけ読んでください!!
3のみモデルではない理由は、複数のモデルをまたがるためです。
「ログインユーザがお気に入り登録している店舗のIDを配列として返す」ということを実装しています。
ということは、「店舗(Salon)」と「お気に入り(Favorite)」という2つのモデルをまたがっています。
そのため、どちらかのモデルに定義してもそれは不自然となるのです。
こういった時のために、サービス層というレイヤーが存在します。
気になる人は、「DDD」「ドメインサービス」「アプリケーションサービス」などのワードについて調べてみてください。
多分少し難しいです。
結論
Laravelというフレームワークでwebアプリケーションを開発しようと努力することは素晴らしいことです。
徐々にLaravelを理解していき、アプリケーションが動いた時の達成感も大きいです。
ですが、フレームワークという利便性に溺れ続ける怠慢だけは、許してはいけません。
筆者は、構造や本質を理解しようともせず、恩恵を受け続けることこそ怠慢だと考えています。
フレームワークは、すでに用意された恩恵を受けることができ、独特の仕様に精通さえすれば、アプリケーションを開発することができる素晴らしい骨組みです。
ですが、その恩恵を受けるがあまり、本質的な理解は全く進まず、あるポイントで停滞することが考えられます。
ある程度Laravelを理解できたな、という方こそ、オブジェクト指向を勉強されてはいかがでしょうか。
フレームワークの見方も変わってきますし、どんな言語、フレームワークにも通ずる基礎力が身につくでしょう。
ここまで読んでくださった方々、ありがとうございました!
いいねを押してくれると、僕の自己肯定感が上がります!
ご質問またはご指摘がある方は気軽にコメントしてください!喜んで回答します!