LoginSignup
1

More than 5 years have passed since last update.

Slim3にLeague/Containerを導入してみた話(とPSR-11)

Last updated at Posted at 2016-06-30

思ったより大変だった。

Slim3のデフォルトコンテナはPimple。非常にシンプルで機能を削ぎ落としたコンテナと言われている。ただし、Slim3ContainerInterface対応のコンテナなら使える、ということになっている。

そう言えばLeague/Containerも、ContainerInterfaceに対応している。だったら簡単に交換できるかなと思って、特にメリットが何かも考えずにやってみた。

ちなみにContainerInterfaceというは、ちょっと前からあるコンテナの使い方を統一するためのインターフェース。これをベースにPSR-11が議論されている。

Adapter作成

動かす方針としては、
League/ContainerPimpleのように動くアダプターを作成することで対応した。

ContainerInterfaceに対応

ContainerInterfaceを組み込むのは簡単。何しろgethasの2つしかメソッドがない。こんな感じ。

class Container implements ContainerInterface, \ArrayAccess {
    /**
     * @var \League\Container\Container
     */
    private $container;

    public function __construct($container) {
        $this->container = $container;
    }
    public function get($id) {
        return $this->container->get($id);
    }
    public function has($id) {
        return $this->container->has($id);
    }
}

早速使ってみる。が、

やっぱり動かなかった。

ArrayAccessに対応

原因は、Slim3内部で使われるサービスを設定するコードにあった。要するにSlim3のサービス・プロバイダーがPimple前提になっていた。

嫌な予感…

そのサービス・プロバイダーは、こんなコード。

$container['environment'] = function () {
    return new Environment($_SERVER);
};

つまり、Pimpleとして動かすには、ArrayAccessも実装する必要があったのである。

ちなみにArrayAccessというのは、PHPのオブジェクトを配列のように使えるようにするためのインターフェース。

class Container implements ContainerInterface, \ArrayAccess
{
    // 以下、ArrayAccess用のコード 

    public function offsetExists($offset) {
        return $this->container->has($offset);
    }
    public function offsetGet($offset) {
        return $this->container->get($offset);
    }
    public function offsetSet($offset, $value) {
        $this->container->add($offset, $value);
    }
    public function offsetUnset($offset) {
        throw new \BadMethodCallException;
    }
}

それでも、動かない。

動かすために

仕方ないので、コードを追いかけて、問題を探す。幾つかの挙動を変えることで対応することが出来た。

設定箇所:offsetSet

Slim\DefaultServiceProviderにあるregisterというメソッドで設定を行っている。コードとしては、こんな感じ。

$container['name'] = function($c){ /* オブジェクト返す */ };

配列として設定しようとすると、ArrayAccess::offsetSetが呼ばれる、という風にPHPは動く。

  • ポイントは「配列で設定すると「Singleton」として扱う」という前提だったこと。

確かに配列に設定するというメタファーなら、値は共有される気がする。

  • もう一つのポイントは「ファクトリ用のコーラブルの引数にコンテナが入ってくる」こと。

この2点を考慮して書き直すと、

    public function offsetSet($offset, $value)
    {
        $builder = $this->container->add($offset, $value, true);
        if ($builder && is_callable($value)) {
            $builder->withArgument($this);
        }
    }

となった。

addメソッドの第3引数にtrueを設定することでSingletonとして設定している。さらにコーラブルなら、引数に自分自身を設定している。

クラスの実体化:has

League/Containerの特徴は、コンテナ自身でクラスをnewできること。設定によっては、依存性を自動解決して注入することも可能である。

ところが、ファクトリなど設定されてないクラス名にたいしてhasで問い合わせるとfalseが返ってくる。

たしかに。設定されてないのだから、間違ってない。

    public function has($id)
    {
        if ($this->container->has($id)) {
            return true;
        }
        if (is_string($id) && class_exists($id)) {
            return true;
        }
        return false;
    }

として動かした。

Slim3用の設定:settings

まだ、動かん。

見れば、Slim3自体でもPimpleを継承してゴニョゴニョしている。その中で、デフォルト設定がある。これがないので動かなかった。ので、こんなコードを追加。

$container = new Container(new \League\Container\Container);
$container['settings'] = [
    'httpVersion'                       => '1.1',
    'responseChunkSize'                 => 4096,
    'outputBuffering'                   => 'append',
    'determineRouteBeforeAppMiddleware' => false,
    'displayErrorDetails'               => false,
];

確か、以上の修正で動かせた。

動かしてみた感想

学んだことは、コンテナ取り替えるの苦しい

コンテナを使う場合には標準がある。一方、設定する場合には標準がない。これを合わせこむのは面倒。今回のPimpleもLeague/Containerも比較的単純な方のコンテナ(だと思う)ので、複雑なコンテナ使ったら、大変だろうなと。

サービス・プロバイダー自作

League/Containerを使うと決めた時点で、サービス・プロバイダーも自作するべきだった、のだろうと思う。

ただ、Slim3が進化して必要なサービスが増えた場合、追従するのは大変。やはりSlim3謹製のサービス・プロバイダーを使うメリットは大きい。

PSR-11のServiceProvider

さて、こんな面倒な事を、PSRでは統一しようとしている。

どうやって?と思ってみてみたが、意外と簡単な方法があった。ちなみに、ここで開発(というか議論)が進んでいる様子(https://github.com/container-interop/service-provider

そのインターフェースとは…

interface ServiceProvider
{
    /**
     * @return callable[]
     */
    public function getServices();
}

これだけかい。すごい。

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
1