CakePHP4.1に導入されるかもしれないDIコンテナ「phpleague/container」を試す

本記事は [CakePHP Advent Calendar 2019](https://qiita.com/advent-calendar/2019/cakephp) の19日目のエントリーになります。

## はじめに

先日、待ちに待ったCakePHP4.0.0が [正式リリース](https://bakery.cakephp.org/2019/12/15/cakephp_400_released.html) されましたね :clap:

そして、気が早いのですが4.0系がリリースされたということは、次の大きなアップデートは4.1系のリリースということになります。（本当に気が早くてごめんなさい）

そんなこともあり早速 [4.1のロードマップ](https://github.com/cakephp/cakephp/wiki/4.1-Roadmap) をチラ見してみたのですが [Application](https://github.com/cakephp/cakephp/wiki/4.1-Roadmap#application) の欄に以下のような記述があり、何やら面白そうな変化が起きそうだなと感じました。

> - Experimental support for PSR11 compliant dependency injection container. Potential libraries that we could use are:
  - phpleague/container - https://container.thephpleague.com/3.x

日本語に訳すと「PSR11準拠のDIコンテナを試験導入するかも。`phpleague/container`というライブラリに可能性を感じてるやで。」といった感じでしょうか。

というわけで、本記事ではCake4.1に導入されるかもしれないDIコンテナライブラリである [phpleague/container](https://container.thephpleague.com/3.x) を試してみたいと思います。

### 免責事項
- DIやDIコンテナそのものの説明は行いません。
  - インターネットや書籍に素晴らしい記事や文献があるのでそちらをご参照ください。 

## インストール
composerでサクッとインストール出来ます。ちなみに、2019年12月時点では3.3.0がインストールされます。

```bash
$ composer require league/container
```

## クライアントとサービスクラスの作成

まずはクライアントクラス（オブジェクトが注入されるクラス）とサービスクラス（注入対象となるオブジェクトのクラス）を作成します。よくある`Constructor Injection`です。

```php:basic.php
<?php declare(strict_types=1);

namespace Basic;

class Client
{
    /**
     * @var Service
     */
    public $service;

    /**
     * Constructor Injection
     *
     * @param Service $service
     */
    public function __construct(Service $service)
    {
        $this->service = $service;
    }
}

class Service
{
}
```

## DIコンテナの作成

準備が整ったので`phpleague/container`を利用してDIコンテナを作成します。

```php:basic.php
// DI Container
require 'vendor/autoload.php';

use League\Container\Container;

$container = new Container;

$container->add(Service::class);
$container->add('client_alias', Client::class)->addArgument(Service::class);

$client1 = $container->get('client_alias');
$client2 = $container->get('client_alias');

var_dump($client1 instanceof Client); // true
var_dump($client1->service instanceof Service); // true
var_dump($client1 === $client2); //false
```

`add()`メソッドを利用して登録を行い、`get()`メソッドを利用してインスタンスを取得します。エイリアス名の設定なども簡単に出来ます。

## インターフェイスの利用

実際にDIコンテナを利用する時は、インターフェイスを利用して具象クラスを隠蔽することが多いと思いますので、そのパターンも簡単に書いてみたいと思います。

```php:advance.php
<?php declare(strict_types=1);

namespace Advance;

class Client
{
    /**
     * @var ServiceInterface
     */
    public $service;

    /**
     * Constructor Injection
     *
     * @param ServiceInterface $service
     */
    public function __construct(ServiceInterface $service)
    {
        $this->service = $service;
    }
}

interface ServiceInterface
{
}

class CakeService implements ServiceInterface
{
}

class LaravelService implements ServiceInterface
{
}

// DI Container
require 'vendor/autoload.php';

use League\Container\Container;

$container = new Container;

$container->add(ServiceInterface::class, CakeService::class);
$container->add(Client::class)->addArgument(ServiceInterface::class);

$client = $container->get(Client::class);

var_dump($client instanceof Client); // true
var_dump($client->service instanceof ServiceInterface); // true
var_dump($client->service instanceof CakeService); // true
var_dump($client->service instanceof LaravelService); // false
```

インターフェイスを利用した場合でも同じようにサクッとDIコンテナを作成することが出来ます。

## まとめ

数行の記述で簡単に[^1]DIコンテナが実現出来ました。今回は本当に試してみたレベルですが、もう少し深堀りして、CakePHPでこのライブラリがどのように使われるのか想像するのも面白そうです。[^2]ちなみに [ドキュメンテーションサイト](https://container.thephpleague.com/) がかっこよくてテンションが上りました :relaxed:

というわけで、2020年もCakePHPの動向から目が離せません！

[^1]: 簡単と言いつつドキュメントに書いてあるままやっていたら`Autoload`の設定にハマって、作者の方に質問するなどしていました。（即レスで対応してくれて神でした:pray:）
[^2]: Issueを細かくWatchしていないので、もしかしたら既に議論されているかもしれません。