Pimpleのコードリーディングをしました。バージョンは3.2.3です。
このケースで追ってみます
<?php
require_once 'vendor/autoload.php';
use Pimple\Container;
$container = new Container();
$container['session'] = function ($c) {
return new Session();
};
var_dump($container['session']); // eval
$container['session'] = $container->factory(function ($c) {
return new Session();
});
コードリーディング
Containerクラス内ではいくつかのインスタンス変数が管理されています。
class Container implements \ArrayAccess
{
private $values = array();
private $factories;
private $protected;
private $frozen = array();
private $raw = array();
private $keys = array();
public function __construct(array $values = array())
{
$this->factories = new \SplObjectStorage();
$this->protected = new \SplObjectStorage();
foreach ($values as $key => $value) {
$this->offsetSet($key, $value);
}
}
\ArrayAccessをimplementsしてあるのでこれによって添字アクセスなどが可能になっています。
#offsetSet
ではvalues
やkeys
に値をセットしています。keysの値はtrueがセットされJavaでいうSetのような感じで扱われています。
public function offsetSet($id, $value)
{
if (isset($this->frozen[$id])) {
throw new FrozenServiceException($id);
}
$this->values[$id] = $value;
$this->keys[$id] = true;
}
#offsetGet
は以下のように定義されています。初回呼び出し時でClosureをセットしている場合は早期returnされず、$raw($this)
でClosureが評価されて評価された値がvalues
に再度セットされます。
さらに、raw
にClosure本体がセットされるので、factoryでセットしていなければ二回目以降の呼び出しは最初に生成したインスタンスがそのまま利用されることになります。
public function offsetGet($id)
{
if (!isset($this->keys[$id])) {
throw new UnknownIdentifierException($id);
}
if (
isset($this->raw[$id])
|| !\is_object($this->values[$id])
|| isset($this->protected[$this->values[$id]])
|| !\method_exists($this->values[$id], '__invoke')
) {
return $this->values[$id];
}
if (isset($this->factories[$this->values[$id]])) {
return $this->values[$id]($this);
}
$raw = $this->values[$id];
$val = $this->values[$id] = $raw($this);
$this->raw[$id] = $raw;
$this->frozen[$id] = true;
return $val;
}
factoryでセットしている場合はfactories
というSplObjectStorage
のオブジェクトにClosureがattachされます。
public function factory($callable)
{
if (!\method_exists($callable, '__invoke')) {
throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.');
}
$this->factories->attach($callable);
return $callable;
}
factoryでセットされた値に対して#offsetGet
が呼ばれるとisset($this->factories[$this->values[$id])
がtrueになりClosureを評価した値が返されます。
この場合、$raw
には何も保存されないため毎回この処理が走るため、結果として呼び出すたびに新しいインスタンスが生成されることになります。