开头语:TP5.1容器是整个框架的核心之一,理解容器代码,会对阅读tp5.1代码有很大帮助,如果容器这一关没有学好,下面的代码会越看越懵逼。
要看懂容器这块,首先要有4个知识点储备: ArrayAccess, IteratorAggregate, Countable以及反射。这4块地方可以看手册,这里进行简要说明
ArrayAccess:提供将对象当成数组一样使用的能力。继承ArrayAccess需要实现其4个方法,下面将结合代码分析。
IteratorAggregate:提供像循环数组一样循环对像的能力,继承这个接口必须要实现getIterator这个方法,这个方法将返回一个ArrayIterator对象(对象必须传入一个数组,通常一般都是成员属性作为这个数组参数),这样foreach就会循环这个对象,实际上也就是循环这个成员属性数组的内容。
Countable:提供了对象使用count()函数的能力,继承Countable并且实现Countable的count()方法,在使用count(obj)时,会调用对象的count方法。
反射:反射API提供了类在运行时操作类内部的能力。使用反射主要是用来对类以及类的参数进行分析。
下面先看容器类继承ArrayAccess, IteratorAggregate, Countable的代码:
//如果是对象则绑定到注册池,如果是类名则放入类名池(bind池),如果是闭包也放入类名池(bind池),bind池是key(名称索引)-value(类名/闭包)
public function __set($name, $value)
{
$this->bindTo($name, $value);
}
//获取一个不存在或者私有变量时调用,这样可以将成员属性不写在代码里
public function __get($name)
{
return $this->make($name);
}
//检查一个不可被外部访问的成员属性是否设置
public function __isset($name)
{
return $this->bound($name);
}
//销毁一个不可被外部访问的成员属性的值
public function __unset($name)
{
$this->delete($name);
}
public function offsetExists($key)
{
return $this->__isset($key);
}
public function offsetGet($key)
{
return $this->__get($key);
}
public function offsetSet($key, $value)
{
$this->__set($key, $value);
}
public function offsetUnset($key)
{
$this->__unset($key);
}
//Countable
public function count()
{
return count($this->instances);
}
//IteratorAggregate,可以使用foreach遍历注册池中的对象
public function getIterator()
{
return new ArrayIterator($this->instances);
}
//var_dump调用时调用
public function __debugInfo()
{
$data = get_object_vars($this);
unset($data['instances'], $data['instance']);
return $data;
}
1.count()方法用来计算容器注册池中实例的个数
2.getIterator()方法用来遍历注册池的每一个实例
3.__debugInfo()方法用来自定义var_dump方法的内容,这个方法应该是用于放便调试。
4.ArrayAccess和魔术方法__set、__get、__isset()、__unset()一起搭配使用,比方说__set()调用场景是当一个私有属性name需要被设置时:$obj->name,会触发__set(),而搭配上
ArrayAccess,就可以这么用:$obj['name'] = xx; 因此ArrayAccess提供的是一个允许像使用数组那样,使用对象的能力。在这个例子中,__set()需要被写到对应的ArrayAccess方法中去,如代码中的offsetSet(),因为ArrayAccess中触发offsetSet()的就是这样的赋值形式__isset()。其余的几个搭配类比。
5.之所以使用这些魔术方法,是因为容器是封闭的(protected),所有成员属性不对外开放.但又需要暴露一些可操作的接口。
__get()方法作为容器对外开发的接口,它是实例化对象的关键,有这样一段代码,该代码是app类的initialize函数一部分:
$this->configExt = $this->env->get('config_ext', '.php');
// 加载惯例配置文件,优先级最低
$this->config->set(include $this->thinkPath . 'convention.php');
//设置路径环境变量
$this->env->set([
'think_path' => $this->thinkPath,
'root_path' => $this->rootPath,
'app_path' => $this->appPath,
'config_path' => $this->configPath,
'route_path' => $this->routePath,
'runtime_path' => $this->runtimePath,
'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
]);
使用代码追踪工具会发现容器里根本没有设置config或者env的属性,这就是魔术方法__get()在起作用,访问一个不存在的属性会触发__get方法。__get方法就会调用this->make
去创建这个属性名称的实例。这样只要写上$this->config这种不存在的属性,就启动后容器去获取实例。后面的 $this->config->get() 中的get方法是该实例的成员方法.
查看容器类可以发现,这些属性全部写成了注释放在了类的上头,都没有在类中设置:
/**
* @package think
* @property Build $build
* @property Cache $cache
* @property Config $config
* @property Cookie $cookie
* @property Debug $debug
* @property Env $env
* @property Hook $hook
* @property Lang $lang
* @property Middleware $middleware
* @property Request $request
* @property Response $response
* @property Route $route
* @property Session $session
* @property Template $template
* @property Url $url
* @property Validate $validate
* @property View $view
* @property route\RuleName $rule_name
* @property Log $log
*/
class Container implements ArrayAccess, IteratorAggregate, Countable
容器主要属性归纳:
下面再来看看容器使用反射的方法:
/**
* 执行函数或者闭包方法 支持参数调用
* @access public
* @param mixed $function 函数或者闭包
* @param array $vars 参数
* @return mixed
*/
public function invokeFunction($function, $vars = [])
{
try {
$reflect = new ReflectionFunction($function);
$args = $this->bindParams($reflect, $vars);
return call_user_func_array($function, $args);
} catch (ReflectionException $e) {
throw new Exception('function not exists: ' . $function . '()');
}
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param mixed $method 方法
* @param array $vars 参数
* @return mixed
*/
public function invokeMethod($method, $vars = [])
{
try {
if (is_array($method)) {
$class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
$reflect = new ReflectionMethod($class, $method[1]);
} else {
// 静态方法
$reflect = new ReflectionMethod($method);
}
$args = $this->bindParams($reflect, $vars);
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
} catch (ReflectionException $e) {
if (is_array($method) && is_object($method[0])) {
$method[0] = get_class($method[0]);
}
throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()');
}
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param object $instance 对象实例
* @param mixed $reflect 反射类
* @param array $vars 参数
* @return mixed
*/
public function invokeReflectMethod($instance, $reflect, $vars = [])
{
$args = $this->bindParams($reflect, $vars);
return $reflect->invokeArgs($instance, $args);
}
/**
* 调用反射执行callable 支持参数绑定
* @access public
* @param mixed $callable
* @param array $vars 参数
* @return mixed
*/
public function invoke($callable, $vars = [])
{
if ($callable instanceof Closure) {
return $this->invokeFunction($callable, $vars);
}
return $this->invokeMethod($callable, $vars);
}
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 参数
* @return mixed
*/
public function invokeClass($class, $vars = [])
{
try {
$reflect = new ReflectionClass($class);
if ($reflect->hasMethod('__make')) {
$method = new ReflectionMethod($class, '__make');
if ($method->isPublic() && $method->isStatic()) {
$args = $this->bindParams($method, $vars);
return $method->invokeArgs(null, $args);
}
}
$constructor = $reflect->getConstructor();
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
return $reflect->newInstanceArgs($args);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class);
}
}
/**
* 绑定参数
* @access protected
* @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
* @param array $vars 参数
* @return array
*/
protected function bindParams($reflect, $vars = [])
{
//若无参数则不需要绑定参数
if ($reflect->getNumberOfParameters() == 0) {
return [];
}
// 判断数组类型 数字数组时按顺序绑定参数
reset($vars);
$type = key($vars) === 0 ? 1 : 0;
$params = $reflect->getParameters(); //获取参数数组列表
//对每个参数进行分析
foreach ($params as $param) {
$name = $param->getName(); //获取参数名
$lowerName = Loader::parseName($name); //名字转换
$class = $param->getClass(); //获取参数的类型提示类,类型为ReflectionClass对象。
if ($class) {
$args[] = $this->getObjectParam($class->getName(), $vars);
} elseif (1 == $type && !empty($vars)) {
$args[] = array_shift($vars);
} elseif (0 == $type && isset($vars[$name])) {
$args[] = $vars[$name];
} elseif (0 == $type && isset($vars[$lowerName])) {
$args[] = $vars[$lowerName];
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
throw new InvalidArgumentException('method param miss:' . $name);
}
}
return $args;
}
/**
* 获取对象类型的参数值
* @access protected
* @param string $className 类名
* @param array $vars 参数
* @return mixed
*/
protected function getObjectParam($className, &$vars)
{
$array = $vars;
$value = array_shift($array);
if ($value instanceof $className) {
$result = $value;
array_shift($vars);
} else {
$result = $this->make($className);
}
return $result;
}