Laravel のルート定義でコントローラーのクラス名とメソッド名を別々に書きたい

LTS の 5.5 を使ってます。

つまり、こういうことがやりたいのです。

```php
Route::controller(UserController::class)->group(function () {
    Route::get('/users', '@index');
    Route::get('/users/create', '@create');
    Route::post('/users', '@store');
    Route::get('/users/{user}', '@show');
    Route::get('/users/{user}/edit', '@edit');
    Route::put('/users/{user}', '@update');
    Route::delete('/users/{user}', '@destroy');
});
```

↑のまんまだと `Route::resource()` だけで十分ですが、その他のアクションも微妙にほしいとき、↑のような書き方をしたいことがあります。

## 試行錯誤

`Route::get()` などの第２引数の action は `Route::namespace()` と結合されるので次のようにできるかと思いましたが、

```php
Route::namespace(UserController::class)->group(function () {
    Route::get('/users', '@index');
    Route::get('/users/create', '@create');
    Route::post('/users', '@store');
    Route::get('/users/{user}', '@show');
    Route::get('/users/{user}/edit', '@edit');
    Route::put('/users/{user}', '@update');
    Route::delete('/users/{user}', '@destroy');
});
```

これだと `App\Controllers\UserController\@index` などとなって `App\Controllers\UserController\` なんてクラスは無いと怒られます、末尾の `\` が余分です。

`Illuminate\Routing\Router::prependGroupNamespace()` で namespace と action をつなげるときに `\` が付け足されています。

```php
    protected function prependGroupNamespace($class)
    {
        $group = end($this->groupStack);

        return isset($group['namespace']) && strpos($class, '\\') !== 0
                ? $group['namespace'].'\\'.$class : $class;
    }
```

action（↑のコードでは `$class`）の先頭が `@` のときは `\` を付け足さない、とかやってくれれば良かったのですが。。。ので、`Router` を継承して `prependGroupNamespace()` の動きを変えます。

```php
class AppRouter extends Router
{
    protected function prependGroupNamespace($class)
    {
        $group = end($this->groupStack);
        if (isset($group['namespace']) && strpos($class, '\\') !== 0) {
            if ($class[0] === '@') {
                $class = $group['namespace'] . $class;
            } else {
                $class = $group['namespace'] . '\\' . $class;
            }
        }
        return $class;
    }
}
```

そしてサービスプロバイダで `router` を差し替えます。

```php
public function register()
{
    $this->app->singleton('router', function ($app) {
        return new AppRouter($app['events'], $app);
    });
}
```

しかしこれはうまくいきません。それどころか全てのリクエストが 404 になります。

詳しく追ってみたところ、↑のサービスプロバイダの定義よりも先に `Router` がインスタンス化されていました。`\Illuminate\Foundation\Http\Kernel` のコンストラクタ引数に `Router` があるので、初っ端の

```php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
```

の時点で `Router` がインスタンス化され、リクエストのディスパッチにはこれが使用されます。

アプリケーションのサービスプロバイダが実行されるのはもっと後なので、`Router` をアプリケーションで差し替えると、ルート定義される `Router` と、リクエストのディスパッチに使用される `Router` が別のインスタンスになってしまい、ルート定義が空でディスパッチされてしまいます。

## 方法その１：`RouteMatched` イベントで `Route` オブジェクトを書き換える

ルートがマッチした後、ディスパッチされる前に `Router` の `RouteMatched` イベントが発生するので、そのイベントで `Route` オブジェクトを書き換えます。

```php
Route::matched(function (RouteMatched $event) {
    if (isset($event->route->action['uses']) && is_string($event->route->action['uses'])) {
        $event->route->action['uses'] = preg_replace('/\\\\@/', '@', $event->route->action['uses']);
    }
});
```

一応これで一応動きますが、この方法だと `artisan route:list` でエラーになります・・

## 方法その２：`Kernel` より先に `router` のサービス定義する

`Kernel` がインスタンス化されるより先に `router` のサービスを定義すれば良いので、`bootstrap/app.php` にサービスの定義を追加します。

```php
$app->singleton('router', function ($app) {
    return new AppRouter($app['events'], $app);
});
```

`artisan route:list` も動くので「方法その１：`RouteMatched` イベントで `Route` オブジェクトを書き換える」よりよいと思います。

ただし、以下のようにルートを定義するとやっぱり駄目です。

```php
Route::namespace(UserController::class)->group(function () {
    Route::get('/users')->uses('@index');
});
```

この場合は `Router` ではなく `Route` クラスの中のメソッドで `\` が補完されてます。そして `Route` を差し替えるのは簡単ではなさそうです・・・

## さいごに

うーん、どっちの方法も無理矢理感があるので微妙かな・・・もっとうまい方法無かろうか。

下記を見るに、かつては `Route::controller` というものがあって、これはそれと同じものを使えるようにするもののようです。

- https://github.com/lesichkovm/laravel-advanced-route

ただコントローラーのメソッドが暗黙にルートになるもののようで、期待しているものとはちょっと違いそうでした（ルート自体は明示的に書きたい）。