サービスコンテナ入門(Laravel)
5月から配属されたチームがLaravelを使ったチームなので、ちゃんと勉強しとこうと思ってLaravelのドキュメントを読み始めました。Laravelを使う上で避けて通れないのが「サービスコンテナ」ということで、本ポストを学んだことの事項とします。
サービスコンテナとは
公式ドキュメントでは「サービスコンテナはクラスの依存関係を管理し依存性の注入(Dependency Injection)を実行するための強力なツールです。」と記載があります。
依存性の注入(Dependency Injection)ってなに?なんのためにあるのっていう方にはこちらの記事がおすすめです。
それでは早速Laravelのコードを書いて学んでいきましょう。
準備
まずはLaravelプロジェクトを作ります。
composer create-project laravel/laravel service-container-example
※最新の公式ドキュメントではLaravel Sailの利用が推奨されているんですが、今開発用に使っているM1 MacBook Airだとエラーが解決できなかったのでcompoerを使ってます。
プロジェクトディレクトリに入ってPHP用のテストサーバを起動します。
cd service-container-example php artisan serve
ブラウザからhttp://localhost:8000にアクセスしてLaravelのWelcomeページが表示されれば準備完了です。
サービスコンテナの使い方
サービスコンテナを使うためのAPIを作っていきます。まずはコントローラーからです。プロジェクトルートディレクトリで以下のコマンドを実行します。
php artisan make:controller HelloController
./app/Http/Controllers/HelloController.phpができるので、indexメソッドを作成します。
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class HelloController extends Controller { // 追加 public function index() { return "Hello."; } }
./routes/web.phpにHelloController@index用のルーティングを設定します。
<?php use App\Http\Controllers\HelloController; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); // 追加 Route::get('/hello', [HelloController::class, "index"]);
これでhttp://localhost:8000/helloにアクセスしたら「Hello.」と表示されます。
それではこの機能を単純なサービスコンテナを使って実装していきます。
./app/Models/HelloMaker.phpを以下の内容で作成します。
<?php namespace App\Models; class HelloMaker { public function text() { return "Hello from Hello Maker."; } }
以下のコマンドを実行してHelloMakerProvider.phpを作成します。
php artisan make:provider HelloMakerProvider
./app/Providers/HelloMakerProvider.phpが作成されるのでregisterメソッドにサービスコンテナを利用するための処理を追加します。
<?php namespace App\Providers; use App\Models\HelloMaker; use Illuminate\Support\ServiceProvider; class HelloMakerProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // 追加 $this->app->bind(HelloMaker::class, function() { return new HelloMaker(); }); } /** * Bootstrap services. * * @return void */ public function boot() { // } }
そして./config/app.phpで定義されている連想配列のprovidersの項目に「App\Providers\HelloMakerProvider::class」を追加します。
ファイルは結構大きいので全ては載せませんが追加後のprovidersは私の環境では以下のようになっています。
'providers' => [ /* * Laravel Framework Service Providers... */ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Bus\BusServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class, Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, Illuminate\Cookie\CookieServiceProvider::class, Illuminate\Database\DatabaseServiceProvider::class, Illuminate\Encryption\EncryptionServiceProvider::class, Illuminate\Filesystem\FilesystemServiceProvider::class, Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, /* * Package Service Providers... */ /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, // 追加 App\Providers\HelloMakerProvider::class, ],
最後にHelloControllerの内容を以下のように編集します。
<?php namespace App\Http\Controllers; use App\Models\HelloMaker; use Illuminate\Http\Request; class HelloController extends Controller { private $hello_maker; // 追加 public function __construct(HelloMaker $hello_maker) { $this->hello_maker = $hello_maker; } public function index() { return $this->hello_maker->text(); } }
HelloMakerProvider.phpのregisterメソッドの処理を記載することによって、HelloControllerのコンストラクタでHelloMakerが呼ばれたときに自動的に新しいインスタンスが生成されるようになります。
これでhttp://localhost:8000/helloにアクセスしたときに「Hello from Hello Maker.」と表示されます。
ただこれはサービスコンテナを使ってはいますがHelloMakerがそのまま出てきているので依存性の注入の利点が活かせていません。ということで依存性の注入を活かすためにインターフェースを利用したバージョンにしてみましょう。
インターフェースを使ったバージョン
まずは./app/Models/TextMaker.phpをinterfaceとして定義します。
<?php namespace App\Models; interface TextMaker { public function text(); }
HelloMakerをTextMakerの実装にします。
<?php namespace App\Models; // implements TextMakerを追加 class HelloMaker implements TextMaker { public function text() { return "Hello from Hello Maker."; } }
./app/Providers/HelloMakerProvider.phpを以下のように編集します。
<?php namespace App\Providers; use App\Models\HelloMaker; use App\Models\TextMaker; use Illuminate\Support\ServiceProvider; class HelloMakerProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // 変更 $this->app->bind(TextMaker::class, HelloMaker::class); } /** * Bootstrap services. * * @return void */ public function boot() { // } }
./app/Http/Controllers/HelloController.phpの内容を以下に編集します。
<?php namespace App\Http\Controllers; use App\Models\TextMaker; class HelloController extends Controller { // $hello_maker から$text_makerに変更 private $text_maker; public function __construct(TextMaker $text_maker) { $this->text_maker = $text_maker; } public function index() { return $this->text_maker->text(); } }
これでインターフェースを使ったバージョンの実装ができました。動作は変わっていませんが、HelloControllerではTextMakerしか参照していないのにhttp://localhost:8000/helloにアクセスすると「Hello from Hello Maker.」と表示されるはずです。
ついでにインターフェースの実装を呼び分けるパターンも試してみましょう
実装を呼び分けるパターン
以下のコマンドで./app/Http/Controllers/GoodbyeController.phpを作成します。
php artisan make:controller GoodbyeController
内容は以下のようにします。
<?php namespace App\Http\Controllers; use App\Models\TextMaker; use Illuminate\Http\Request; class GoodbyeController extends Controller { private $text_maker; public function __construct(TextMaker $text_maker) { $this->text_maker = $text_maker; } public function index() { return $this->text_maker->text(); } }
./routes/web.phpにGoodbyeController用のルーティング設定を追加します。
use App\Http\Controllers\GoodbyeController; use App\Http\Controllers\HelloController; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Route::get('/hello', [HelloController::class, "index"]); // 追加 Route::get('/goodbye', [GoodbyeController::class, "index"]);
./app/Models/GoodbyeMaker.phpを作成し、内容を以下のようにします。
<?php namespace App\Models; class GoodbyeMaker implements TextMaker { public function text() { return "Goodbye from Goodbye Maker."; } }
以下のコマンドで./app/Providers/TextMakerProvider.phpを作成します。
php artisan make:provider TextMakerProvider
内容を以下のようにします。
<?php namespace App\Providers; use App\Http\Controllers\GoodbyeController; use App\Http\Controllers\HelloController; use App\Models\GoodbyeMaker; use App\Models\HelloMaker; use App\Models\TextMaker; use Illuminate\Support\ServiceProvider; class TextMakerProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { $this->app->when(HelloController::class) ->needs(TextMaker::class) ->give(HelloMaker::class); $this->app->when(GoodbyeController::class) ->needs(TextMaker::class) ->give(GoodbyeMaker::class); } /** * Bootstrap services. * * @return void */ public function boot() { // } }
./app/Providers/HelloMakerProvider.phpは不要になるので削除します。
最後に./config/app.phpのprovidersからApp\Providers\HelloMakerProvider::classをApp\Providers\TextMakerProvider::classを変更します。
これでhttp://localhost:8000/helloにアクセスすると「Hello from Hello Maker.」と表示され、http://localhost:8000/goodbyeにアクセスすると「Goodbye from Goodbye Maker.」と表示されます。