LoginSignup
327
232

More than 1 year has passed since last update.

【Laravel】サービスコンテナとは?2つの強力な武器を持ったインスタンス化マシーン。簡単に解説。

Last updated at Posted at 2020-03-09

はじめに

サービスコンテナは、
Laravelのコアとなる機能で
Laravelのめちゃくちゃ便利で魔法のような仕組みを
実現してくれているものです。

しかし、普段Laravelを使っていても
簡単なアプリケーションを作るうえではあまり意識しないところかもしれません。
(意識しなくていいようになってる)

このサービスコンテナについて理解すると、
より便利に自分でカスタマイズしたりできるし、
何より面白いです。

Laravelのサービスコンテナとは何なのかについて
調べましたので、簡単に解説します。

サービスコンテナとは

サービスコンテナとは、
一言でいうと
「クラスをインスタンス化してくれるマシーン」
というイメージ。
Untitled Diagram-Copy of Page-3.png

具体的なコードで見てみます。

ClassAをインスタンス化するとき、
普通はこのようにnewしますね。

ClassA.php
class ClassA
{
    //
}
Main.php
$classA = new ClassA();

サービスコンテナを使ってインスタンス化する場合はこのようになります。

Mail.php
$classA = app()->make(ClassA::class);

このapp()関数でサービスコンテナを取得し、
そのサービスコンテナのmake()メソッドでインスタンス化しています。

$classAに入っているのは
先ほどnewしたときと全く同じです。
ただClassAをインスタンス化してくれました。

「newと同じならそんな機能いらなくね?」
と思ったかもしれませんが、
もちろんそれだけではありません。

サービスコンテナは、
ある2つの強力な武器(便利機能)を持った
「クラスをインスタンス化してくれるマシーン」
です。

その2つの便利機能とは
【1】依存解決してくれる
【2】インスタンス化の方法をカスタマイズできる
です。

Untitled Diagram-Page-3.png

この2つの武器がどういうものなのか、
見ていきます。

【1】依存解決

「依存解決」っていったい何なのかというと、
「コンストラクタで型宣言(タイプヒンティング)したクラスを、自動でインスタンス化して渡してくれる」
ということです。

具体例をコードで見てみます。

ClassAとClassBがあり、
ClassAのコンストラクタでClassBを型宣言して受け取るようになっています。

ClassA.php
class ClassA
{
    public function __construct(ClassB $classB)
    {
        \Log::info('ClassAインスタンス化完了');
    }
}
ClassB.php
class ClassB
{
    public function __construct()
    {
        \Log::info('ClassBインスタンス化完了');
    }
}

※インスタンス化されたことを確認するため、ログを出力するようにしています。

このClassAをインスタンス化するとき、
普通ならこのようにしますね。

Main.php
$classB = new ClassB();
$classA = new ClassA($classB);

// laravel.log
// local.INFO: ClassBインスタンス化完了  
// local.INFO: ClassAインスタンス化完了  

まずClassBをnewでインスタンス化して、
そのClassBインスタンスを引数に渡してClassAをnewでインスタンス化します。

この手順を踏まずに
引数を無視していきなり
ClassAをnewでインスタンス化しようとすると、
もちろんエラーが発生します。

Main.php
$classA = new ClassA();

// laravel.log
// local.ERROR: Too few arguments to function ClassA::__construct()

では次に、サービスコンテナを使ってみましょう。
サービスコンテナを使えば、
事前にClassBをインスタンス化する必要はありません。

いきなりClassAをインスタンス化しちゃいましょう。

Main.php
$classA = app()->make(ClassA::class);

// laravel.log
// local.INFO: ClassBインスタンス化完了  
// local.INFO: ClassAインスタンス化完了  

すると、エラーも出ないし
ちゃんとClassAの中でClassBがインスタンス化されています。

