PHP
laravel
LaravelDay 1

Undocumented Laravel (1) Routing 編

この記事について

Laravel Advent Calendar 2017 の 12/1 のエントリーです。

Laravel 公式サイトに載ってない機能について紹介します。

今回は、ルーティング編です。

公式ドキュメントに載ってないということは、後方互換を考慮せずに変更されることもありうるので、使用にあたっては熟慮されることをオススメします。

役に立つか立たないか、ご自身の目でお確かめください。

環境

  • PHP 7.1.4
  • Laravel 5.5.14

目次

  • ルーティングファイルを分離する
  • FormRequest にてルートパラメータを取得する
  • フォールバックルート
  • RouteMatched イベント

ルーティングファイルを分離する

わりと知られたところでは、routes 以下のファイル(web.php, api.php) を分割する方法です。

app/Providers/RouteServiceProvider.php にメソッドを追加して、すでにある mapWebRoutes と同様に読み込むファイルを指定すればOKです。

以下の例では、app/Http/Controllers/Admin というディレクトリをつくって、サイト管理者のみアクセスできるルートを別ファイルに分離する場合の記述例です。

RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider
{
    public function map()
    {
        $this->mapApiRoutes();

        $this->mapWebRoutes();
        // ↓↓↓追加↓↓↓
        $this->mapAdminRoutes();
    }

    protected function mapAdminRoutes()
    {
        Route::middleware('web')
             ->namespace($this->namespace . '\Admin')
             ->prefix('admin')
             ->as('admin.')
             ->group(base_path('routes/admin.php'));
    }
}

おまけ:Route::as メソッド

5.4 からルートグループの設定が、上記のようにメソッドチェーンでできるようになりました。

5.4 未満

Route::group(['middleware' => 'web', 'namespace' => 'Admin', 'prefix' => 'admin', 'as' => 'admin.'], function () {});

5.4 以上

Route::middleware('web')
    ->namespace('Admin')
    ->prefix('admin')
    ->as('admin.')
    ->group(function () {});

これらのメソッドについては以下に記載があるんですが、 as だけ載っていません。

https://laravel.com/docs/5.5/routing#route-groups

当然使えるだろうと思ってソースコードを読んでみても、メソッド定義はされておらず(Route ファサードの実体は Router クラスです)、毎度おなじみ __call メソッドによって RouteRegistrar クラスへ委譲されていました。

FormRequest にてルートパラメータを取得する

POST 系の処理で使用する、FormRequest を継承したクラスにおいて、ルートパラメータを取得する方法をご紹介します。

ルートパラメータの取得は、色んな方法がありますが、Controller においては、引数に DI して使うのがいちばんいいと思います。

たとえば、Route::get('/users/{id}', ...) というルートでは、

class UserController extends Controller
{
    public function show(int $id)
    {
    }
}

のようにすればユーザーIDが取得できますし、モデルバインディングを使って、Route::get('/users/{user}', ...) とすれば、

class UserController extends Controller
{
    public function show(User $user)
    {
    }
}

のように User クラスのインスタンスが取得できます。

では、FormRequest クラスでは、どうやってルートパラメータを取得すればいいでしょう?

Route::put('/users/{id}', ...) というユーザーモデルを更新するルートに対して、UserUpdateRequest というクラスでバリデーションするケースを考えます。

何らかの理由で、Request クラスにメソッドをつくる必要があって、その中でルートパラメータにアクセスするには、以下の二通りが考えられます。

UserUpdateRequest.php
class UserUpdateRequest extends FormRequest
{
    public function users()
    {
        $userIds = [];
        $userIds[] = $this->route('id');
        $userIds[] = Route::input('id');

        return $userIds; // ["1", "1"]
    }
}

ちなみに、モデルバインディングを使っていれば、モデルのインスタンスをこれで取ることができます。

フォールバックルート

どのルートにもマッチしない URL にアクセスされた場合、通常は NotFoundHttpException がスローされ、404 ページが表示されます。

Route::fallback() メソッドを使えば、どのルートにもマッチしない URL でアクセスされた場合に呼ばれるコールバック関数を作成し、何らかの処理を挟むことができます。

web.php
Route::fallback(function ($route) {
    // ここで何らかの処理をする
    throw new NotFoundHttpException($route . ' not found');
});

もしくは、

web.php
Route::fallback(function ($route) {
    if (Str::startsWith($route, 'some/url')) {
        return view('special_view');
    }
    throw new NotFoundHttpException($route . ' not found');
});

みたいに、特定の URL のときだけ、特別なビューを返す、とかできそうです。

まぁ、あまりいい使い途が思い浮かびませんね(笑)

RouteMatched イベント

いずれかのルートにマッチした場合に呼ばれるコールバック関数を設定することができます。

RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider
{
    public function boot()
    {
        parent::boot();

        Route::matched(function (RouteMatched $event) {
            Log::debug('route matched', ['event' => $event]);
        });
    }
}

RouteMatched イベントは、公開プロパティとして、Route および Request のインスタンスを保持しており、それらの情報をデータベースなどに保存したりするために使えそうです。

まとめ

Undocumented Laravel と題して、公式ドキュメントに載っていない機能をいくつかご紹介しました。

Laravel には他にもさまざまな隠し(?)機能がありますので、いずれ他のカテゴリーについても、記事にしていきたいと思います。

他にも公式ドキュメントに載っていないルーティング周りの便利な機能があれば、ぜひ、コメント欄にてご紹介ください :bow: