“一步一步走,心急吃不来热豆腐呦” —郭X村口大爷
这节我们继续读上节没读完的代码。上节我们说到,应用主体传入了一个属性container,属性值为容器实例。然后,我们又了解了该容器类继承自Pimple容器实现自ContainerInterface接口,且这个容器接口继承自PsrContainerInterface。所以,Slim默认容器是符合PSR7规范的。之后,我们又从Container类构造方法开始研读,
/**
* 创建新容器
* Create new container
*
* @param array $values The parameters or objects.
*/
public function __construct(array $values = [])
{
// 继承父类的构造方法
parent::__construct($values);
// 获取配置文件中settings配置项的值
$userSettings = isset($values['settings']) ? $values['settings'] : [];
// 注册Slim需要的一些默认服务
$this->registerDefaultServices($userSettings);
}
第一行代码是
parent
::
__construct
($values);
/**
* 实例化容器
* Instantiates the container.
*
* Objects and parameters can be passed as argument to the constructor.
*
* @param array $values The parameters or objects
*/
public function __construct(array $values = array())
{
// 实例化factories,protected属性,且赋值为\SplObjectStorage()实例化对象
// SplObjectStorage提供了从对象到数据的映射,或者忽略数据提供对象集
$this->factories = new \SplObjectStorage();
$this->protected = new \SplObjectStorage();
// 将传入的配置项整合到容器对象
foreach ($values as $key => $value) {
$this->offsetSet($key, $value);
}
}
// 注册Slim需要的一些默认服务
$this->registerDefaultServices($userSettings);
查看之...
private function registerDefaultServices($userSettings)
{
$defaultSettings = $this->defaultSettings;
/**
* settings属性赋值集合对象,该对象是defaultSettings和userSettings的合并数组经处理后产生的集合对象
* This service MUST return an array or an
* instance of \ArrayAccess.
*
* @return array|\ArrayAccess
*/
$this['settings'] = function () use ($userSettings, $defaultSettings) {
return new Collection(array_merge($defaultSettings, $userSettings));
};
// 注册Slim默认服务
$defaultProvider = new DefaultServicesProvider();
$defaultProvider->register($this);
}
$this['settings']赋值为Slim\Collection类实例。
// 依赖注入容器
$container = $app->getContainer();
/** * Enable access to the DI container by consumers of $app * * @return ContainerInterface */
public function getContainer(){
return $this->container;
}
getContainer()方法返回Container类对象,然后
// 在容器中注册Monolog日志组件
$container['logger'] = function($c) {
$logger = new \Monolog\Logger('my_logger');
$file_handler = new \Monolog\Handler\StreamHandler('../logs/app.log');
$logger->pushHandler($file_handler);
return $logger;
};
可看到以闭包形式传递一个对象给容器对象数组$container['logger']。且我们在之后使用时,可直接以属性方式调用该logger对象。这步是如何完成的呢?
$this->logger->addInfo("发生了一件有趣的事,并把它记录了下来”);
这和\ArrayAccess接口相关,并非实现了\ArrayAccess接口的类,只要给他的实例对象传入$test['a'] = 'hello',$test->a属性就可以获取到hello的,如果你要实现的话,会很遗憾的发现Undefined property: Test::$a…,而且即使你这样定义
Class Test implements ArrayAccess { ... }
$test = new Test;
$test['a'] = 'hello’;
echo $test['a’];
若没做任何处理,也不会有任何输出的。
实现\ArrayAccess必须实现这四个方法:
offsetExists($offset) //判断是否存在偏移位置
offsetGet($offset) //获取偏移量
offsetSet($offset, $value) //设置偏移量
offsetUnset($offset) //去除偏移量
之前我们已经了解到,它在处理一些配置项时,使用$this->values数组存储键值对,使用$this->keys数组存储判断值,记录该id是否已被values存储。
/**
* 设置参数或闭包对象
* Sets a parameter or an object.
*
* Objects must be defined as Closures.
*
* Allowing any PHP callable leads to difficult to debug problems
* as function names (strings) are callable (creating a function with
* the same name as an existing parameter would break your container).
*
* @param string $id The unique identifier for the parameter or object
* @param mixed $value The value of the parameter or a closure to define an object
*
* @throws FrozenServiceException Prevent override of a frozen service
*/
public function offsetSet($id, $value)
{
if (isset($this->frozen[$id])) {
throw new FrozenServiceException($id);
}
// 将赋值给values数组,并用keys数组记录
$this->values[$id] = $value;
$this->keys[$id] = true;
}
offsetGet是比较重要的函数,学习之
/**
* 得到一个参数或者对象
* Gets a parameter or an object.
* $id是一个唯一标识符
* @param string $id The unique identifier for the parameter or object
* 返回值为参数或对象
* @return mixed The value of the parameter or an object
*
* @throws UnknownIdentifierException If the identifier is not defined
*/
public function offsetGet($id)
{
// 不存在则抛出异常
if (!isset($this->keys[$id])) {
throw new UnknownIdentifierException($id);
}
// raw数组尚不知何时赋值,但!is_object($this->values[$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];
}
// factories也不知有何作用
if (isset($this->factories[$this->values[$id]])) {
return $this->values[$id]($this);
}
// 下面这几句解释了之前的疑惑,若值为闭包则将执行后的值赋给$val
// raw数组存储原生闭包函数,并在获取过程中冻结该$id,即fronzen数组存储冻结的id
// 最后返回闭包执行结果
$raw = $this->values[$id];
$val = $this->values[$id] = $raw($this);
$this->raw[$id] = $raw;
$this->frozen[$id] = true;
return $val;
}
由此知,在第一次执行闭包后,会将执行结果存储在raw数组中,下次就无需再次执行,直接根据id调用raw[]中的值。
/**
* 检查参数或对象是否设置
* Checks if a parameter or an object is set.
*
* @param string $id The unique identifier for the parameter or object
*
* @return bool
*/
public function offsetExists($id)
{
return isset($this->keys[$id]);
}
/**
* 去除参数或对象
* Unsets a parameter or an object.
*
* @param string $id The unique identifier for the parameter or object
*/
public function offsetUnset($id)
{
if (isset($this->keys[$id])) {
if (is_object($this->values[$id])) {
unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
}
unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
}
}