【Laravel】Laravelのキモ、「サービスコンテナ」の仕組みを理解する〜前編〜

ゆーたろー

こんにちは、ゆーたろーです。

HRTechベンチャーのエンジニアです。
TypeScript/Vue.js(Nuxt.js)/Laravelを使っています。

・プログラミングスクール講師
・月1で勉強会運営
・神戸メインのグルメインスタ運営

など色々やっています。1児のパパです。

この記事ではLaravelフレームワークのキモであるサービスコンテナについて解説していきます。

「Laravelのすごいところはどこか?と聞かれてサービスコンテナと答えられない人はにわかだー!」

という情報をTwitterでも見たことがあるくらい、Laravelの大きな大きな機能の1つです。

サービスコンテナは概念が少し難しいのでその分、解説ボリュームは多くなってしまいます。

ということで、前編と後編に分けて記事を書きます。
(1つの記事にまとめると読む方が途中で挫折する気がする)

前編の内容

前編のこの記事では以下の内容について解説します。

  • サービスコンテナの概要
  • バインド
  • 解決

まずはサービスコンテナについて知って、基本的な機能を理解します。

後編の内容

  • DI/依存性の注入
  • ファサード(Facades)

後半では上記の内容を扱います。

サービスコンテナの大きな機能であるDI/依存性の注入について深く理解していきます。
(ファサードはおまけです)

後編はこちら。
(ですが、まずはサービスコンテナについてあまりよくわかってないという方は前編から読んでいただいた方が良いです)

参考書籍

この記事はこれらの技術書の内容をベースにまとめています。

初学者向けではないですが、とても勉強になる内容の技術書です。

以下のリンクから購入することができます。

こちらは初心者〜実務経験6ヶ月くらいまでの方にオススメの技術書です。

対応バージョンがLaravel5.8.9と少し古いですが、1つ目の参考書より全体的に内容が易しいので青本の次のステップとしておすすめです。

ゆーたろー

この記事を読んでにわかLaravelエンジニア卒業に少しでも近づきましょう!笑

目次

サービスコンテナとは

一言でいうと?

Laravel内でのインスタンス管理の役割を担ってくれるナイスガイ

です。

どんな機能があるのかというと大きく2つあり、

  • あるクラスのインスタンスを生成する方法を自由にカスタマイズすることができる
  • あるクラスと依存関係にあるクラスのインスタンスを管理してくれる

です。

これについてこれから前編と後編に分けてできるだけ深く掘り下げていくので2つとも読み終わった後に読む前より「なるほど!」と思っていただけると思います。

サービスコンテナのすごいところ

ビジネスロジックはサービスコンテナに対して「このインスタンスちょうだーい!」と要求するだけで、事前に登録された生成方法に従って生成されたインスタンスを返してくれることです。

初学者には「ビジネスロジック?」となるかもしれないのでここはControllerに書くロジック(処理)に置き換えていただければと思います。
(ビジネスロジックはServiceクラスに切り出すことの方が多いですが一旦馴染みやすいようにControllerで…)

開発者がインスタンス管理に悩むことなく、Laravelのたくさんの機能を使うことができるようになります。

ゆーたろー

サービスコンテナ様様や…

サービスコンテナが解決してくれる課題

前述した

  • インスタンスを要求するだけでサービスコンテナが返してくれる
  • 開発者がインスタンス管理に悩まなくて済む

というすごいところがどういう課題を解決してくれるのか。

同一アプリケーション内の複数クラスが同じ機能を使う場合、利用するクラスに応じてインスタンスを生成することになります。
(機能自体もクラスとして定義されていますからね)

ただ、中には複数クラスから同じインスタンスを返してほしいものもあります。

例えば、環境設定やキャッシュ管理まわりとか。

「機能を使う」ということはその処理をビジネスロジック(Controller or Service)に書いていくことになるのですが、これを例えば「個別でインスタンスを生成するのか、もしくは共通のインスタンスを使うのか」をそれぞれ(というか全ての)クラスで定義・制御しようとすることはコードの複雑化に繋がります。

極端な話かもしれませんが、1行だけが異なる内容の複数の関数を全て別関数として定義していく感じなのかなと思います。

このようなことをなくすために前述の

ビジネスロジックはサービスコンテナに対して「このインスタンスちょうだーい!」と要求するだけで、事前に登録された生成方法に従って生成されたインスタンスを返してくれる

という機能を持つサービスコンテナが役立ってくれるわけですね。

バインド(bind)について

バインド(bind)とは?

サービスコンテナにインスタンスの生成方法を登録する

