ThinkPHP6 实例化 Http 类和依赖注入
index.php 第二句
// 执行HTTP应用并响应
$http = (new App())->http;
1 实例化App类
public function __construct(string $rootPath = '')
{
// 定义一些目录地址
$this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
$this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
$this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
// 将provider.php中返回的类的映射合并到 $bind 变量中,相同名称的会覆盖,如此可以通过在该文件中定义方式改变框架所使用的某些核心类
if (is_file($this->appPath . 'provider.php')) {
$this->bind(include $this->appPath . 'provider.php');
}
// 将当前App实例保存到静态属性 $instance 中
static::setInstance($this);
// 绑定当前实例到 $instances 数组的中,后面再有用到这个类实例时直接取这里的
$this->instance('app', $this);
$this->instance('think\Container', $this);
}
看下App类继承的Container类中的 bind 方法
public function bind($abstract, $concrete = null)
{
// 如果是数组的话,就循环该数组,数组中的每个元素重走该 bind 方法,我们上面调用的 bind 就是先走了这里
if (is_array($abstract)) {
foreach ($abstract as $key => $val) {
$this->bind($key, $val);
}
// 如果是匿名函数,直接保存到 bind 中
} elseif ($concrete instanceof Closure) {
$this->bind[$abstract] = $concrete;
// 如果是一个类的实例,则保存到 instances 数组中
} elseif (is_object($concrete)) {
$this->instance($abstract, $concrete);
// 我们前面调用 bind 方法在进过第一次 foreach 循环后就是走这里了,绑定到 bind 变量中
} else {
// 获取真实类名,因为有可能传过来的是一个别名,当然在这里两个都不是,依然返回了它们自己
$abstract = $this->getAlias($abstract);
$this->bind[$abstract] = $concrete;
}
return $this;
}
再看看 getAlias 方法
public function getAlias(string $abstract): string
{
// 检查 $bind 中是否有保存对应的类名
if (isset($this->bind[$abstract])) {
$bind = $this->bind[$abstract];
// 如果有,并且是一个字符串(还有可能是闭包),那这个字符串有可能还是别名,继续查找 $bind
if (is_string($bind)) {
return $this->getAlias($bind);
}
}
return $abstract;
}
实例化Http类
App类中并没有http这个成员变量,(new App())->http 会调用 App 类的魔术方法 __get
// 在 App 继承的 Container类中
public function __get($name)
{
return $this->get($name);
}
public function get($abstract)
{
// 判断 bind 变量中是否有绑定对应的类名或者 instances 变量中是否已保存对应类的实例
// 比如这里 $abstract=http, 在 bind 中是有的
if ($this->has($abstract)) {
// 获取这个类的实例
return $this->make($abstract);
}
throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
}
让我们看看 make方法里做了什么
/**
* 创建类的实例 已经存在则直接获取
* @access public
* @param string $abstract 类名或者标识
* @param array $vars 变量
* @param bool $newInstance 是否每次创建新的实例
* @return mixed
*/
public function make(string $abstract, array $vars = [], bool $newInstance = false)
{
$abstract = $this->getAlias($abstract);
// 如果 instances 已保存对应的实例,就直接返回
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
// 如果是一个闭包
if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
// em....这里还没看
$object = $this->invokeFunction($this->bind[$abstract], $vars);
} else {
// 通过反射获得所需要的类实例
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
// 保存实例到 instances 数组中
$this->instances[$abstract] = $object;
}
return $object;
}
然后是 invokeClass 方法
public function invokeClass(string $class, array $vars = [])
{
try {
// 获取 http 的反射类
$reflect = new ReflectionClass($class);
if ($reflect->hasMethod('__make')) {
$method = new ReflectionMethod($class, '__make');
// 如果有 __make 方法,并且是公有的静态方法,执行 __make 方法并返回,后面在实例 Request 类时会走这里。
if ($method->isPublic() && $method->isStatic()) {
$args = $this->bindParams($method, $vars);
return $method->invokeArgs(null, $args);
}
}
// 获取类的构造函数
$constructor = $reflect->getConstructor();
// 绑定构造函数的参数,http 这个类的构造方法需要一个App类的实例作为参数,这个方法的注释中有些到支持依赖注入,如何实现的依赖注入就在这个 bindParams 方法中
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
//
$object = $reflect->newInstanceArgs($args);
$this->invokeAfter($class, $object);
return $object;
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
}
}
bindParams 是如何实现依赖注入的
protected function bindParams($reflect, array $vars = []): array
{
// 获取方法的参数个数,0的话直接返回空数组
if ($reflect->getNumberOfParameters() == 0) {
return [];
}
// 判断数组类型 数字数组时按顺序绑定参数
reset($vars); // 将数组的指针指到数组的第一个元素
$type = key($vars) === 0 ? 1 : 0; // 获取当前指针所在数组元素的下标,判断是不是0
$params = $reflect->getParameters(); // 获取方法所需的参数
$args = [];
foreach ($params as $param) {
$name = $param->getName(); // app
$lowerName = Str::snake($name); // 大写转下划线
$class = $param->getClass(); // 获取参数类型限定的类 think\App
// 如果参数类型限定是一个类,这里就是走的这个
if ($class) {
// getObjectParam 方法中再次调用了 make 方法来获取需要的 App类,而这个 App 类已经在前面 new App()的时候保存到 instances 变量中了,如此就完成了依赖注入
// 假如 App 类的构造函数中需要其它类的实例作为参数,那就会再走一遍这个流程
// 我的理解,依赖注入就是对类实例的需要通过传参来实现,控制反转就是这个传参不需要使用者主动传,交由Container完成
$args[] = $this->getObjectParam($class->getName(), $vars);
} elseif (1 == $type && !empty($vars)) {
...
}
return $args;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: