目次

DIコンテナー - League/Container

Version 3.3 (MIT License)

y2sunlight 2020-04-18

定番ライブラリー に戻る

関連記事

リンク

テストプログラムの所在

{Project Folder}\test\league-container\

League/Containerについて

DIコンテナの主な目的はコントローラへの依存性の注入(DI)にあると思います。それはビジネスロジックであるサービスと、コントローラやビューとの結合性を如何に疎にするかによって開発効率、保守性やテスト容易性が決まるからに他なりません。しかし「ちょっとした機能のプログラムをPHPでサクサクと実装したい」のが目的のApricotにDIコンテナが果たして必要なのでしょうか。迷いましたが結果的には、ORMやリクエストルーターと同じくシンプルで軽量なものを選定して追加することにしました。現在ではDIコンテナはもはやソフトウェア開発にとって当たり前の部品なのかもしれません。

DIコンテナにはいくつかの候補があがりました。シンプルで軽量という時点で多機能で秀作なDIコンテナであるPHP-DIは除外されましたが、場合によっては選択してもよかったと思っています。そして次の2つが候補に残りました:

PimpleはSlimでも採用されいる軽量DIコンテナです。どちらにするか迷いましたが、最終的には、PHP-DIにも搭載されている Auto Wiring をサポートしているContainerの方を採用することにしました。Auto Wiring とはコンストラクター引数の型ヒントを調べることにより、オブジェクトとそのすべての依存関係を再帰的に自動的に解決する機能の事です。Auto Wiring については手動での簡易実装を考えていたので、その手間のかからないContainerを選びました。

尚、Containerについては、(Containerではあまりに一般的な呼称なので)誤解のないように、プロバイダ名を冠して「League/Container」と呼ぶ事にします。League(リーグ)の正式名称は The League of Extraordinary Packages で、この組織は最新のコーディング標準を使用して、しっかりとテストされたPHPパッケージを構築するために結束した開発者のグループのことです。

インストール

composer require league/container
Using version ^3.3 for league/container
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing psr/container (1.0.0): Loading from cache
  - Installing league/container (3.3.0): Loading from cache
Writing lock file
Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!
Note:
Eclipse起動中にパッケージを取得した場合は、プロジェクト・エクスプローラー内の[プロジェクト(apricote)]を右クリックして[リフレッシュ]を選択して下さい。また、新しく取得したパッケージのインテリセンスが有効にならない場合は、プロジェクトのビルトまたはクリーン&ビルドを行ってビルドリストの更新を行って下さい。

パッケージの取得が終わると composer.jsonrequire に以下が追加されます。

composer.json
{
    "require": {
        "league/container": "^3.3"
    }
}


テストプログラム

以下の例題は、コンストラクター・インジェクションを3つの方法で行ったものです。最初はDIコンテナーを使用しない場合、2つ目はDIコンテナーを使用した場合、最後にAuto Wiringを使ったものです。テスト用のコードはテストフォルダ(test\league-container\)に、作成します。この例は League/Containerのマニュアル に記載されている Auto Wiringの例題 を簡素化したものです。

Foo.php
<?php declare(strict_types=1);
 
namespace Acme;
 
class Foo
{
    /**
     * @var \Acme\Bar
     */
    public $bar;
 
    /**
     * @var \Acme\Baz
     */
    public $baz;
 
    /**
     * Construct.
     *
     * @param \Acme\Bar $bar
     * @param \Acme\Baz $baz
     */
    public function __construct(Bar $bar, Baz $baz)
    {
        $this->bar = $bar;
        $this->baz = $baz;
    }
}
 
class Bar
{
    // ..
}
 
class Baz
{
    // ..
}


【テスト1】DIコンテナを使用しない場合

DIコンテナーを使わずに、サービスをコントローラに手動で注入している例です。尚、例題ではComposerによるAutoloadを使用していないので、spl_autoload_register()で代替しています。

index1.php
<?php declare(strict_types=1);
require __DIR__.'/../../vendor/autoload.php';
 
// ComposerのAutoloadの代替
spl_autoload_register(function ($class)
{
    require __DIR__.'/Foo.php';
});
 
//-------------------------------------
// 手動によるコンストラクター・インジェクション
//-------------------------------------
$baz = new Acme\Baz;
$bar = new Acme\Bar;
$foo = new Acme\Foo($bar, $baz);
 
var_dump($foo instanceof Acme\Foo);           // true
var_dump($foo->bar instanceof Acme\Bar);      // true
var_dump($foo->baz instanceof Acme\Baz);      // true

