PHP
laravel
DI
サービスコンテナ

もう怖くない DIだって知らなくていい こんなにカンタンだから今日から Laravelのサービスコンテナ を使ってみて!

b.jpg

それは新しい new だった

そういえばちゃんと学んだことがなかったサービスコンテナ。
正直、ずっと、知らなくてもなんとかなるしまぁそんなのもあるよね、あーここはどうしてもそういうのがいるからとりあえず動く程度には使っておくか、という「軽いおつきあい」程度でした。
そもそも公式のドキュメントが何言ってるのか全然わかんない。
DIってなに?(書いてない)
サービスプロバイダのこと?始めるのに手順が多くてしんどい……。
と思っていましたが、改めて学んで整理してみたら、全然そんなことなかった。
超カンタンだった。
ということを、僕のように「難しそうだ」と躊躇している人に知ってもらいたい!と思って筆を執りました。

前提

  • クラスとインスタンスの違いがわかる
  • オートローダー(PSR4)がわかる。
  • Laravelでクラスの増やし方、どこに書くかはわかる。
  • 公式ドキュメントの「サービスコンテナ」を読んだけどさっぱりだった。
  • DIってなにそれおいしいの?

目標

  • DIを説明せずにサービスコンテナを理解する
  • 新しいクラスを書き始めるための「最小限のステップ」を定義してみる
  • 公式ドキュメントに書いてある手順もできるだけ省略する!
  • え?これだけでいいの!? じゃあ僕も私も今日からやってみよう、と思ってもらえる

結論

サービスコンテナはクラスのインスタンスを作ってくれるマシーン。
つまり、PHPの new を機能拡張したもの。

基本的な利用手順

(...)はオプションです。省略可能な手順。

  1. サービス化したいクラスを作る
  2. (サービスプロバイダを作って app.php に登録する|AppServiceProviderを使う)
  3. (registerにインスタンス化する方法を定義しておく)
  4. (bootでサービス同士の依存関係を解決する)
  5. (ファサードを定義する & app.phpに登録する)
  6. サービスコンテナにインスタンスを提供してもらう

あれ? 省略可? ということは…

  1. サービス化したいクラスを作る
  2. サービスコンテナにインスタンスを提供してもらう

これだけで良いんですか?
そうみたいです。ちょっと詳しく見てみましょう。

サービスコンテナ・サービスプロバイダとは?

サービスプロバイダとは?

Laravelをやっているとこちらの単語のほうがよく登場しますが、これは 「インスタンスの作り方」を「まとめて」書いておくのに便利なツール で、あってもなくてもかまいません。無くてもいいなら端折ってしまえ!

というわけで、サービスプロバイダは要りません。しばらく忘れてください。

サービスコンテナとは?

一般的にはDIコンテナとかIoCコンテナと言われているものですが、公式ドキュメントでは「サービスコンテナ」と呼ばれています(この話は長くなりそうなので末尾のポエムにて…)。

個人的に、サービスコンテナは オートローダに似たもの だと思っています。
オートローダは、規則に従ってファイルを置いておけば、明示的にincludeしなくても、どこからでもそのクラスを使えるようにしてくれるもの。それをもっと高機能で効率的にして、クラスではなく「インスタンス」を提供してくれるのがサービスコンテナ。並べて比べるとこのようなイメージ。

オートローダ サービスコンテナ
要求するときのキーワード クラス名 クラス名など
提供してくれるもの クラスが定義されたソースファイル クラスのインスタンス
$obj = new SomeClass; $obj = app()->make('SomeClass');

サービスコンテナにインスタンスを提供してもらう方法 その1

先程の表にあるように、PHPでは、とあるクラス App\SampleClass のインスタンスが欲しいと思ったとき、このように書くのはみなさんご存知の通り。

$sampleobject = new App\SampleClass;

これと同じこと をLaravelのサービスコンテナにお願いするときは、こう書きます。

$sampleobject = app()->make('App\SampleClass');

