ログ日記

作業ログと日記とメモ

Zend\Di の依存関係ループを解消する

勘違いしていたので追記

SessionManager は 複数扱えないので、セッションを複数持つ場合は以下のようにして名前空間だけ分ける。

<?php
class SessionStorage{}
class SessionAdapter{
    public function __construct(SessionStorage $storage = null){
        $this->storage = $storage;
    }
}
class SessionManager{
    public function __construct(SessionAdapter $adapter){
        $this->adapter = $adapter;
    }
}

class AuthStorage{
    public function __construct(SessionManager $manager){
        $this->manager = $manager;
    }
    public function setName($name)
    {
        $this->name = $name;
    }
}
class AuthAdapter{
}
class AuthService{
    public function __construct(AuthAdapter $adapter){
        $this->adapter = $adapter;
    }
    public function setStorage(AuthStorage $storage){
        $this->storage = $storage;
    }
}

if (0):
$di = new \Zend\Di\Di();
$im = $di->instanceManager();
$im->addAlias('auth1', 'AuthService');
$im->setParameters('auth1',
                   array('storage' => 'storage1'));
$im->addAlias('storage1', 'AuthStorage');
$im->setInjections('storage1',
                   array('setName' => array('name' => 'name1')));

$im->addAlias('auth2', 'AuthService');
$im->setParameters('auth2',
                   array('storage' => 'storage2'));
$im->addAlias('storage2', 'AuthStorage');
$im->setInjections('storage2',
                   array('setName' => array('name' => 'name2')));

var_dump($di->get('auth1'));
var_dump($di->get('auth2'));

セッション名やlifetime, start, destroyは共有される。
これを分けるには、SessionManagerがPHPのsession_*関数を使ってセッション管理することをやめて独自Cookieを発行するようにならないと無理ぽい。

ここまで追記




複数セッションを使いたい場合にSessionManagerをエイリアス設定することが必要と書いた*1が、一つ一つ$di->get()せずに最初に依存関係を全部定義するやり方だとうまくいかなかった。

Uncaught exception 'Zend\Di\Exception\CircularDependencyException' with message 'Circular dependency detected: Zend\Session\SessionManager depends on Zend\Session\Storage\StorageInterface and viceversa'

エラーが出る。


これは、$instanceManager->setParameters や setInjections でパラメーター名を指定したらそれが依存関係解消中はずっと共有されることから起きる。
例えば

<?php
class Storage{}
class A {
    public function __construct(Storage $storage = null){
        $this->storage = $storage;
    }
}
class B {
    public function __construct(A $a){
        $this->a = $a;
    }
}
$di = new \Zend\Di\Di();
$im = $di->instanceManager();
$im->setParameters('B', array('storage' =>'Storage'));
var_dump($di->get('B'));

このように書くと、Aのstorageをインジェクションできる。
setParameterで指定しているのはBだが、Bが依存しているAに対するパラメーターも同時に指定したことになる。


これがどういうことになるかというと

<?php
class SessionStorage{}
class SessionAdapter{
    public function __construct(SessionStorage $storage = null){
        $this->storage = $storage;
    }
}
class SessionManager{
    public function __construct(SessionAdapter $adapter){
        $this->adapter = $adapter;
    }
}

class AuthStorage{
    public function __construct(SessionManager $manager){
        $this->manager = $manager;
    }
}
class AuthAdapter{
}
class AuthService{
    public function __construct(AuthAdapter $adapter){
        $this->adapter = $adapter;
    }
    public function setStorage(AuthStorage $storage){
        $this->storage = $storage;
    }
}

$di = new \Zend\Di\Di();
$im = $di->instanceManager();
$im->setInjections('AuthService',
                   array('setStorage' => array('storage' => 'AuthStorage')));
var_dump($di->get('AuthService'));

単純に書くと、これで依存関係のエラーになる。
AuthService#setStorage のパラメーターを storege = AuthStorage に設定しているつもりが、実は SessionAdapter#__construct の storageパラメーターの方まで設定されてしまうという罠。



解決方法は二通り。

  • 先に依存関係の深い部分を呼び出しておく
    • $di->get('AuthService') の前に $di->get->('SessionManager') しておく
    • するとstorage = AuthStorage が設定されていない状態で SessionManager が生成されるので依存関係が思った通りに解決する
  • setInjectionsは使わずに、setParametersでメソッド名とパラメーター番号を指定する
<?php
...
$di = new \Zend\Di\Di();
$im = $di->instanceManager();
$im->setParameters('AuthService',
                   array('AuthService::setStorage:0' => 'AuthStorage'));
var_dump($di->get('AuthService'));


この辺の動作はDi.phpの 537行付近や584行付近にある。


これはパラメーター名が同じことから起こっているので、もし自分で作ったクラスでこういうエラーが出たらパラメーター名を変えるのが手っ取り早いかもしれない。


ここから更に追記

addAliasしてエイリアスベースで設定していると、AuthService::setStorage:0 のような記述はできないようだった。
Di.phpのソースを追って出来そうだと判断したけれど、公式の使い方じゃないのかもしれない。