これは、サービスコンテナが
「ClassAをインスタンス化するぞ」
「と思ったら、コンストラクタでClassBが型宣言されてるな」
「じゃー先にClassBをインスタンス化しよう」
「ClassBのインスタンス化完了!」
「このClassBインスタンスを引数に渡して、ClassAをインスタンス化しよう」
「ClassAインスタンス化完了!」
ってことをしてくれているわけです。

便利ですね。

さらに、
この依存解決は再帰的に行ってくれます。

つまり、
このClassBがさらにClassCをコンストラクタ引数に持っていたとしても、
さらにClassD、ClassEと入れ子になっていたとしても
全部自動でインスタンス化してくれるというわけです。

ClassA
class ClassA
{
    public function __construct(ClassB $classB)
    {
        \Log::info('ClassAインスタンス化完了');
    }
}
ClassB
class ClassB
{
    public function __construct(ClassC $classC)
    {
        \Log::info('ClassBインスタンス化完了');
    }
}
ClassC
class ClassC
{
    public function __construct()
    {
        \Log::info('ClassCインスタンス化完了');
    }
}
Main.php
$classA = app()->make(ClassA::class);

// laravel.log
// local.INFO: ClassCインスタンス化完了  
// local.INFO: ClassBインスタンス化完了  
// local.INFO: ClassAインスタンス化完了  

これがサービスコンテナの2つの武器のうちの1つめ
「依存解決」です。

make()メソッドの中で具体的にどうやって依存解決がされているのか?
というさらに深い部分に関しては
別の記事で解説しています。
【Laravel】引数でタイプヒントしただけでインスタンスがもらえるのはなぜ?Laravelの魔法を解明してみる。
面白いので興味があれば見てみてください。

【2】インスタンス化方法のカスタマイズ

次に、2つ目の武器
「インスタンス化方法のカスタマイズ」
について解説します。

インスタンス化方法のカスタマイズを定義するには、
サービスコンテナのbind()メソッドを使います。

app()->bind('呼び出しキーワード', 'インスタンス化方法');

この様に第1引数に、インスタンス化する際の「呼び出しキーワード」を入れ、
第2引数に、インスタンス化方法を記述します。
 
 
まずはとりあえず具体例を見てみましょう。

ClassX.php
class ClassX
{
    public $foo;

    public function __construct()
    {
        \Log::info('ClassX instantiated');
    }
}

$fooプロパティを持っているClassXです。

このClassXのインスタンス化方法をカスタマイズします。

Main.php
app()->bind(ClassX::class, function () {
    $classX = new ClassX();
    $classX->foo = 'bar';
    return $classX;
});

「呼び出しキーワード」がClassX::class1

$classX = new ClassX();
$classX->foo = 'bar';
return $classX;

となっています。

ClassXをインスタンス化するとき、
プロパティ$fooに文字列barをセットする。
というカスタマイズですね。

この状態でインスタンス化するとこうなります。

Main.php
$classX = app()->make(ClassX::class);

\Log::info($classX->foo);

// laravel.log
// local.INFO: ClassXインスタンス化完了
// local.INFO: bar

ClassXがインスタンス化され、$fooに文字列barがちゃんと入っていますね。

この例のカスタマイズ方法は、
プロパティに文字列を入れる。というだけでしたが、
他にもなんでもできます。

例えば、キーワード「ClassX::class」で呼ばれたら、
ClassYのインスタンスを返す。
なんていうこともできます。

Main.php
// カスタマイズ方法定義
app()->bind(ClassX::class, function () {
    return new ClassY();
});

// 「ClassX::class」キーワードでインスタンス化
$classX = app()->make(ClassX::class);

// laravel.log
// local.INFO: ClassYインスタンス化完了

イメージとしては、
サービスコンテナが
「キーワード」と「インスタンス化方法」の対応表を持っていて、
make()で呼び出されるたびに
その対応表を確認してインスタンス化してくれているという感じです。
Untitled Diagram-Page-3 (1).png

そして、app()->bind()メソッドを使うことで、
その対応表に追記していっているイメージです。

ちなみに、
このbind()を使って対応表に追記するこの行為を
「結合」といいます。
 
 
上記の例はすごく簡単でシンプルなものでしたが、
結合の方式はもっといろいろあります。

例えばこんなの。

Main.php
app()->when(Japan::class)
    ->needs(Car::class)
    ->give(function () {
        return new Toyota();
    });
        
app()->when(America::class)
    ->needs(Car::class)
    ->give(function () {
       return new Benz();
    });

これは、呼び出し元のクラス(make()を使った場所)
によって、インスタンス化するクラスを変えるというカスタマイズ方法。

app()->when(Japan::class)
Japanクラスの中で
->needs(Car::class)
Car::classのキーワードでmake()したら
->give(function () {return new Toyota();});
Toyotaのインスタンスを返す。

app()->when(America::class)
Americaクラスの中で
->needs(Car::class)
Car::classのキーワードでmake()したら
->give(function () {return new Benz();});
Benzのインスタンスを返す。

という設定になっています。
 
 
あとはこんなのとか。

Main.php
app()->bind(CarInterface::class, function () {
    return new CarClass();
});

これは、CarInterfaceというインターフェース名で呼び出したら、
返却するインスタンスはCarClassになるように設定しています。

ちなみに、この
インターフェース名と具象クラスの対応を定義する場合は下記のような
省略記述が可能です。

Main.php
app()->bind(CarInterface::class, CarClass::class);

 
 
あとはすごく重要な機能として、シングルトンによる結合を定義することもできます。

Main.php
app()->singleton(ClassX::class, function () {
    return new ClassX();
});

シングルトンとは何か?については解説しませんので、
他の方の記事などご参考にしてみてください。
 
 
サービスコンテナの2つ目の武器
「インスタンス化方法のカスタマイズ」
ですが、こんな風にいろいろと柔軟に使うことができます。
 
 
 
ちなみに、
この結合の定義はどこに書いてもいいのですが、
一般的にここに書くといいよ。と用意されているのが
「サービスプロバイダ」というやつです。

app/Providers/AppServiceProvider.phpは最初から用意されていて、
そのregister()メソッドに
これまで見たような結合の定義を書いておきます。

このサービスプロバイダは
Laravel起動時に最初に実行される仕組みになっているので、
ここに結合定義を書いておけば
まず最初に「対応表」にインスタンス化のカスタマイズ方法を
全部追記しておくことができるというわけです。

おわりに

サービスコンテナは
「クラスをインスタンス化してくれるマシーン」
であるということ。

そして強力な2つの武器
「依存解決」
「インスタンス化方法のカスタマイズ」
を持っているということ。
を解説していきました。

Laravelフレームワーク自体がこのサービスコンテナの機能を使いまくって動いていて、
実は、自分たちが知らないうちにこのサービスコンテナの恩恵をたくさん受けていたりします。

今回得た知識をもとにLaravelのソースコードを改めて読んでみると
色々発見があって面白いかもです。

Laravelのちょっと深いところを理解していきたいと思っている方、
これらの記事もおすすめですので是非読んでみてください。
(次に進む前に、LGTMしてもらえるとうれしいです)
魔法のようなLaravelも素のPHPに始まり素のPHPに終わる。Laravelのライフサイクルを簡単に解説。
【Laravel】サービスコンテナとは?2つの強力な武器を持ったインスタンス化マシーン。簡単に解説。
【Laravel】引数でタイプヒントしただけでインスタンスがもらえるのはなぜ?Laravelの魔法を解明してみる。
【Laravel】ファサードとは?何が便利か?どういう仕組みか?

参考

【Laravel】引数でタイプヒントしただけでインスタンスがもらえるのはなぜ?Laravelの魔法を解明してみる。
https://readouble.com/laravel/6.x/ja/container.html

  1. 「ClassX::class」と書くと、名前空間を含むクラスの完全修飾名を文字列で取得しています
    「インスタンス化方法」が

327
232
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
327
232