2つポイントがあります。

  • このコードは、どこにでも書けます。
  • このコードを書くのに特別な準備は不要です。

そう。ちょうど、PHPの new がどこにでも書けるのと同じように。

もう少し解説します。
app()はグローバル関数として定義されているので、Laravelアプリならクラスの中でも外でも、いつでもどこでも呼ぶことができます。で、呼ぶと何が出てくるかというと、サービスコンテナのインスタンスが出てきます。app() = サービスコンテナ。
また、これを使うのに、サービスプロバイダになにか書かなきゃいけないの? いいえ、何も書かなくても使えます。クラスがきちんと定義されているなら(PHPのnewでインスタンスが作れるなら)すぐに実行できます。だからちょうど、今まで new を使っていたところはほとんどこれで置き換えができます。

これが サービスコンテナにインスタンスを提供してもらう方法。
サービスコンテナって、new なのか!?
そうそう、ちょっと乱暴に理解するとそんな感じです。

ちなみに先程の書き方は、他にもこのように書くことができます。すべて同じ結果です。それぞれ、こんな意図でしょうか。

$sampleobject = app()->make('App\SampleClass'); // コンテナがインスタンスを作るよ

$sampleobject = Illuminate\Container\Container::getInstance()->make('App\SampleClass'); // コンテナを取ってきてインスタンスを作るよ

$sampleobject = app()->resolve('App\SampleClass'); // コンテナがクラスの依存を解決するよ
$sampleobject = resolve('App\SampleClass'); // クラスの依存を解決するよ

$sampleobject = app('App\SampleClass'); // インスタンスくれ
$sampleobject = Illuminate\Foundation\Application::getInstance()->make('App\SampleClass'); // アプリ本体を取ってきてインスタンスを作るよ

最後の2行がなかなか衝撃的(笑)。もはや「Laravel=サービスコンテナ」ということ※。Laravelやっているなら、知らないわけにはいかないってことじゃないですか!

事実です。 class Application extends Container です...。

サービスコンテナにインスタンスを提供してもらう方法 その2 自動注入

さて、app()->make は new である、と説明させていただきました。
そのとき疑問に思いませんでしたか?

コンストラクタに引数がある場合はどうやって渡すんですか?と。
ここで、Laravelサービスコンテナを最強たらしめる最も強力な武器が登場します。

コンストラクタインジェクション

たとえば、実践的な例だと、HTTP通信をしたいクローラを作る場合。
コンストラクタで、クラスが使うHTTPモジュールを渡して使うことがあります。
今までのPHPだったらこのように書いていました。

$moduleobject = new App\ModuleClass();
$sampleobject = new App\SampleClass($moduleobject);

これと同じこと をLaravelのサービスコンテナにお願いするときは、こう書きます。

$sampleobject = app()->make('App\SampleClass');

なんと。さっきと同じです。
new App\ModuleClass()はどこに行った!?

ここです。

App/SampleClass.php
class SampleClass {
    public function __construct(App\ModuleClass $moduleclass)
    {
        // do something
    }
}

サービスコンテナは、makeするとき、そのクラスのコンストラクタの引数をチェック。そしてタイプヒントで指定されたクラスを自動的に new してコンストラクタに渡してくれます。これがコンストラクタインジェクション。欲しいクラスをmakeするだけで、それが必要なクラスを自動的にnewして集めてきてくれるわけです。もちろん、

  • このコードは、どこにでも書けます。
  • このコードを書くのに特別な準備は不要です。

渡してもらうクラスも、渡されるクラスも、どんなクラスでも大丈夫です。

まとめ

もう一度登場していただきましょう。
サービスコンテナを使う手順は、これだけです。

  1. サービス化したいクラスを作る
  2. サービスコンテナにインスタンスを提供してもらう

