PHPでDIをやる(pimple)


PHPでDIをやりたくなったのでpimpleで簡単にやってみました。
これがベストかは全くわかりません。

対象読者

DIという単語を聞いたことがあるひと
Laravelでサービスコンテナという単語を聞いたことがあるひと

用語の整理(諸説あります)

DI
外部から必要なインスタンスを注入すること

DIコンテナ
DIを便利に行う仕組み、箱。
設定を書いておくとよしなにやってくれるかんじ。

余談ですが、Laravelでもこの仕組があり、それは「サービスコンテナ」と呼ぶらしい。

まず、DIでないパターン

<?php

class Message
{
    private $message;

    public function __construct()
    {
        $this->setMessage();
    }

    private function setMessage (): void
    {
        $this->message = 'こんにちは世界';
    }

    public function getMessage(): string
    {
        return $this->message;
    }
}

class Call
{
    private $message;

    function __construct()
    {
        // 必要なインスタンスを得るためにクラスの内部でインスタンス化しています。
        // 注入にはなっていません。
        $this->message = new Message();
    }

    function callMessage () {
        echo $this->message->getMessage();
    }
}

$call = new Call();

$call->callMessage();

// こんにちは世界

手動DI


class Call
{
    private $message;

    function __construct(Message $message)
    {
        $this->message = $message;
    }

    function callMessage () {
        echo $this->message->getMessage();
    }
}

// ここで手動でインスタンスを注入しています。
$message = new Message();
$call = new Call($message);

$call->callMessage();
// こんにちは世界

手動DIがたくさんになるとめんどいです。


class Call
{
    private $message;

    function __construct(
        Message $message,
        Message2 $message2,
        Message3 $message3
    )
    {
        $this->message = $message;
        $this->message2 = $message2;
        $this->message3 = $message3;
    }

    function callMessage () {
        echo $this->message->getMessage();
        echo $this->message2->getMessage2();
        echo $this->message3->getMessage3();
    }
}

$message = new Message();
$message2 = new Message2();
$message3 = new Message3();

$call = new Call($message, $message2, $message3);

$call->callMessage();

// こんにちは世界こんにちは日本ハローアメリカ

pimpleを使ったコンストラクタインジェクションの例。コンストラクタ内で注入します。

// インストール
$ ./composer.phar require pimple/pimple ~3.0
<?php

require_once __DIR__.'/vendor/autoload.php';

use Pimple\Container;

$container = new Container();

$container['Message'] = function ($c) {
    return new Message();
};

$container['Message2'] = function ($c) {
    return new Message2();
};

$container['Message3'] = function ($c) {
    return new Message3();
};

// ここでインスタンスをコンテナに入れている感じです。
$container['call'] = function ($c) {
    return new Call($c['Message'], $c['Message2'], $c['Message3']);
};

class Call
{
    private $message;

    function __construct(
        Message $message,
        Message2 $message2,
        Message3 $message3
    )
    {
        $this->message = $message;
        $this->message2 = $message2;
        $this->message3 = $message3;
    }

    function callMessage () {
        echo $this->message->getMessage();
        echo $this->message2->getMessage2();
        echo $this->message3->getMessage3();
    }
}

// この部分を端折ることができます。
//$message = new Message();
//$message2 = new Message2();
//$message3 = new Message3();
//
//$call = new Call($message, $message2, $message3);


$container['call']->callMessage();
// こんにちは世界こんにちは日本ハローアメリカ

設定の記述量が多くて、便利なのかという感じなのですが、別クラスに設定を書いておいて使いたいときに注入すればいいので便利だと思います。

副産物として、テストがしやすくなったり、インターフェイスをかませてインターフェイスを注入することでクラス同士を疎結合にして、保守性を上げるということができるようになるようです。

参考
PIMPLEA simple PHP
Dependency Injection Container
https://pimple.symfony.com/

フレームワークを作りながらLaravelのアーキテクチャを学ぶ / Learn Laravel's architecture while creating a framework
https://speakerdeck.com/fendo181/learn-laravels-architecture-while-creating-a-framework?slide=109