ことをバインド(bind)と言います。

前述したサービスコンテナの機能の「〜事前に登録された生成方法に従って〜」の部分の話です。

つまり予め指示してあげることでインスタンスを生成する方法を自由にカスタマイズすることができるということです。

また、バインドすることを結合とも言います。(以下、ドキュメント参考)

ReaDouble(サービスコンテナ)

公式ドキュメント(の日本語訳)には結合というワードが結構出るので結合という呼び方で覚えておいた方が良いかもしれませんね。

使い方としては「インスタンスをサービスコンテナに結合する」です。

サービスコンテナの実体

サービスコンテナの実体はIlluminate\Foundation\Applicationクラス。

サービスコンテナ(Illuminate\Foundation\Applicationクラス)のインスタンス取得は以下コードで行います。
($appがサービスコンテナのインスタンスです)

// app関数から取得
$app = app();

// Application::getInstanceメソッドから取得
$app = \Illuminate\Foundation\Application::getInstance();

// Appファサードから取得
$app = \App::getInstance();

この技術書や技術記事でも1番上のapp()が使われることが多いようです。

バインド(結合)のサンプルコード

かなり簡易的ですが、サービスコンテナへのバインド(結合)のコードを書きます。

<?php

use Illuminate\Support\Facades\Log;

class SampleClass
{
    public function __construct()
    {
        Log::debug('サンプルクラスのコンストラクタです');
    }
}

// バインド(結合)
app()->bind(Sample::class, function () {
    Log::debug('サービスコンテナへのバインドです');
    return new SampleClass();
})

上記コードから分かる通り、->bind()メソッドを使ってインスタンスをサービスコンテナにバインドしています。

->bind()メソッドの基本的な使い方は以下。

app()->bind(要求するクラス, インスタンスの生成方法);

インスタンス生成方法は処理なのでクロージャ(無名関数)が書かれることが多いです。

よって、以下の形です。

app()->bind(Sample::class, function () {
    // インスタンス生成方法
});

サンプルコードのバインド内容を踏まえ、SampleClassクラスのインスタンス生成は以下の順序で行われます。

  • ログファイルに「サービスコンテナへのバインドです」と出力する
  • SampleClassクラスのインスタンスを生成する

(次に解説する解決をしていないので上記コードでは実際にはインスタンスは生成されません)

今回は例として適当なクラスのファイルを準備して、クラスの定義とサービスコンテナへのバインドを書きましたがバインド処理はサービスプロバイダというこれまたLaravelの機能ですがそこに書くことが多いです。

サービスプロバイダについてはまた別の記事にまとめますので投稿次第リンクを貼ります。

バインドの種類

サービスコンテナへのインスタンスのバインドには下記の通り、いくつかの方法があります。

  • bindメソッド
  • bindIfメソッド
  • singletonメソッド
  • instanceメソッド
  • whenメソッド

それぞれのバインド方法やサンプルコードについてはまた別の記事にまとめます。

解決(resolve)について

解決(resolve)とは?

サービスコンテナが指定のインスタンスを生成して返す

ことを解決(resolve)と言います。

前述したこちら。

ビジネスロジックはサービスコンテナに対して「このインスタンスちょうだーい!」と要求するだけで、事前に登録された生成方法に従って生成されたインスタンスを返してくれる

の、「〜サービスコンテナに対して「このインスタンスちょうだーい!」と要求する〜」の部分は解決です。

サービスコンテナがビジネスロジックから指定されたインスタンスを(バインドされた処理があればその通りに)生成して返してくれることです。

つまり呼び出しです。

バインドのところでもご紹介したReaDoubleには依存解決と書かれていました。

解決のサンプルコード

サービスコンテナが(クラス間の依存関係を)解決したインスタンスを取得する方法は以下の2つです

  • makeメソッド
  • appへルパ関数

Laravel7.xまではresolveヘルパ関数を使ってもできたのですが、Laravel8.xは使えなくなっています。(非推奨なだけかも)

バインドの時に使ったサンプルコードに追記します。

<?php

use Illuminate\Support\Facades\Log;

class SampleClass
{
    public function __construct()
    {
        Log::debug('サンプルクラスのコンストラクタです');
    }
}

// バインド(結合)
app()->bind(Sample::class, function () {
    Log::debug('サービスコンテナへのバインドです');
    return new SampleClass();
})

// makeメソッドによる解決
$sample1 = app()->make(SampleClass::class);

// appヘルパ関数による解決
$sample2 = app(SampleClass::class);

