Laravelアプリとモジュール(アプリ依存パッケージ)の連携
本記事は、鹿児島 Advent Calendar 2025 13日目の記事です。
Laravelアプリにグルーコードを追加し、
アプリとモジュール(アプリ依存パッケージ)を連携させる方法[1]を紹介します。
前提
本記事では、以下を「モジュール(アプリ依存パッケージ)」の定義としています。
アプリに依存するようなファイル(ルーティングやコントローラなど)を含むパッケージ
Reactでのビューを含む場合もありますが、本記事では取り扱いません。
また、モジュール側のサービスプロバイダではなく、
アプリ側のサービスプロバイダでルートを読み込むことに重点を置いています。
実行環境
PHP 8.5.0
Laravel v12.42.0
Node.js v24.12.0
最終的なコード
手順
プロジェクトの作成
Laravelアプリの雛形を作成します。
本記事では、Laravel公式が提供しているスターターキットの代わりに、
筆者がカスタマイズしたものをusingオプションで指定しています。
※ usingオプションを使用しなくても、以降の手順で支障はありません。
以下のコマンドを実行
laravel new advent --using=miraiportal/dynasty-starter-kit
Which testing framework do you prefer?
Pestを選択
Would you like to run npm install and npm run build?
Yesを選択
モジュラモノリス対応
利用するモジュールを列挙する app/Enums/Module.php と、
モジュールのルートを読み込む app/Providers/ModuleServiceProvider.php
を追加します。
<?php
declare(strict_types=1);
namespace App\Enums;
use Illuminate\Support\Facades\Route;
enum Module: string
{
public static function loadWebRoutes(): void
{
foreach (self::web() as $module) {
$webRoute = $module::webRoute();
assert(is_string($webRoute));
Route::middleware('web')->group($webRoute);
}
}
public static function loadApiRoutes(): void
{
foreach (self::api() as $module) {
$apiRoute = $module::apiRoute();
assert(is_string($apiRoute));
Route::middleware('api')->prefix('api')->group($apiRoute);
}
}
public static function loadConsoleRoutes(): void
{
foreach (self::console() as $module) {
require $module::consoleRoute();
}
}
/** @return class-string[] */
private static function web(): array
{
return self::modulesDefining('webRoute');
}
/** @return class-string[] */
private static function api(): array
{
return self::modulesDefining('apiRoute');
}
/** @return class-string[] */
private static function console(): array
{
return self::modulesDefining('consoleRoute');
}
/** @return class-string[] */
private static function modulesDefining(string $method): array
{
return array_filter(
array_map(fn (self $module) => $module->value, self::cases()),
fn (string $class) => is_callable([$class, $method]),
);
}
}
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Enums\Module;
use Illuminate\Support\ServiceProvider;
final class ModuleServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->loadWebRoutes();
$this->loadApiRoutes();
$this->loadConsoleRoutes();
}
private function loadWebRoutes(): void
{
if ($this->app->routesAreCached()) {
return;
}
Module::loadWebRoutes();
}
private function loadApiRoutes(): void
{
if ($this->app->routesAreCached()) {
return;
}
Module::loadApiRoutes();
}
private function loadConsoleRoutes(): void
{
if (! $this->app->runningInConsole()) {
return;
}
Module::loadConsoleRoutes();
}
}
また、以下のように bootstrap/providers.php を変更して、
上記で追加したサービスプロバイダがアプリで読み込まれるようにします。
return [
App\Providers\AppServiceProvider::class,
+ App\Providers\ModuleServiceProvider::class,
];
Pingモジュールの追加
本記事では、筆者が公開しているモジュールを追加します。
以下のコマンドを実行
composer require dynasty/ping
モジュールで利用するルートを定義する app/Modules/PingModule.php
を追加します。
<?php
declare(strict_types=1);
namespace App\Modules;
final class PingModule
{
private const string ROUTES_DIR = 'vendor/dynasty/ping/routes';
public static function apiRoute(): string
{
return base_path(self::ROUTES_DIR.'/api.php');
}
public static function consoleRoute(): string
{
return base_path(self::ROUTES_DIR.'/console.php');
}
}
最後に、以下のように app/Enums/Module.php を変更して、
利用するモジュールとしてPingモジュールを追加します。
namespace App\Enums;
+ use App\Modules\PingModule;
use Illuminate\Support\Facades\Route;
enum Module: string
{
+ case Ping = PingModule::class;
+
public static function loadWebRoutes(): void
{
foreach (self::web() as $module) {
動作確認
API
以下のコマンドを実行
composer run dev
http://127.0.0.1:8000/api/ping にアクセスして、
pong という文字列が返ってくることを確認
コンソール
以下のコマンドを実行
php artisan app:ping
pong という文字列が返ってくることを確認
おまけ
アプリ側からモジュール側の実装を切り替えたり
#[\Override]
public function register(): void
{
$this->app->bind(
PingUseCase::class,
PingCustomInteractor::class,
);
}
機能フラグ(Laravel Pennant)でルートの公開/非公開を切り替えたり
foreach (self::api() as $module) {
$apiRoute = $module::apiRoute();
assert(is_string($apiRoute));
Route::middleware([
EnsureFeaturesAreActive::using($module),
'api',
])->prefix('api')->group($apiRoute);
}
-
別の表現をすると、
「外部パッケージを使用せず、モジュラモノリスを限定的に実現する」
といった内容です。 ↩︎
Discussion