この「サービスコンテナにインスタンスを提供してもらう」はほとんどの場合、コンストラクタインジェクションで実装することになることを踏まえてもう少し詳しく書くと、このようになります。

  1. サービス化したいクラスAを作る
  2. そのサービスを使いたいクラスBのコンストラクタで、タイプヒントにそのクラスAを入れる
  3. そのクラスBもサービスコンテナにインスタンスを作ってもらう

例えば、WEBを取得してパースするクローラを作ろうかなと思ったとき。
HTTP通信するモジュールが欲しいな。
取ってきたHTMLをパースするモジュールも。
あとデータを保存するモジュール…。
というわけで、下記3つのクラスを3つのファイルに作りました。

App/HttpConnecter.php
App/HtmlParser.php
App/DataStorage.php

次に、Crawlerの実装はどうなるかというと

Crawler.php
class Crawler
{
    protected $connector;
    protected $storage;
    protected $parser;

    public function __construct( App\HttpConnecter $connector, App\DataStorage $storage, App\HtmlParser $parser )
    {
        $this->connector = $connector;
        $this->storage = $storage;
        $this->parser = $parser;
    }

    public function crawl( )
    {
        // ... $this->connector などが使える
    }

そして、使うときは、こうするか……

$crawler = app('Crawler');

crawler もコンストラクタに入れてもらう。

class TaskScheduler
{
    protected $crawler;

    public function __construct( Crawler $crawler )
    {
        $this->crawler = $crawler;
    }

これが、サービスコンテナの基本的な機能とその使い方。
驚くほどカンタン、というのは、誰を隠そう、僕自身の感想です♬

これを読んだ人も試しにやってみて、その手軽さに驚いていただけると嬉しいです。

ちょっとまてカンタンすぎるぞ!

そうです、すみません。確かにちょっと端折りすぎました。
まずは基本機能がどれだけカンタンかを知ってほしかったんです!

だから僕も心を鬼にして、初稿の半分以上をボツにしてテーマを絞りました。

というわけで、次回はそのボツ原稿の中から大胆にぶっとばした「サービスプロバイダ」と最低限知っておいたほうが良い「結合」について、シングルトンを題材に書いてみました。

第1回 サービスコンテナ 「それは新しい new だった」
第2回 サービスプロバイダ 「シングルトンはたった1行」

その勢いで、スタティック地獄を抜け出すためのファサード講座とか、マニアックに技術を追求するメソッドインジェクション解体新書とか、LaravelをLaravelたらしめるクラスだけじゃなくてなんでも載っかるサービスコンテナの真実、とか書いてみたいですね。(いつかそのうち…)(チカラが及べば……)

ポエム:DIってなんだ?

広告をちょっとかじってきた経験があるので「ネーミングはホントに大事だな」とよく意識が向いてしまいます。名前がおかしいと、それを聞いた人が理解しにくいだけでなく誤解を招くことがある。逆にそうならない名前をつけてあげないといけない。

その典型例が「DI・依存注入」という名前。
DIってなんだ? そもそも名前の意味がわからない。dependencyもinjectionも依存も注入も聞き慣れなくてイメージできない。なんでこんな名前かっていうとね……と説明を始めなきゃいけなくて、その説明だけでなんだかわからない難しいもののような気がする。
でも実はDIは難しくない。悪いのは、名前。

もう1つ話をややこしくしているとしたら、DIっていうのは単なるデザインパターン(設計の考え方)であって、それを具体的に解決してくれる何か、いわば「DI解決マシーン」が必要なんですよね。その「DI解決マシーン」の1つが、PHPのLaravelに実装されている「サービスコンテナ」。

出来上がったそれはDIをとてもエレガントに解決してくれたから、DIは何かを考える機会が減っていって、だんだん「DI」という名前は必要なくなっていくんじゃないかな。

ものすごく極端に言うと、 「洗濯機」という単語がまだ世の中になくて、代わりに「洗剤注入」と呼んでいるような状況 なのかなー。きっと他にももっとすごい「洗濯機」があるかもしれないし、この先登場するかもしれない。そうなったらきっと「洗剤注入」という言葉は使われなくなっている、かもしれない。