【Laravel】laravel-modulesでmiddlewareの登録をする

Laravel で手っ取り早くモジュラモノリスやるときは laravel-modules を良く使います。

GitHub - nWidart/laravel-modules: Module Management In Laravel
Module Management In Laravel. Contribute to nWidart/laravel-modules development by creating an account on GitHub.

Modules 配下に各モジュールを配置しているためファイルがモジュール毎にまとまっており、内部設計的にはシンプルでわかりやすいと思います。ただし、モジュール間でファイルを相互に参照出来るので、依存関係の検証は別の仕組みでなんとかする必要があります。

今回は、laravel-modules でモジュール内に作成した middleware を登録する方法をまとめてみます。

準備

Laravel と laravel-modules をインストールしておきます。今回は両方とも11です。

$ composer create-project laravel/laravel .
$ composer require nwidart/laravel-modules

忘れがちな設定ですが、extra セクションに merge-plugin を追加します。

"extra": {
    "laravel": {
        "dont-discover": []
    },
    "merge-plugin": {
        "include": [
            "Modules/*/composer.json"
        ]
    }
},

適当な名前のモジュールと middleware を作っておきます。middleware は artisan コマンドを使うと簡単に生成出来ます。

Artisan commands - Laravel Modules
$ php artisan module:make Posts
$ php artisan module:make-middleware PostsMiddleware Posts

作成した middleware に適当な処理を追加しておきます。

<?php

namespace Modules\Posts\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class PostsMiddleware
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next)
    {
        echo 'Hello, world!! ';
        return $next($request);
    }
}

bootstrap/app.php で登録する

middleware は bootstrap/app.php の withMiddleware セクション内で登録するのが正攻法です。

Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing ...
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // グローバルミドルウェアで登録
        $middleware->append(\Modules\Posts\Http\Middleware\PostsMiddleware::class);

        // ミドルウェアグループに登録
        $middleware->appendToGroup('group', [\Modules\Posts\Http\Middleware\PostsMiddleware::class]);

        // エイリアスとして登録
        $middleware->alias(['post' => \Modules\Posts\Http\Middleware\PostsMiddleware::class ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

以下のようなルートを用意して GET リクエストを投げてみます。

<?php

use Illuminate\Support\Facades\Route;

Route::get('/posts/global', function() {
    return '(global)';
});

Route::get( '/posts/alias', function() {
    return '(alias)';
})->middleware('post');

Route::middleware(['group'])->group(function() {
    Route::get( '/posts/group', function() {
        return '(group)';
    });
});
$ curl http://localhost:8000/posts/global
Hello, world!! (global)%

$ curl http://localhost:8000/posts/alias 
Hello, world!! Hello, world!! (alias)%

$ curl http://localhost:8000/posts/group
Hello, world!! Hello, world!! (group)%

alias と group はグローバルミドルウェアと重複して登録しているので2回出力がありますが、ちゃんと動いていることが確認出来ます。

ただし、グローバルミドルウェアとして登録されているので、モジュール配下のルート以外で定義されたルートでも当該 middleware は適用されてしまいます。この点だけは注意が必要です。

<?php

use Illuminate\Support\Facades\Route;

Route::get('/global', function() {
    return '(global common)';
});
$ curl http://localhost:8000/global     
Hello, world!! (global common)%

RouteServiceProvider で登録する

別の方法として、RouteServiceProvider に記述することも出来ます。

この RouteServiceProvider は Illuminate\Foundation\Support\Providers\RouteServiceProvider クラスを継承しており、register 内で map メソッドを呼び出します。

/**
 * Register any application services.
 *
 * @return void
 */
public function register()
{
    $this->booted(function () {
        $this->setRootControllerNamespace();

        if ($this->routesAreCached()) {
            $this->loadCachedRoutes();
        } else {
            $this->loadRoutes();

            $this->app->booted(function () {
                $this->app['router']->getRoutes()->refreshNameLookups();
                $this->app['router']->getRoutes()->refreshActionLookups();
            });
        }
    });
}

/**
 * Load the application routes.
 *
 * @return void
 */
protected function loadRoutes()
{
    if (! is_null(self::$alwaysLoadRoutesUsing)) {
        $this->app->call(self::$alwaysLoadRoutesUsing);
    }

    if (! is_null($this->loadRoutesUsing)) {
        $this->app->call($this->loadRoutesUsing);
    } elseif (method_exists($this, 'map')) {
        $this->app->call([$this, 'map']);
    }
}

RouteServiceProvider の map メソッド内では、web 及び api ルートに関する定義がされているので、ここで middleware の登録をすると良さそうです。今回はエイリアスを登録して web ルートに適用してみます。

<?php

namespace Modules\Posts\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Modules\Posts\Http\Middleware\PostsMiddleware;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * Called before routes are registered.
     *
     * Register any model bindings or pattern based filters.
     */
    public function boot(): void
    {
        parent::boot();
    }

    /**
     * Define the routes for the application.
     */
    public function map(): void
    {
        Route::aliasMiddleware('post', PostsMiddleware::class);
        $this->mapApiRoutes();

        $this->mapWebRoutes();
    }

    /**
     * Define the "web" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     */
    protected function mapWebRoutes(): void
    {
        Route::middleware(['web', 'post'])->group(module_path('Posts', '/routes/web.php'));
    }

    /**
     * Define the "api" routes for the application.
     *
     * These routes are typically stateless.
     */
    protected function mapApiRoutes(): void
    {
        Route::middleware('api')->prefix('api')->name('api.')->group(module_path('Posts', '/routes/api.php'));
    }
}

GET リクエストを投げてみます。

$ curl http://localhost:8000/posts/global
Hello, world!! (global)%

middleware が適用されていました。モジュール内で作成した middleware をモジュール内で閉じたい場合はこの方法が良さそうな気がします。

ただし、モジュール配下の RouteServiceProvider 内で登録した middleware のエイリアスはモジュール外でも使えるということと、モジュール外で使った場合、当該モジュールが disabled だとエイリアスが見つからず500エラーを返してしまう点は注意が必要です。

<?php

use Illuminate\Support\Facades\Route;

Route::get('/global', function() {
    return '(global common)';
})->middleware('post');
{
    "Posts": false
}
$ curl -I  http://localhost:8000/global
HTTP/1.1 500 Internal Server Error
タイトルとURLをコピーしました