LaravelのControllerクラスって何やってるの？

## はじめに

### この記事について
[Laravel Advent Calendar 2017](https://qiita.com/advent-calendar/2017/laravel) 6日目の記事です。

5日目は4日目に引き続き @mpyw さんの記事で、リレーションを含んだルールを持つアクセスポリシーとResourcefulルーティングを組み合わせたときのURLパターンの考察と実装例でした。最終的なPolicyクラスのサンプルコードは何が書いてあるかひと目でわかるようになっていますね！

@mpyw さんは直接の面識はないのですが、いつも読みやすい記事を投稿してくださっていて、最近ですと↓の記事に助けられました。(PHPではありませんが)
[たぶんこれが一番分かりやすいと思います React + Redux のフロー図解](https://qiita.com/mpyw/items/a816c6380219b1d5a3bf)

また、この記事は @ytake さんの [ytake/lumen-adr-example](https://github.com/ytake/lumen-adr-example) のコードを読んでADRパターンの勉強をしていたところ出てきた疑問から生まれました。


### 環境
* PHP 7.1.8
* Laravel 5.5.14

## Controllerクラスは必須ではない

[コントローラのドキュメント](https://laravel.com/docs/5.5/controllers) にこのようなTipsがあります。

![Controllers_-_Laravel_-_The_PHP_Framework_For_Web_Artisans.png](https://qiita-image-store.s3.amazonaws.com/0/27764/35ea057b-9cc9-112a-42c8-07c086768a76.png)
> Tip!! コントローラはベースクラスの拡張を要求してはいません。しかし、middleware、validate、dispatchのような便利な機能へアクセスできなくなります。

初学者は読み飛ばしてしまうでしょうし、このTipsは5.3のドキュメントから追加されたみたいですので、5.2以前から始めた方は見たことがないかもしれません。
これは何を意味しているのでしょうか？

## 試してみる

簡単なControllerを作ってみましょう。(プロジェクト初期化直後を想定)

``` app/Http/Controllers/WelcomeController.php
<?php

namespace App\Http\Controllers;

class WelcomeController extends Controller
{
    public function index()
    {
        return view('welcome');
    }
}
```

``` routes/web.php
<?php

Route::get('/', 'WelcomeController@index');
```

いつものWelcome画面が出てきますね！

次にControllerクラスの継承をやめてみましょう。

``` app/Http/Controllers/WelcomeController.php
<?php

namespace App\Http\Controllers;

class WelcomeController
{
    public function index()
    {
        return view('welcome');
    }
}
```

...これも問題なくWelcome画面が出ます！

## ソースを読んでみる

### Illuminate\Routing\Controller (as BaseController)

フレームワーク内にあるコントローラの基底クラスです。全て見てもそんなに長くないので見ていきましょう。[^1]

``` vendor/laravel/framework/src/Illuminate/Routing/Controller.php
<?php

namespace Illuminate\Routing;

use BadMethodCallException;

abstract class Controller
{
    /**
     * コントローラに登録されたミドルウェア
     */
    protected $middleware = [];

    /**
     * コントローラにミドルウェアを登録
     */
    public function middleware($middleware, array $options = [])
    {
        foreach ((array) $middleware as $m) {
            $this->middleware[] = [
                'middleware' => $m,
                'options' => &$options,
            ];
        }
        return new ControllerMiddlewareOptions($options);
    }

    /**
     * コントローラにされたミドルウェアを取得
     */
    public function getMiddleware()
    {
        return $this->middleware;
    }

    /**
     * コントローラのアクションを実行(渡された関数名の動的コール)
     */
    public function callAction($method, $parameters)
    {
        return call_user_func_array([$this, $method], $parameters);
    }

    /**
     * 未定義の関数が呼ばれたときのマジックメソッド
     */
    public function __call($method, $parameters)
    {
        throw new BadMethodCallException("Method [{$method}] does not exist on [".get_class($this).'].');
    }
}
```

前半は `$middleware` 配列への登録と取得です。ここで指すミドルウェアはコントローラに紐づくもので、これが無くてもグローバルミドルウェアやルート定義時のミドルウェアは適用可能です。

`callAction` はルート定義で `'Controller@action'` の形の文字列で渡されるアクションの呼び出しで使われますが、先ほどの実験でみたようにように無くても動きます。[^2]
`__call` はエラー文言を整形しているだけです。

### App\Http\Controllers\Controller

プロジェクト初期化時にControllersディレクトリに作成されるクラスです。

``` app/http/controllers/controller.php
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
```

Tipsにあるvalidateとdispatchはここで使われているtraitを指しているようですね。

長くなるので割愛しますが、3つのtraitのうち `AuthorizeRequests` の `authorizeResource` メソッドのみ `$this->middleware` を呼び出しているので、`BaseController` を継承せずにこの機能を使うとエラーになります。
他の2つのtraitは `BaseController` に依存しておらず、それぞれ独立して使用できます。

## まとめと考察

Tipsにある `middleware、validate、dispatchのような便利な機能` とは、Controllerクラスに実装されたものとtraitとして実装されたものをひとまとめに指しているということがわかりました。
特に、 `middleware` と `authorizeResource` のメソッドを使わない場合はプレーンなクラスとしてコントローラを定義可能です。

これは、特定のフレームワークへの依存を嫌うようなプロジェクトにおいてはメリットになります。
そうでなくてLaravelが大好きという人も、詳しい実装を知ることでウェブ職人[^3]に一歩近づくでしょう。

## さいごに

小さな疑問でもフレームワークのソースを追ってみることで、そのフレームワークの仕様はもちろん、言語そのものに対する理解が深まると思います。
そのためにも、 [PhpStorm](https://www.jetbrains.com/phpstorm/) などのリッチなIDEを使うことを強くおすすめします。
ソースが読みやすいというのも、Laravelのいいところの一つだと思っています。[^4]


7日目は @yoshikyoto さんのLaravel触ってみた系の記事の予定です。よろしくお願いします！

[^1]: [元ソース](https://github.com/laravel/framework/blob/5.5/src/Illuminate/Routing/Controller.php) から、アノテーションを省略してコメントを日本語に書き換えています
[^2]: `callAction` がない場合でも、 [ControllerDispatcher](https://github.com/laravel/framework/blob/5.5/src/Illuminate/Routing/ControllerDispatcher.php) 内で `$controller->{$method}(...array_values($parameters));` が呼ばれるのですが `callAction` との違いがわかる方がいらっしゃれば解説いただけると幸いです
[^3]: Laravelのキャッチコピー "The PHP Framework For Web Artisans" の和訳として使われます
[^4]: 他のフレームワークと具体的に比べたわけではないです