
ThinkPHP 6 从原先的 App
类中分离出 Http
类,负责应用的初始化和调度等功能,而 App
类则专注于容器的管理,符合单一职责原则。
以下源码分析,我们可以从 App
,Http
类的实例化过程,了解类是如何实现自动实例化的,依赖注入是怎么实现的。
从入口文件出发
当访问一个 ThinkPHP 搭建的站点,框架最先是从入口文件开始的,然后才是应用初始化、路由解析、控制器调用和响应输出等操作。
入口文件主要代码如下:
1 2 3 4 5 6 7 8 9 10 | require __DIR__ . '/../vendor/autoload.php';
$http = ( new App())->http;
$response = $http ->run();
$response ->send();
$http -> end ( $response );
|
Salin selepas log masuk
App 实例化
执行 new App() 实例化时,首先会调用它的构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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;
if ( is_file ( $this ->appPath . 'provider.php')) {
$this ->bind( include $this ->appPath . 'provider.php');
}
static ::setInstance( $this );
$this ->instance('app', $this );
$this ->instance('think\Container', $this );
}
|
Salin selepas log masuk
构造函数实现了项目各种基础路径的初始化,并读取了 provider.php 文件,将其类的绑定并入 $bind 成员变量,provider.php 文件默认内容如下:
1 2 3 4 | return [
'think\Request' => Request:: class ,
'think\exception\Handle' => ExceptionHandle:: class ,
];
|
Salin selepas log masuk
合并后,$bind 成员变量的值如下:

$bind 的值是一组类的标识到类的映射。从这个实现也可以看出,我们不仅可以在 provider.php 文件中添加标识到类的映射,而且可以覆盖其原有的映射,也就是将某些核心类替换成自己定义的类。
static::setInstance($this) 实现的作用,如图:

think\App 类的 $instance 成员变量指向 think\App 类的一个实例,也就是类自己保存自己的一个实例。
instance() 方法的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public function instance(string $abstract , $instance )
{
if (isset( $this ->bind[ $abstract ])) {
$bind = $this ->bind[ $abstract ];
if ( is_string ( $bind )) {
return $this ->instance( $bind , $instance );
}
}
$this ->instances[ $abstract ] = $instance ;
return $this ;
}
|
Salin selepas log masuk
执行结果大概是这样的:

Http 类的实例化以及依赖注入原理
这里,$http = (new App())->http,前半部分好理解,后半部分乍一看有点让人摸不着头脑,App 类并不存在 http 成员变量,这里何以大胆调用了一个不存在的东东呢?
原来,App 类继承自 Container 类,而 Container 类实现了__get() 魔术方法,在 PHP 中,当访问到的变量不存在,就会触发__get() 魔术方法。该方法的实现如下:
1 2 3 4 | public function __get( $name )
{
return $this ->get( $name );
}
|
Salin selepas log masuk
实际上是调用 get() 方法:
1 2 3 4 5 6 7 8 9 10 | public function get( $abstract )
{
if ( $this ->has( $abstract )) {
return $this ->make( $abstract );
}
throw new ClassNotFoundException(' class not exists: ' . $abstract , $abstract );
}
|
Salin selepas log masuk
然而,实际上,主要是 make() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public function make(string $abstract , array $vars = [], bool $newInstance = false)
{
if (isset( $this ->instances[ $abstract ]) && ! $newInstance ) {
return $this ->instances[ $abstract ];
}
if (isset( $this ->bind[ $abstract ])) {
$concrete = $this ->bind[ $abstract ];
if ( $concrete instanceof Closure) {
$object = $this ->invokeFunction( $concrete , $vars );
} else {
return $this ->make( $concrete , $vars , $newInstance );
}
} else {
$object = $this ->invokeClass( $abstract , $vars );
}
if (! $newInstance ) {
$this ->instances[ $abstract ] = $object ;
}
return $object ;
}
|
Salin selepas log masuk
然而,然而,make() 方法主要靠 invokeClass() 来实现类的实例化。该方法具体分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public function invokeClass(string $class , array $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 ) : [];
$object = $reflect ->newInstanceArgs( $args );
$this ->invokeAfter( $class , $object );
return $object ;
} catch (ReflectionException $e ) {
throw new ClassNotFoundException(' class not exists: ' . $class , $class , $e );
}
}
|
Salin selepas log masuk
以上代码可看出,在一个类中,添加__make() 方法,在类实例化时,会最先被调用。以上最值得一提的是 bindParams() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | protected function bindParams( $reflect , array $vars = []): array
{
if ( $reflect ->getNumberOfParameters() == 0) {
return [];
}
reset( $vars );
$type = key( $vars ) === 0 ? 1 : 0;
$params = $reflect ->getParameters();
$args = [];
foreach ( $params as $param ) {
$name = $param ->getName();
$lowerName = self::parseName( $name );
$class = $param ->getClass();
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 ;
}
|
Salin selepas log masuk
而这之中,又最值得一提的是 getObjectParam() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | protected function getObjectParam(string $className , array & $vars )
{
$array = $vars ;
$value = array_shift ( $array );
if ( $value instanceof $className ) {
$result = $value ;
array_shift ( $vars );
} else {
$result = $this ->make( $className );
}
return $result ;
}
|
Salin selepas log masuk
getObjectParam() 方法再一次光荣地调用 make() 方法,实例化一个类,而这个类,正是从 Http 的构造函数提取的参数,而这个参数又恰恰是一个类的实例 ——App 类的实例。到这里,程序不仅通过 PHP 的反射类实例化了 Http 类,而且实例化了 Http 类的依赖 App 类。假如 App 类又依赖 C 类,C 类又依赖 D类…… 不管多少层,整个依赖链条依赖的类都可以实现实例化。
总的来说,整个过程大概是这样的:需要实例化 Http 类 ==> 提取构造函数发现其依赖 App 类 ==> 开始实例化 App 类(如果发现还有依赖,则一直提取下去,直到天荒地老)==> 将实例化好的依赖(App 类的实例)传入 Http 类来实例化 Http 类。
这个过程,起个装逼的名字就叫做「依赖注入」,起个摸不着头脑的名字,就叫做「控制反转」。
这个过程,如果退回远古时代,要实例化 Http 类,大概是这样实现的(假如有很多层依赖):
1 2 3 4 5 6 7 8 9 10 11 | .
.
.
$e = new E();
$d = new D( $e );
$c = new D( $d );
$app = new App( $c );
$http = new Http( $app );
.
.
.
|
Salin selepas log masuk
这得有多累人。而现代 PHP,交给「容器」就好了。容器还有不少功能,后面再详解。
Atas ialah kandungan terperinci ThinkPHP6源码:从Http类的实例化看依赖注入是如何实现的. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!