結果

D:\usr\ws2019\apricot\test\league-container\index1.php:17:boolean true
D:\usr\ws2019\apricot\test\league-container\index1.php:18:boolean true
D:\usr\ws2019\apricot\test\league-container\index1.php:19:boolean true


【テスト2】DIコンテナを使用する場合

DIコンテナ― によるコンストラクター・インジェクションの例です。依存関係をクラスコンストラクターに渡すことは、依存性注入の最も簡単な方法です。League/Container ではコンストラクター・インジェクションの他に、セッター・インジェクションとクラスファクトリーによる依存性注入の方法もサポートしています。

index2.php
<?php declare(strict_types=1);
require __DIR__.'/../../vendor/autoload.php';
 
// ComposerのAutoloadの代替
spl_autoload_register(function ($class)
{
    require __DIR__.'/Foo.php';
});
 
//-------------------------------------
// DIコンテナによるコンストラクター・インジェクション
//-------------------------------------
$container = new League\Container\Container;
 
$container
    ->add(Acme\Foo::class)
    ->addArgument(Acme\Bar::class)
    ->addArgument(Acme\Baz::class)
;
$container->add(Acme\Bar::class);
$container->add(Acme\Baz::class);
 
$foo = $container->get(Acme\Foo::class);
 
var_dump($foo instanceof Acme\Foo);           // true
var_dump($foo->bar instanceof Acme\Bar);      // true
var_dump($foo->baz instanceof Acme\Baz);      // true

結果

D:\usr\ws2019\apricot\test\league-container\index2.php:25:boolean true
D:\usr\ws2019\apricot\test\league-container\index2.php:26:boolean true
D:\usr\ws2019\apricot\test\league-container\index2.php:27:boolean true


【テスト3】Auto Wiringを使用する場合

League/Containerは Auto Wiring 機能をサポートします。これは、コンストラクター引数の型ヒントを調べることにより、オブジェクトとそのすべての依存関係を再帰的に自動的に解決する機能です。但し、注入できるのはオブジェクト型の変数だけです。

Auto Wiring はデフォルトで無効になっているので、有効にするにはコンテナデリゲートとして ReflectionContainer を登録しなけらばなりません。
index3.php
<?php declare(strict_types=1);
require __DIR__.'/../../vendor/autoload.php';
 
// ComposerのAutoloadの代替
spl_autoload_register(function ($class)
{
    require __DIR__.'/Foo.php';
});
 
//-------------------------------------
// Auto Wiring によるコンストラクター・インジェクション
//-------------------------------------
$container = new League\Container\Container;
 
// ReflectionContainerをデリゲートとして登録してAuto Wiringを有効にする
// (注)デフォルトでは、ReflectionContainerは、要求するたびにそれを解決します。
$container->delegate(
    new League\Container\ReflectionContainer
);
 
$foo = $container->get(Acme\Foo::class);
 
var_dump($foo instanceof Acme\Foo);           // true
var_dump($foo->bar instanceof Acme\Bar);      // true
var_dump($foo->baz instanceof Acme\Baz);      // true

結果

D:\usr\ws2019\apricot\test\league-container\index3.php:23:boolean true
D:\usr\ws2019\apricot\test\league-container\index3.php:24:boolean true
D:\usr\ws2019\apricot\test\league-container\index3.php:25:boolean true


【テスト4】Auto Wiringを使用する場合(キャッシュ有効)

デフォルトでは ReflectionContainer は、要求の度にそれを解決しようとします。ReflectionContainer でキャッシュ機能を有効にするには以下のようにcacheResolutions()を使用します。

index4.php
<?php declare(strict_types=1);
require __DIR__.'/../../vendor/autoload.php';
 
// ComposerのAutoloadの代替
spl_autoload_register(function ($class)
{
    require __DIR__.'/Foo.php';
});
 
//-------------------------------------
// Auto Wiring によるコンストラクター・インジェクション
//-------------------------------------
$container = new League\Container\Container;
 
// ReflectionContainerのキャッシュを有効にするに、以下のようにします
$container->delegate(
    (new League\Container\ReflectionContainer)->cacheResolutions()
    );
 
$fooOne = $container->get(Acme\Foo::class);
$fooTwo = $container->get(Acme\Foo::class);
 
var_dump($fooOne === $fooTwo); // true

結果

D:\usr\ws2019\apricot\test\league-container\index4.php:23:boolean true