// 上記メソッドが実行されると
// ログファイルに「サービスコンテナへのバインドです」と出力されて
// Sampleクラスのインスタンス生成時にコンストラクタが実行され、ログファイルに「サンプルクラスのコンストラクタです」と出力されて
// 生成されたSampleClassクラスのインスタンスが$sample1、$sample2に格納される

makeメソッド、appヘルパ関数ともに引数に文字列を指定します。

それぞれのメソッドが実行されると、引数の文字列にバインドされた処理が実行されて、その戻り値を返します。

なのでbindメソッドの第1引数とmakeメソッドの引数が一致する必要があります。

バインドしていない文字列の解決

先ほど、順序をまとめましたが、サービスコンテナへのバインドをしてない文字列でも解決することができます。

<?php

use Illuminate\Support\Facades\Log;

class SampleClass
{
    public function __construct($className = 'サンプル')
    {
        
        Log::debug("{$className}クラスのコンストラクタです");
    }
}

// バインドなし

// makeメソッドによる解決
$sample1 = app()->make(SampleClass::class);

上記のコードではSample::classという文字にバインドされた処理がありませんが、実際にSampClassクラス(具象クラス)が存在するので自動でインスタンスを生成して返してくれます。
(ただ、インスタンス生成処理をバインドしていないので普通にインスタンスを返すだけ)

この場合はログファイルに「サンプルクラスのコンストラクタです」と出力されます。

以下のように解決すると、ログファイルには「Sampleクラスのコンストラクタです」と出力されます。

// makeメソッドによる解決
$sample1 = app()->make(SampleClass::class, 'Sample');

最後に

ここまででこの記事を読む前よりサービスコンテナの概念や基本的な機能、使い方の理解が深まったと思います。

STEP
クラスを定義する
<?php

use Illuminate\Support\Facades\Log;

class SampleClass
{
    public function __construct()
    {
        Log::debug('サンプルクラスのコンストラクタです');
    }
}
STEP
インスタンス生成方法をバインドする
// バインド(結合)
app()->bind(Sample::class, function () {
    Log::debug('サービスコンテナへのバインドです');
    return new SampleClass();
})

なお、サービスコンテナにバインドしていない状態で解決する場合でも条件はありますがインスタンスは生成することができます。その際は単純にインスタンスを生成するだけ。

STEP
サービスコンテナが解決したインスタンスを取得する
// makeメソッドによる解決
$sample1 = app()->make(SampleClass::class);

// appヘルパ関数による解決
$sample2 = app(SampleClass::class);

前半は

  • サービスコンテナの概要
  • バインド
  • 解決

と、サービスコンテナの(超)基礎的な内容について解説していきました。

後半は以下の内容について解説して、サービスコンテナについても理解をさらに深めていきます。

  • DI/依存性の注入
  • ファサード(Facades)

特にDI/依存性の注入に関してはサービスコンテナの大きな機能であり、

あるクラスと依存関係にあるクラスのインスタンスをよしなに管理してくれる

について解説していきます。

この前編の内容がしっかり頭に入っている状態で後編を読んだ方が理解度は上がるのでまずはこの記事の内容をしっかり理解して後編を読んでみてください。

参考記事

Laravelを学ぶオススメ教材

定番の青本です。これからLaravelを学習したいという方全員にオススメできる入門書です。
今買うなら上記リンクから飛べるLaravel6対応版の青本を強くオススメします。

青本と同じ著者が執筆した技術書で青本の次に読むとLaravelの理解がかなり深まります。
Laravelの基礎は身についた方にオススメです。

現役Laravelエンジニアのレベルアップに最適な技術書です。
サービスコンテナ、レイヤードアーキテクチャ、テストなど実践的な内容が幅広く学べます。
(難しい話もあるのでそこまで初心者向きではないです)

“頭おかしい”プログラミングスクール『やんばるエキスパート』

内定者続出のエンジニア転職特化オンラインコミュニティ『転職クエスト』

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

この記事を書いた人

上場グループのHRTechベンチャーで働くWebエンジニアです。
新卒で入社した大手重工メーカを4年で退職し、2020年4月からエンジニアとキャリアチェンジしました。

仕事ではTypeScript/Vue.js(Nuxt.js)/Laravelを主に使っています。

プログラミングスクールの講師やデザイン関連のお仕事もさせてもらっています。

神戸で「つながる勉強会」という勉強会を月1で運営しています。
https://tsunagaru-kobe.connpass.com/

お仕事のご依頼、ご相談はお問い合わせページもしくはTwitterのDMからお願いします。

コメント

コメントする

目次
閉じる