分析Laravel执行流程.
一、自动加载
定位
- 从
public/index.php
定位到bootstrap/autoload.php
- 从
bootstrap/autoload.php
定位到_vendor/autoload.php
- 从
vendor/autoload.php
定位到__DIR__ . '/composer' . '/autoload_real.php';
定位完毕, 你会看到这样的代码:
1 | return ComposerAutoloaderInitda859f5bf8329e41e463c784e73d3cdf::getLoader(); |
ComposerAutoloaderInitda859f5bf8329e41e463c784e73d3cdf 简称本类
那我们就从 getLoader()
方法入手。
getLoader()
文件位于: __DIR__ . '/composer' . '/autoload_real.php'
;
逻辑顺序:
一、如果静态变量 $loader
不为空则返回 $loader
,
1 | if (null !== self::$loader) { |
二、注册一个自动加载程序,加载程序为本类的 loadClassLoader()
方法,
1 | spl_autoload_register(array('ComposerAutoloaderInitda859f5bf8329e41e463c784e73d3cdf', 'loadClassLoader'), true, true); |
[loadClassLoader 方法逻辑]
1 | public static function loadClassLoader($class) |
静态方法,含有一个 $class
参数,判断如果 $class
等于 Composer\Autoload\ClassLoader
,则载入当前目录下 的 ClassLoader.php
文件,实际上是在为这句代码工作:
1 | self::$loader = $loader = new \Composer\Autoload\ClassLoader(); |
三、$loader
得到 ClassLoader
类(\Composer\Autoload\ClassLoader
)的一个实例,卸载自动加载程序 loadClassLoader
,
1 | self::$loader = $loader = new \Composer\Autoload\ClassLoader(); |
四、载入路径信息,设置路径信息,
五、载入一些 autoload_x.php
形式的文件,
分别有:
autoload_namespaces.php
autoload_psr4.php
autoload_classmap.php
并进行各自的循环 set 操作,如
1 | $loader->set($namespace, $path); |
【set 函数】2个参数,
一个前缀
,一个路径
。如果前缀非真,将paths
转为数组类型赋值给类成员变量fallbackDirsPsr0
,如果前缀为真,则将路径赋值给$this->prefixesPsr0[$prefix[0]][$prefix]
,这个写法的意思等同于字母索引,比如phpDocumentor
,则数组就图所示:
六、执行一个 $loader->register(true);
,
[register 方法逻辑]
1 | $loader->register(true); |
一个布尔值参数,将传给 spl_autoload_register
第三个参数中。 而自动加载程序为:array($this, 'loadClass')
,也就是本类的 loadClass()
方法。
[loadClass 方法逻辑]
1 | public function loadClass($class) |
一个 $class
参数,用了 findFile()
方法判断文件是否存在,存在则调用函数 includeFile()
载入文件
1 | function includeFile($file) |
七、还载入了一个autoload_files.php
,而里面也是一组文件数组,貌似预加载一些函数库文件吧,没有继续深入这里了。
verdor/composer/autoload_files.php
1 | if ($useStaticLoader) { |
八、最后返回一个 $loader
变量,也就是 ClassLoader
类的实例。
1 | foreach ($includeFiles as $fileIdentifier => $file) { |
1 | function composerRequire022a3829915a6a8f070076dbcb34819c($fileIdentifier, $file) |
好了,现在看看 $loader
这个实例到此拥有些什么?部分截图所示:
可以看出类属性包含了具有字母索引的一些命名空间,文件路径等信息。这和刚才载入那几个文件进行 set
操作有关,想起来了吗?
到此 getLoader()
方法逻辑结束。
总结
实现自动化的关键代码是 vendor/autoload.php
的 ::getLoader()
静态方法, 利用此方法内部的 $loader->register(true)
; 方法注册自动化载入方法,这样,当 new
对象的时候自动触发 loadClass()
了,而上面提到的 set
一些路径信息,正是自动化的必备条.
如有兴趣可以自行查看 vendor/composer/ClassLoader.php
的 loadClass()
方法代码细节。
上面如果没懂的,请打开文件代码,跟着慢慢走,慢慢看,一定能懂。
再返回到 vendor/autoload.php
,再把 return $loader
返回到上一层。 即 bootstrap/autoload.php
,
这行的代码 require __DIR__.'/../vendor/autoload.php
‘;
我们 var_dump()
下 require
的返回值,和刚才 $loader
的部分截图完全一致。 其实有从 aotuload_real.php
文件开始,我尝试过删除 return
,也没有任何报错,不知道这里的 return
意义为何
再看 index.php
定位到了 bootstrap/app.php
打开就看到第一个
1 | $app = new Illuminate\Foundation\Application( |
下面来详细介绍:
Container 类
定位
- 来到
bootstrap/app.php
发现一段英文注释:
1 | /* |
翻译过来:
1 | / * |
换句话说,把这个对象实例作为一个可依靠稳固的地基,来存放各种“建筑物(组件)”,So 地基= IOC 容器? 好复杂有木有,不管它了,读代码去吧,读代码就像聊天,得用心倾听别人的心声。
紧接着 new
了一个对象。
1 | $app = new Illuminate\Foundation\Application( |
new Illuminate\Foundation\Application()
构造函数做了什么?
Application
类还传入了当前 laravel 根目录的地址这样一个参数。如 “D:\webSite\Laravel”
1 | public function __construct($basePath = null) |
命名空间 Illuminate
的具体文件路径位于 Vendor/laravel/framework/src/Illuminate
。
打开文件: Vendor/laravel/framework/src/Illuminate/Application.php
到这里: class Application extends Container implements ApplicationContract, HttpKernelInterface
继承 Container
类,对应上面的use:use Illuminate\Container\Container
; 并且要实现2个接口: ApplicationContract
,HttpKernelInterface
咱们先看看这个 Container
类到底是什么。 怎么找具体文件上面已经说过了,下面就直接略过,直接打开 Container.php
。
Container
类
Vendor/laravel/framework/src/Illuminate/Container/Container.php
The ArrayAccess interface
数据访问接口,PHP5的一个新接口,接口提供访问对象数组。
1 | class Container implements ArrayAccess, ContainerContract |
需要实现四个方法:
abstract public boolean offsetExists ( mixed $offset )
abstract public mixed offsetGet ( mixed $offset )
abstract public void offsetSet ( mixed $offset , mixed $value )
abstract public void offsetUnset ( mixed $offset )
先不用懂这4个方法是什么意思,看看 Container
类怎么实现的就懂了。
offsetExists
1 | public function offsetExists($key) |
1 | public function bound($abstract) |
当外部使用 isset()
函数时触发此方法,而方法内部则也是使用一个 isset
来判断返回一个 bool
值。这个 bindings
先不管,我们主要知道这个方法做了什么事情。
offsetGet
1 | public function offsetGet($key) |
当外部尝试获取 $key
的时候,如 echo
,print_r
,var_dump
,赋值等。 就会触发此方法,而方法内部也是直接一个 reutrn
,简单!
offsetSet
1 | public function offsetSet($key, $value) |
当外部尝试赋值的时候,则触发此方法。 此方法内部先进行一个 instanceof
判断 如果 value
参数不是一个闭包,则将 $value
封装成一个闭包,而闭包函数的内部直接一个 return
。
offsetUnset
1 | public function offsetUnset($key) |
当外部尝试使用 unset
函数的时候触发此方法, 方法内部是一个 unset
函数,那些变量看不懂,先不管。
这就是 ArrayAccess
,一个类实现它的4个方法,在不同操作时触发4个方法,知道这些就够了,具体调用的示例这里我没有举出来,PHP 官网有,请自行翻阅,锻炼下动手能力。
好吧继续往下看;
ReflectionMethod
针对类方法ReflectionFunction
针对普通函数ReflectionParameter
针对函数的参数
具体代码演示如下:
1 | /* ReflectionMethod 示例 |
然后看到 Container
类还实现这个接口 ContainerContract
。
对应 use Illuminate\Contracts\Container\Container as ContainerContract
;
提示:Contracts
翻译 “合同、契约”。
接口里面的方法有兴趣可自行查看,我们主要看 Container
的实现这些方法的细节。
最后我们简述下 Contracts 作为本章结束语: 我的理解是一系列组件服务的接口集合
探索 Application 构造函数
定位
- OK,从入口地址
public/index.php
看到如下代码:
1 | /* |
翻译:
注释很有趣,翻译凑合看吧。 则现在我们打开 bootstarp/app.php
文件,因为这是 $app
这个玩意儿的出生地。
1 | /* |
翻译:
Application.php
咱们就从在这里摸索一下构造函数里面发生了什么,打开 Application.php
文件。
打开的 Application.php
是位于 vendor/laravel/framework/src/Illuminate/Foundation/Application.php
,而命名空间是 Illuminate\Foundation
Application
类的构造函数如下:
1 | public function __construct($basePath = null) |
因为代码量真的很多,如果每一点都要说,不仅会看得云里雾里,还不一定能理解,所以,我这里以总结的方式概述,特别值得学习的地方再单独提出来说。
一、registerBaseBindings()
注册一些基本的绑定到容器中。
简单点说,此方法内部进行3
次赋值,赋值后的变量及变量内容形式如下:
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
借鉴:
1 | Container::$instance = $this |
变量名具体含义:
$this
,也就是 Application
类。
static::setInstance,之前说过 Application
是 Container
的子类,而 $instance
静态变量是在 Container
类中已经定义好的,:
Application.php
1 | class Application extends Container implements ApplicationContract, HttpKernelInterface |
Container.php
1 | class Container implements ArrayAccess, ContainerContract |
$this->instances
,也是在 Container
中定义的,含义为存放容器的共享实例,
1 | /** |
你可以在 registerBaseBindings
方法的最后面打印如下3个变量进行检测,得到的都是 application object
,
1 | protected function registerBaseBindings() |
到此,所谓的基本绑定结束,还是云里雾里的,英文不好只能看代码了,反正你记住父类的2个成员属性已经得到了 application
对象。
二、registerBaseServiceProviders()
注册所有的基础服务提供商。
好吧,第二章提过的 ioc 容器=地基
,开始买材料准备施工,找几个最基础的供应商商来进行合作,搞水泥的啊,砌砖的啊,以后有更多的需求,根据自己的需求在去找供应商谈。Laravel
现在注册了3
个提供商,一个事件,一个是路由, 一个日志。
1 | protected function registerBaseServiceProviders() |
我们先意淫一下大概的意思,找到了供应商,准备合作签合同(
register
),先和搞水泥的签(event
),在和砌砖的签(routing
),和谁签?I’m the boss($this
)。 既然我是老板,合同条款肯定得看清楚了,咱们去看看合同先(register
方法 ) 嗯!合同说的很清楚,首先确定我和供应商是否签过合同了,签过了(getProvider()
来判断)就滚蛋,浪费时间;虽然我是老板,但不是法人,你打个电话叫他过来,名字叫狗蛋(resolveProvider
方法,如果$provider
为string
类型,则根据提供的类名帮供应商实例化并return
),如:
1 | public function resolveProvider($provider) |
OK,差不多,狗蛋把字一签(
$provider->register()
),供应商算是正式入驻施工团队了,当然了,合同还说明以后要是有其他要改的地方,直接填一份声明即可,$options
是register
方法 的第二个参数。
1 | public function register($provider, $options = [], $force = false) |
既然签了合同,就要如公司档案,狗蛋屁颠屁颠的跑去档案室了(
$this->markAsRegistered($provider)
)标记为已注册;好,大功告成(return $provider
)。
三、registerCoreContainerAliases()
注册核心容器的别名。
嗯,这个简单的多,还有啥好说的呢,定义容器里面一些核心类的别名,有兴趣直接去看这个方法就行。
1 | public function registerCoreContainerAliases() |
当然了最后是存放在 $aliases
这个数组里面哟,在 container
定义的成员属性。
四、setBasePath()
设置基本路径
1 | public function setBasePath($basePath) |
1 | protected function bindPathsInContainer() |
1 | protected $basePath; |
这个更简单了,这就是前面说 $app
出生地的地方,传了一个路径参数,
1 | $app = new Illuminate\Foundation\Application( |
至此,$app
终于生出来了,绑定了 application
对象,和3
个供应商签了合同,给一些核心类起了别名
,配置了 laravel
根目录地址
认识 Bind
第三章的探索相信有很多学友只是知道一些基本流程,并未真正意义上知道其所以然,不过学习还需要循序渐进,先了解,在深入,最后总结,本章,咱们将要探秘 bind()
方法。
Bind
方法位于Container.php
1 | /** |
Bind 第一次被使用是在 EventServiceProvider 类的 register() 方法里
vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php
第三章说到注册3个基本的服务提供商,分别是事件( event )和路由( route )还有日志(Log),都分别执行了各自的 $provider->register()。
首先看第一次执行 bind()
的代码上下文:
1 | /** |
$this->app
怎么来的?这个成员属性是在抽象类 ServiceProvider
中定义好的,并在其构造函数中进行赋值:
1 | abstract class ServiceProvider |
大家可以查阅,所有的服务提供商都继承于这个类,而在 application
类中的 registerBaseServiceProviders()
传入了这个参数 $this
:
1 | $this->register(new EventServiceProvider($this)); |
接下来我们看见的是 singleton
并非 bind
方法,而 singleton
意为单例,在方法内部调用如下:
- vendor/laravel/framework/src/Illmiunate/Container/Container.php
1 | /** |
只不过是把 bind
的第三个参数设置为共享状态。
好,结合现在的情况,在分析下参数,给了一个 events
字符串,还有一个闭包函数,闭包需要传入一个参数 $app
,这个参数无非就是 application
的实例。
好,把这个来龙去脉弄清楚了,我们才能更清晰的分析 bind
内部逻辑。
Bind 开头进行了一个数组判断,为了大家看的清楚,我简单的举了个例子:
extractAlias
方法只是返回了这样一个数组:
很简单吧,提取后在重组别名:
代码带来的功能就是这样,具体的意义,我把注释翻译,大家尽量去理解:
1 | // If no concrete type was given, we will simply set the concrete type to the |
dropStaleInstances
1 | protected function dropStaleInstances($abstract) |
删除以后,判断了如下:
1 | if (is_null($concrete)) { |
为了把这里弄懂,我们现在假设 $concrete
确实为 null
,然后看往下是怎么发展的。
1 | // If the factory is not a Closure, it means it is just a class name which is |
如果 $concrete
不是一个闭包
,就封装成一个闭包
,刚才我们假设了为 null
,并且 $concrete
= $abstract
,那么在看一下 getClosure
方法:
1 | protected function getClosure($abstract, $concrete) |
我们以刚才的假设来虚拟场景,$concrete = $abstract
,所以 $method=build
,并且执行 $container->build($concrete)
方法,
接下来要认识一个新数组 bindings
:
1 | $this->bindings[$abstract] = compact('concrete', 'shared'); |
Compact
创建一个由参数所带变量组成的数组。如果参数中存在数组,该数组中变量的值也会被获取,这里我们知道 $conrete
被封装成一个闭包了,而 $shared
是在 singleton
方法中传的 true
,也就是1
。所以现在bindings
数组形式如下:
这里有部分人可能会纠结,你刚才不是说 $concrete
为 null
,怎么又变成了一个闭包对象;刚才仅仅只是用假设,来弄懂一个环节,所以正常来说这里就是这样。
1 | // If the abstract type was already resolved in this container we'll fire the |
Events
肯定没有解析过,所以这里的 rebound
反弹暂时不能过多讲解。
到现在为止,闭包内的代码依然没有执行,而 bind
方法也到此为止了。
总结
Bind
方法不仅可以智能的创建快捷方式,还可以封装闭包函数,并把所有的对应绑定都存入到 bindings
数组中,如果一旦某个对象解析过了依然调用 bind
方法,那么就会 rebound
反弹,通过监听者的回调函数更新对象。
认识 Make
上一章了解了 bind
,bind
封装了一个闭包到数组 bindings
,那绑定完了总需要使用吧,就好像生产零件,先规定了怎么生产( bind
),然后再生产( make
)所以 make
出来了,咱们一起看看,make
在容器里面起到什么作用。
还是老办法,先找出第一次调用 make
的地方,之前第三章探索 application
构造函数还是很有必要的,因为若干的第一次 work
,以及认识这些关键的方法都包含在内,如果对以下所说的方法比较模糊,自行回顾第三章,并打开代码走一遍。
我们知道 application
构造函数里面调用了 registerBaseServiceProviders
(注册基本的服务提供商),内部调用了 register
方法,register
方法内部又调用了 markAsRegistered
(标记为已注册),咱们现在看 markAsRegistered
方法:
Application.php@markAsRegistered
1 | protected function markAsRegistered($provider) |
1 | function get_class ($object = null) {} |
正确执行 Make
vendor/laravel/framework/src/Illuminate/Container@offsetGet
offsetGet
1 | public function offsetGet($key) |
make
1 | public function make($abstract, array $parameters = []) |
resolve
1 | protected function resolve($abstract, $parameters = []) |
先是获取正确的别名 getAlias
。
1 | $abstract = $this->getAlias($abstract); |
接下来也是一个单例的判断,如果当前 instances
数组包含此类型,则直接 return
此单元。
1 | if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { |
接着执行了一个新玩意 getConcrete
并将结果赋值给 $concrete
,咱们在跳过去看看 getConcrete
,这个方法的大概意思是得到一个给定的抽象的具体类型。
1 | $concrete = $this->getConcrete($abstract); |
getConcrete
1 | protected function getConcrete($abstract) |
先是来了个看不懂的方法 getContextualConcrete
不明觉厉:
1 | protected function getContextualConcrete($abstract) |
findInContextualBindings
1 | protected function findInContextualBindings($abstract) |
我们暂时还没看到 contextual
数组用过的地方,大家先看看就好。 并且输出 contextual
现在还是空数组,所以回到 getConcrete
方法,判断不成立,继续往下走。
接着,判断我们上一章学过的 bindings
数组,我们知道数组已经包含了 events
这个单元了,
什么时候包含的?
是在 application
类的 register
方法内执行的 $provider->register()
,而 events
服务商类的 register
方法执行了 $this->app->singleton
,而 singleton
其实在执行 bind
,只不过是共享绑定而已,我们再复习一遍吧。
那也就是说,这里判断不成立,events
已经绑定好了,直接跳过 if
块,最后直接
1 | if (isset($this->bindings[$abstract])) { |
我们知道,这里的 concrete
在 events
单元内是一个闭包对象,在把上一章的截图重发一下
到此为止,getConcrete
理论上已经结束了,但是为了在弄清楚一点,我们继续把刚才 if
内没有执行的代码说一下:
咱们找到第一次能够执行此场景的代码,在入口文件 index.php
:
1 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); |
首先通过 getAlias
方法,知道现在 $abstract
参数是 app\Http\Kernel
,app\Http\Kernel
到目前为止是没有经过 bind
,所以判断成立,走 if
里面。
make
1 | public function make($abstract, array $parameters = []) |
resolve
1 | protected function resolve($abstract, $parameters = []) |
有3个知识点,isBuildable
, build
,还有一个 make
的递归。
isBuildable
,确定给定的具体物是可信赖的。
1 | protected function isBuildable($concrete, $abstract) |
要么具体物和抽象类型相等,要么具体物就是个闭包方法,至少要满足一样。
好,如果满足其中一样,我们现在场景的 events
是一个闭包,满足了,执行 build
Build
方法注释大概的意思:为给定的类型实例化一个具体物的实例。
方法开头直接:
1 | if ($concrete instanceof Closure) { |
终于露出你的庐山真面目了,events
闭包原型,我们在截图确认一下:
嗯哼,$this
参数相当于闭包函数中的参数 $app
,要注意的是 $this
是 application
类,不要认为是在 container
类定义的方法 $this
就是 container
,它们是继承关系,而 new
的是 application
类,所以 $this
属于谁应该很清楚了。
然而就这样执行了闭包,里面的肯定是组件的一些功能,这也不是本章主题,所以略过。
至于这个 make
递归,暂时还不知道,等用到的时候在说,目前为止 $object
是一个实例了,我们截图看看是哪个类的实例即可,因为这些都是刚才闭包内执行的结果。
这个很简单,就是判断 bindings
数组单元内 shared
如果为共享状态(也就是1),则将 $object
进行赋值。
接下来有个 fireResolvingCallbacks
方法,我的理解是启动“具有解析过程”回调函数,目前没有执行到,我们也就不去说了,还是那句话,用的时候在具体来分析,更好理解。
Make
方法最后:
1 | $this->resolved[$abstract] = true; |
将 true
赋值到 resolved
,意为已经解析过的,最后 return $object
, make 结束。
总结
就目前为止,我们知道, make
会借用 bindings
(bind而来) 来获取具体的闭包对象,然后用 build
方法来调用闭包函数,所以说 bind
和 make
是具有紧密的联系的
启动 kernel
前两章初步认识了 bind
和 make
使我们明白生成一个实例的步骤,接下来,我们要认识 kernel
,这是与用户交互的关键,咱们打开入口文件
- 原文:
1 | /* |
- 翻译:
1 | /* |
咱们根据这行代码和这个参数,具体来捋一捋 $kernel
是如何生成的,也当复习。
$app
这个不用解释即 application
类,之前说的 make
方法是位于 container
类中,其实 application
类中也有 make
方法,如下:
1 | public function make($abstract, array $parameters = []) |
我们现在还不知道具体的用意在哪,不过我们看见了内部调用了父类( container
)的 make
方法,
所以执行过程是先通过子类的 make
调用到父类的 make
,根据参数咱们继续往下看,这里只传了一个 Illuminate\Contracts\Http\Kernel
参数,所以 $parameters
可以无视了。
在继续分析父类 make
方法之前,我们要知道,在 bootstrap/app.php
里进行了3次 singleton
操作, singleton
前面也说过,也就是调用 bind 方法,如下:
1 | $app->singleton( |
我们看见第一个 singleton
一目了然,对应了我们今天要说的类,不过后面多了一个参数(app\Http\Kernel
),这个参数原型名字是 $concrete
,即具体物。
了解以上这些,我们才正式分析父类的 make
方法:
Make
方法前一章单独说过了,所以略过没有执行到的代码。
这里我们知道,Illuminate\Contracts\Http\Kernel
还没有生成实例,只是绑定了而已,说到绑定,不知道大家还记得 bind
方法是怎么执行的吗?其中有一步是判断如果 $concrete
这个参数不是一个闭包的,则调用 getClosure
方法来强制生成一个闭包给 $concrete
,如下:
1 | protected function getClosure($abstract, $concrete) |
咱们继续看 container
类的 make
方法。
1 | $concrete = $this->getConcrete($abstract); |
我们知道这个方法只能返回2种参数,一个是闭包对象,一个字符参数,这里 $concrete
自然是得到一个闭包对象,因为已经绑定过了。
接着直接执行了 build
方法,咱们看 build
方法内的关键代码:
1 | if ($concrete instanceof Closure) { |
getLastParameterOverride
1 | protected function getLastParameterOverride() |
嗯看到这里,我们上一章也说过,一个闭包的调用,这里需要跳回 getClosure
继续分析了:
$concrete($this, $parameters)
这里是传了一个 this
参数,即 application
类,所以 $container=application
类,那结合刚才说 getClosure
方法,这里闭包内部应该是执行一个 $container->make($concrete)
逻辑,注意参数 $concrete
是第一次传的 app\Http\
字符串;那更直接一点,代码其实是这样 application->make(‘app\Http\Kernel’)
。
从 make(‘Illuminate\Contracts\Http\Kernel’)
又转换到 make(‘app\Http\Kernel’)
,我们现在只需要知道这个顺序。
好,那再一次去看 make
如何实例化这个 app\Http\Kernel
类的,这次应该有不同的认识。
再次执行到 getConcrete
方法,我们说了要么是返回一个闭包对象
,要么就是一个字符串
,前面都是返回的闭包,而这次呢?转到 getConcrete
相关代码:
1 | protected function getConcrete($abstract) |
Build 方法逻辑顺序:
先是我们最熟悉的闭包实例判断,我们这里只是一个字符串,所以不成立,再见。
1 | if ($concrete instanceof Closure) { |
然后 new
了一个反射类
1 | $reflector = new ReflectionClass($concrete); |
先是来了个 isInstantiable
判断,
1 | if (! $reflector->isInstantiable()) { |
PHP
官网解释:
只要你不能实例化,直接抛错。
接下来又学到一个新数组
1 | $this->buildStack[] = $concrete; |
绑定栈,百度百科科普一下:
app\Http\Kernel
进入了栈结构了
紧接着又继续用到反射方法 getConstructor
,继续 PHP
官网:
1 | $constructor = $reflector->getConstructor(); |
这下清楚了,如果反射不存在的话就返回一个 null 。
接下来也是一个 is_null
判断
1 | / If there are no constructors, that means there are no dependencies then |
注释很明了,不需要多余的工作,先是出栈,弹出 $concrete
,然后直接返回对象实例。
这里 app\Http\Kernel
继承了父类 Illuminate\Foundation\Http\Kernel
,并且有构造函数,这里先发一下构造函数原型,以便后面更容易理解:
那么现在我们还得继续往下分析,发一下 $constructor
调试截图,更清晰一点:
OK,那么现在 $constructor
是一个 ReflectionMethod
对象,这个反射方法对象我们在第二章也演示过,是针对类的方法。
紧接着:
1 | $dependencies = $constructor->getParameters(); |
getParameters
方法示例:
一环扣一环,返回值依然是一个新对象,不过是针对参数。
调试截图:
嗯,确实匹配构造函数的参数,没什么大问题,每一个参数即是一个 ReflectionParameter
对象。
嗯哼,接下来也很关键:
1 | // Once we have all the constructor's parameters we can create each of the |
接着出栈:
1 | array_pop($this->buildStack); |
最后很关键的地方来了:
1 | return $reflector->newInstanceArgs($instances); |
这个简单了吧,正好对应构造函数
1 | public function __construct(Application $app, Router $router) |
这样解决的依赖关系.
总结
本章关键点在于 build
方法利用反射解决依赖关系那里,还有也进一步学习了 getClosure
方法内部逻辑,