Laravel框架深入学习

Laravel 5.8 中文文档

源码安装

先下载composer,并添加到环境变量里面,然后执行如下命令,将在本目录生成laravel5.8目录

1
composer dumpautoload -o
1
composer create-project --prefer-dist laravel/laravel=5.8.* laravel5.8

本地搭建

环境搭建篇

基础知识

PHP反射

PHP反射ReflectionClass、ReflectionMethod

PHP反射(ReflectionClass、ReflectionMethod)在ThinkPHP框架的控制器调度模块中的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class person{
public $name;
public $gender;
public function say(){
echo $this->name," \tis ",$this->gender,"\r\n";
}
public function set($name, $value) {
echo "Setting $name to $value \r\n";
$this->$name= $value;
}
public function get($name) {
if(!isset($this->$name)){
echo '未设置';
$this->$name="正在为你设置默认值";
}
return $this->$name;
}
}
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
35
36
37
38
// 反射获取类的原型
$obj = new ReflectionClass('person');
$className = $obj->getName();
$Methods = $Properties = array();

foreach($obj->getProperties() as $v)
{
$Properties[$v->getName()] = $v;
}

foreach($obj->getMethods() as $v)
{
$Methods[$v->getName()] = $v;
}

echo "class {$className}\n{\n";
echo "\t<br>";
is_array($Properties) && ksort($Properties);
foreach($Properties as $k => $v)
{
if($v->isPublic())
echo 'Public';
if($v->isPrivate())
echo 'Private';
if($v->isProtected())
echo 'protected';
echo "\t";
echo $k;
echo "<br>";
}

if(is_array($Methods))
ksort($Methods);
foreach($Methods as $k => $v)
{
echo "function {$k}(){}\n<br>";
}
echo "}\n";
1
2
3
4
5
6
7
class person {
Public gender
Public name
function get(){}
function say(){}
function set(){}
}

简单理解Ioc服务容器

浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)

两张图让你理解 IoC (控制反转)

引用:laravel 学习笔记 —— 神奇的服务容器

简单理解就是:表面上使用很简单的调用,其背后框架已经写了很多,控制反转,依赖注入。具体可以看下面简单的例子;

1
2
3
4
5
6
7
8
9
10
interface SuperModuleInterface
{
/**
* 超能力激活方法
*
* 任何一个超能力都得有该方法,并拥有一个参数
*@param array $target 针对目标,可以是一个或多个,自己或他人
*/
public function activate();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* X-超能量
*/
class XPower implements SuperModuleInterface
{
public function activate()
{
// 这只是个例子。。具体自行脑补
}
}

/**
* 终极炸弹 (就这么俗)
*/
class UltraBomb implements SuperModuleInterface
{
public function activate()
{
// 这只是个例子。。具体自行脑补
}
}
1
2
3
4
5
6
7
8
9
class Superman
{
public $module;

public function __construct(SuperModuleInterface $module)
{
$this->module = $module
}
}

什么叫依赖注入

本文从开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于 依赖注入(DI) 。是不是豁然开朗?事实上,就是这么简单。下面就是一个典型的依赖注入:

1
2
3
4
5
// 超能力模组
$superModule = new XPower;

// 初始化一个超人,并注入一个超能力模组依赖
$superMan = new Superman($superModule);

但是这样还是很繁琐,每次都要new一个,也算是手动了,接下来Ioc容器要出场了。

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
class Container
{
protected $binds;

protected $instances;

public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}

public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}

array_unshift($parameters, $this);

return call_user_func_array($this->binds[$abstract], $parameters);
}
}
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
35
36
37
38
39
// 创建一个容器(后面称作超级工厂)
$container = new Container;

// 向该 超级工厂 添加 超人 的生产脚本
$container->bind('superman', function($container, $moduleName) {
return new Superman($container->make($moduleName));
});

// 向该 超级工厂 添加 超能力模组 的生产脚本
$container->bind('xpower', function($container) {
return new XPower;
});

// 同上
$container->bind('ultrabomb', function($container) {
return new UltraBomb;
});

// ****************** 华丽丽的分割线 **********************
// 开始启动生产
$superman_1 = $container->make('superman', ['xpower']);
$superman_2 = $container->make('superman', ['ultrabomb']);
var_dump($superman_1);
var_dump($superman_2);
$superman_1->module->activate();

/*
C:\FakeD\Software\phpstudy\PHPTutorial\WWW\test\container.php:86:
object(Superman)[5]
public 'module' =>
object(XPower)[6]

C:\FakeD\Software\phpstudy\PHPTutorial\WWW\test\container.php:87:
object(Superman)[7]
public 'module' =>
object(UltraBomb)[8]

XPower
*/

看到没?通过最初的 绑定(bind) 操作,我们向 超级工厂 注册了一些生产脚本,这些生产脚本在生产指令下达之时便会执行。发现没有?我们彻底的解除了 超人 与 超能力模组 的依赖关系,更重要的是,容器类也丝毫没有和他们产生任何依赖!我们通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为生产一个类的实例的 脚本 ,只有在真正的 生产(make) 操作被调用执行时,才会触发。

这样一种方式,使得我们更容易在创建一个实例的同时解决其依赖关系,并且更加灵活。当有新的需求,只需另外绑定一个“生产脚本”即可。

Laravel简化版的Ioc容器

引用: laravel Ioc(控制反转)和Di(依赖注入)

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php
interface Visit{
public function go();
}
class Leg implements Visit{
public function go(){
echo 'walt to Tibet!!!';
}
}
class Car implements Visit{
public function go(){
echo "drive car to Tibet!!!";
}
}
class Train implements Visit{
public function go(){
echo "go to Tibet by train!!!";
}
}
class Container{
protected $bindings=[];
//全局变量保持闭包函数
public function bind($abstract,$concrete=null,$shared=false){
//如果这个不是一个闭包函数,就用getClosure创建一个闭包函数
if(!$concrete instanceof Closure){
$concrete=$this->getClosure($abstract,$concrete);
}
//保存成多位数组
$this->bindings[$abstract]=compact('concrete','shared');
}
//生成一个闭包函数
protected function getClosure($abstract,$concrete){
//这里的$c对应build方法$concrete($this);中的this
return function($c) use($abstract,$concrete){
$method=($abstract==$concrete)?'build':'make';
return $c->$method($concrete);
};
}
public function make($abstract){
$concrete=$this->getConcrete($abstract);
//判断是否是一个闭包函数
if($this->isBuildable($concrete,$abstract)){
//是一个闭包函数就实例化
$object=$this->build($concrete);
}else{
$object=$this->make($concrete);
}
return $object;
}
//判断是否是一个闭包函数
protected function isBuildable($concrete,$abstract){
return $concrete===$abstract||$concrete instanceof Closure;
}
//判断全局变量$this->bindings里有没有注入这个类
protected function getConcrete($abstract){
if(!isset($this->bindings[$abstract])){
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
//用反射的方式实例化类
public function build($concrete){
//如果是一个闭包函数就直接返回
if($concrete instanceof Closure){
return $concrete($this);
}
//反射实例化类
$reflector=new ReflectionClass($concrete);
//反射的方式判断类是否可以实例化
if(!$reflector->isInstantiable()){
echo $message="Target [$concrete] is not instantiable.";
}
//判断有没有构造函数
$constructor=$reflector->getConstructor();
//如果没有构造函数就直接实例化
if(is_null($constructor)){
return new $concrete;
}
//反射构造函数的参数
$dependencies=$constructor->getParameters();
$instances=$this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
protected function getDependencies($parameters){
$dependencies=[];
foreach($parameters as $parameter){
//反射方式参数的接口比如Traveller类中的$trafficTool的变量必须是Visit这个类型的
$dependency=$parameter->getClass();
if(is_null($dependency)){
$dependencies[]=NULL;
}else{
//如果要有限制的参数,在注入这个类
$dependencies[]=$this->resolveClass($parameter);
}
}
return (array)$dependencies;
}
protected function resolveClass(ReflectionParameter $parameter){
return $this->make($parameter->getClass()->name);
}
}
class Traveller{
protected $trafficTool;
public function __construct(Visit $trafficTool)
{
$this->trafficTool=$trafficTool;
}
public function visitTibet(){
$this->trafficTool->go();
}
}
$app=new Container();
$app->bind("Visit","Train");
$app->bind("traveller","Traveller");
$tra=$app->make("traveller");
$tra->visitTibet();

具体大家可以用phpstorm单步调试一下,在理解Laravel框架之前,请务必搞懂这些。

PHP 自动加载 命名空间 深度总结

PHP 自动加载 深度总结
PHP命名空间和自动加载类

self和static的区别

php面向对象中self和static的区别

self在子类中还是会调用父类的方法

在调用static,子类哪怕调用的是父类的方法,但是父类方法中调用的方法还会是子类的方法

总结就是:self只能引用当前类中的方法,而static关键字允许函数能够在运行时动态绑定类中的方法。

目录结构

快速入门 —— 目录结构

查看路由

1
php artisan route:list

生成模型类的时候指定生成到 app/Models 目录下:

1
php artisan make:model Models/Test
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
35
36
37
38
39
├── app			#包含Controller、Model、路由等在内的应用目录,大部分业务将在该目录下进行
│ ├── Console
│ ├── Exceptions
│ ├── Http
│ │ ├── Controllers
│ │ │ └── Auth
│ │ └── Middleware ##程序的中间件
│ └── Providers
├── bootstrap #框架启动载入目录
│ └── cache
├── config #各种配置文件的目录
├── database #数据库相关目录
│ ├── factories
│ ├── migrations
│ └── seeds
├── public #网站入口,应当将ip或域名指向该目录而不是根目录
│ ├── css
│ └── js
├── resources #资源文件目录
│ ├── js
│ │ └── components
│ ├── lang
│ │ └── en
│ ├── sass
│ └── views
├── routes #目录包含了应用定义的所有路由
├── storage #目录包含了编译后的 Blade 模板、基于文件的 Session、文件缓存,以及其它由框架生成的文件
│ ├── app
│ │ └── public
│ ├── framework
│ │ ├── cache
│ │ │ └── data
│ │ ├── sessions
│ │ ├── testing
│ │ └── views
│ └── logs
└── tests #测试目录
├── Feature
└── Unit

框架入口

1
2
3
4
5
6
7
8
9
10
11
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); //这段代码的意思是向App容器请求一个Illuminate\Contracts\Http\Kernel的实例。

// 处理请求
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture() // 创建请求实例
);
$response->send();
$kernel->terminate($request, $response);

生命周期解析

自动加载初始化的源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public/index.php
---->require __DIR__.'/../vendor/autoload.php';

vendor/autoload.php
--->require_once __DIR__ . '/composer/autoload_real.php';
--->return ComposerAutoloaderInitf9ab117ae16ec33fcc1bd122a5283b94::getLoader();

vendor/composer/autoload_real.php
--->self::$loader = $loader = new \Composer\Autoload\ClassLoader();
--->require_once __DIR__ . '/autoload_static.php';
---> call_user_func(\Composer\Autoload\ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::getInitializer($loader));

vendor/composer/autoload_static.php
--->ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::getInitializer($loader)
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
35
36
37
38
39
40
41
42
43
44
45
46
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}

/***********************获得自动加载核心类对象********************/ spl_autoload_register(array('ComposerAutoloaderInitf9ab117ae16ec33fcc1bd122a5283b94', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInitf9ab117ae16ec33fcc1bd122a5283b94', 'loadClassLoader'));
/***********************初始化自动加载核心类对象********************/
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';

call_user_func(\Composer\Autoload\ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}

$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}

$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
/***********************注册自动加载核心类对象********************/
$loader->register(true);

/***********************自动加载全局函数********************/
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequiref9ab117ae16ec33fcc1bd122a5283b94($fileIdentifier, $file);
}
return $loader;
}
}
1
2
3
4
5
6
7
8
9
10
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::$prefixesPsr0;
$loader->classMap = ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::$classMap;

}, null, ClassLoader::class);
}

ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94类的核心就是 getInitializer () 函数,它将自己类中的顶级命名空间映射给了 ClassLoader 类。值得注意的是这个函数返回的是一个匿名函数,为什么呢?原因就是 ClassLoader 类中的 prefixLengthsPsr4、prefixDirsPsr4 等等都是 private 的。。。普通的函数没办法给类的 private 成员变量赋值。利用匿名函数的绑定功能就可以将把匿名函数转为 ClassLoader 类的成员函数。关于匿名函数的绑定功能

自动加载注册的源码分析

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
$loader->register(true);

--->

public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

--->

public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);

return true;
}
}

--->
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}

$file = $this->findFileWithExtension($class, '.php');

// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}

if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}

if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}

return $file;
}
--->
function includeFile($file)
{
include $file;
}

这样,每当 PHP 遇到一个不认识的命名空间的时候,PHP 会自动调用注册到 spl_autoload_register 里面的函数堆栈,运行其中的每个函数,直到找到命名空间对应的文件。

Composer 不止可以自动加载命名空间,还可以加载全局函数。怎么实现的呢?很简单,把全局函数写到特定的文件里面去,在程序运行前挨个 require 就行了。这个就是 composer 自动加载的第五步,加载全局函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInitf9ab117ae16ec33fcc1bd122a5283b94::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequiref9ab117ae16ec33fcc1bd122a5283b94($fileIdentifier, $file);
}

---->

function composerRequiref9ab117ae16ec33fcc1bd122a5283b94($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;

$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}

Application实例化

Application 实例化

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
35
36
37
//  /bootstrap/app.php
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class, //接口
App\Http\Kernel::class //实现
); //这段代码意思是向App容器声明,当我需要一个Illuminate\Contracts\Http\Kernel的实例时,创建一个单实例的App\Http\Kernel给我。
//singleton 方法绑定一个只会解析一次的类或接口到容器,然后接下来对容器的调用将会返回同一个对象实例:

$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
//这段代码意思是向App容器声明,当我需要一个Illuminate\Contracts\Console\Kernel的实例时,创建一个单实例的App\Console\Kernel给我。

$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);

return $app;
//这段代码意思是向App容器声明,当我需要一个Illuminate\Contracts\Debug\ExceptionHandler,创建一个单实例的App\Exceptions\Handler。

// /vendor/laravel/framework/src/Illuminate/Foundation/Application.php
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}

$this->registerBaseBindings();

$this->registerBaseServiceProviders();

$this->registerCoreContainerAliases();
}

第一步

第一步依赖于初始化时传入的路径参数,路径为项目的根目录。在 setBasePath 方法中,bindPathsInContainer 方法被调用。这里又用到了 instance 方法,把路径填入了类的 $instances 数组中。

1
2
3
4
5
6
7
8
9
10
11
12
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}

image.png

第二步

1
2
3
4
5
6
7
8
9
10
11
12
13
protected function registerBaseBindings()
{
static::setInstance($this);

$this->instance('app', $this);
$this->instance(Container::class, $this);

$this->singleton(Mix::class);

$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}

这里同样调用了 instance 方法,但与之前不同的是,这里传入的第二个参数是一个类,而之前的是一个字符串。在第二步中,第一行它为 Application 类注册了一个名为 $instance 的静态实例,二三两行则为 Application 实例中的 $instances 数组属性填入了两个 key 为 ‘app’ 和 Container::class,value 为 $this(即Application实例)的值。

第三步

1
2
3
4
5
6
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}

第三步注册了三个 ServiceProvider,在注册过程中,ServiceProvider 的 register 方法会被执行,每一个 ServiceProvider 一般都会有 register 方法。

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
public function register($provider, $options = [], $force = false)
{
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}

// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}

if (method_exists($provider, 'register')) {
$provider->register();
}

$this->markAsRegistered($provider);

// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->booted) {
$this->bootProvider($provider);
}

return $provider;
}

第四步

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
35
36
37
38
39
40
41
42
43
44
45
public function registerCoreContainerAliases()
{
$aliases = [
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
];

foreach ($aliases as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}

这一步是在为一些核心类注册别名。

四步走下来,Application 的实例化就算完成了。

接收请求并响应

引用: 深度挖掘 Laravel 生命周期

解析内核实例

在Application实例化时,我们已经将 HTTP 内核Console 内核 绑定到了 APP 容器,使用时通过 APP 容器make() 方法将内核解析出来,解析的过程就是内核实例化的过程。

1
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

进一步挖掘 Illuminate\Foundation\Http\Kernel 内核的 __construct构造方法,它接收 APP 容器路由器 两个参数。

在实例化内核时,构造函数内将在 HTTP 内核定义的「中间件组」注册到 路由器,注册完后就可以在实际处理 HTTP 请求前调用这些「中间件」实现 过滤 请求的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;

$router->middlewarePriority = $this->middlewarePriority;

foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}

foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware);
}
}

处理 HTTP 请求

1
2
3
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
创建请求实例

请求实例 Illuminate\Http\Requestcapture() 方法内部通过 Symfony 实例创建一个 Laravel 请求实例。这样我们就可以获取到用户请求报文的相关信息了。

1
2
3
4
5
6
public static function capture()
{
static::enableHttpMethodParameterOverride();

return static::createFromBase(SymfonyRequest::createFromGlobals());
}
处理请求

请求处理发生在 HTTP 内核handle() 方法内。handle() 方法接收一个 HTTP 请求,并最终生成一个 HTTP 响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();

$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);

$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));

$response = $this->renderException($request, $e);
}

$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);

return $response;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);

Facade::clearResolvedInstance('request');

// 启动 「引导程序」
$this->bootstrap();

// 发送请求至路由
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}

将发现这段代码没有一行废话,它完成了大量的逻辑处理:

  • 首先,将 $request 实例注册到 APP 容器 供后续使用;
  • 之后,清除之前 $request 实例缓存;
  • 然后,启动「引导程序」;
  • 最后,发送请求至路由。
启动「引导程序」

上面的代码块说明在 $this->bootstrap(); 方法内部有实际调用「引导程序」,而 bootstrap() 实际调用的是 APP 容器的 bootstrapWith (), 来看看

1
2
3
4
5
6
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

protected function bootstrappers()
{
return $this->bootstrappers;
}
1
2
3
4
5
6
7
8
9
10
11
12
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;

foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

$this->make($bootstrapper)->bootstrap($this);

$this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
}
}

作为示例我们随便挑一个「引导程序」来看看其内部的启动原理。

这边我们选 Illuminate\Foundation\Bootstrap\LoadConfiguration::class,它的功能是加载配置文件。

还记得我们讲解「2.2 创建 Laravel 应用实例」章节的时候有「注册应用的基础路径并将路径绑定到 APP 容器」。此时,LoadConfiguration 类就是将 config 目录下的所有配置文件读取到一个集合中,这样我们就可以项目里通过 config() 辅助函数获取配置数据。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php

namespace Illuminate\Foundation\Bootstrap;

use Exception;
use SplFileInfo;
use Illuminate\Config\Repository;
use Symfony\Component\Finder\Finder;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Config\Repository as RepositoryContract;

class LoadConfiguration
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$items = [];

// First we will see if we have a cache configuration file. If we do, we'll load
// the configuration items from that file so that it is very quick. Otherwise
// we will need to spin through every configuration file and load them all.
if (file_exists($cached = $app->getCachedConfigPath())) {
$items = require $cached;

$loadedFromCache = true;
}

// Next we will spin through all of the configuration files in the configuration
// directory and load each one into the repository. This will make all of the
// options available to the developer for use in various parts of this app.
$app->instance('config', $config = new Repository($items));

if (! isset($loadedFromCache)) {
$this->loadConfigurationFiles($app, $config);
}

// Finally, we will set the application's environment based on the configuration
// values that were loaded. We will pass a callback which will be used to get
// the environment in a web context where an "--env" switch is not present.
$app->detectEnvironment(function () use ($config) {
return $config->get('app.env', 'production');
});

date_default_timezone_set($config->get('app.timezone', 'UTC'));

mb_internal_encoding('UTF-8');
}

/**
* Load the configuration items from all of the files.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param \Illuminate\Contracts\Config\Repository $repository
* @return void
*
* @throws \Exception
*/
protected function loadConfigurationFiles(Application $app, RepositoryContract $repository)
{
$files = $this->getConfigurationFiles($app);

if (! isset($files['app'])) {
throw new Exception('Unable to load the "app" configuration file.');
}

foreach ($files as $key => $path) {
$repository->set($key, require $path);
}
}

/**
* Get all of the configuration files for the application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return array
*/
protected function getConfigurationFiles(Application $app)
{
$files = [];

$configPath = realpath($app->configPath());

foreach (Finder::create()->files()->name('*.php')->in($configPath) as $file) {
$directory = $this->getNestedDirectory($file, $configPath);

$files[$directory.basename($file->getRealPath(), '.php')] = $file->getRealPath();
}

ksort($files, SORT_NATURAL);

return $files;
}

/**
* Get the configuration file nesting path.
*
* @param \SplFileInfo $file
* @param string $configPath
* @return string
*/
protected function getNestedDirectory(SplFileInfo $file, $configPath)
{
$directory = $file->getPath();

if ($nested = trim(str_replace($configPath, '', $directory), DIRECTORY_SEPARATOR)) {
$nested = str_replace(DIRECTORY_SEPARATOR, '.', $nested).'.';
}

return $nested;
}
}

所有 「引导程序」列表功能如下:

发送请求至路由
Laravel中使用管道模式

Laravel 在框架中的很多地方使用了管道设计模式,最常见的就是中间件的实现。

当请求最终到达控制器动作被处理前,会先经过一系列的中间件。每个中间价都有一个独立的职责,例如,设置 Cookie、判断是否登录以及阻止 CSRF 攻击等等。

每个阶段都会对请求进行处理,如果请求通过就会被传递给下一个处理,不通过就会返回相应的 HTTP 响应。

这种机制使得我们很容易在请求最终到达应用代码前添加处理操作,当然如果不需要这个处理操作你也可以随时移除而不影响请求的生命周期。

1
2
3
4
return (new Pipeline($this->app))   //容器
->send($request) //请求
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) //中间件
->then($this->dispatchToRouter()); //路由

在 「发送请求至路由」这行代码中,完成了:管道(pipeline)创建、将 $request 传入管道、对 $request 执行「中间件」处理和实际的请求处理四个不同的操作。

在开始前我们需要知道在 Laravel 中有个「中间件」 的概念,即使你还不知道,也没关系,仅需知道它的功能是在处理请求操作之前,对请求进行过滤处理即可,仅当请求符合「中间件」的验证规则时才会继续执行后续处理。

有关 「管道」的相关知识不在本文讲解范围内。参考: Laravel 中管道设计模式的使用 —— 中间件实现原理探究

那么,究竟一个请求是如何被处理的呢?

我们来看看 $this->dispatchToRouter() 这句代码,它的方法声明如下:

1
2
3
4
5
6
7
8
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);

return $this->router->dispatch($request);
};
}

这里的router在之前在构造函数我们已经将 Illuminate\Routing\Router 对象赋值给 $this->router 属性。 router 实例的 disptach() 方法去执行 HTTP 请求,在它的内部会完成如下处理:

  1. 查找对应的路由实例
  2. 通过一个实例栈运行给定的路由
  3. 运行在 routes/web.php 配置的匹配到的控制器或匿名函数
  4. 返回响应结果
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public function dispatch(Request $request)
{
$this->currentRequest = $request;

return $this->dispatchToRoute($request);
}

public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}

protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);

$this->container->instance(Route::class, $route);

return $route;
}

protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});

$this->events->dispatch(new Events\RouteMatched($route, $request));

return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}

protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;

$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}

执行 $route->run() 的方法定义在 Illuminate\Routing\Route 类中,最终执行「在 routes/web.php 配置的匹配到的控制器或匿名函数」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function run()
{
$this->container = $this->container ?: new Container;

try {
if ($this->isControllerAction()) {
return $this->runController();
}

return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}

这部分如果路由的实现是一个控制器,会完成控制器实例化并执行指定方法;如果是一个匿名函数则直接调用这个匿名函数。

其执行结果会通过 Illuminate\Routing\Router::prepareResponse($request, $response) 生一个响应实例并返回。

至此,Laravel 就完成了一个 HTTP 请求的请求处理。

发送响应

1
$response->send();
1
2
3
4
5
6
7
8
9
10
11
12
13
public function send()
{
$this->sendHeaders();
$this->sendContent();

if (\function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
}

return $this;
}

终止程序

1
$kernel->terminate($request, $response);
1
2
3
4
5
6
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);

$this->app->terminate();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected function terminateMiddleware($request, $response)
{
$middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
$this->gatherRouteMiddleware($request),
$this->middleware
);

foreach ($middlewares as $middleware) {
if (! is_string($middleware)) {
continue;
}

[$name] = $this->parseMiddleware($middleware);

$instance = $this->app->make($name);

if (method_exists($instance, 'terminate')) {
$instance->terminate($request, $response);
}
}
}

以上便是 Laravel 的请求生命周期的始末。

总结

在 「创建 Laravel 应用实例」时不仅会注册项目基础服务、注册项目服务提供者别名、注册目录路径等在内的一系列注册工作;还会绑定 HTTP 内核及 Console 内核到 APP 容器, 同时在 HTTP 内核里配置中间件和引导程序。

进入 「接收请求并响应」里,会依据运行环境从 APP 容器 解析出 HTTP 内核或 Console 内核。如果是 HTTP 内核,还将把「中间件」及「引导程序」注册到 APP 容器

所有初始化工作完成后便进入「处理 HTTP 请求」阶段。

一个 Http 请求实例会被注册到 APP 容器,通过启动「引导程序」来设置环境变量、加载配置文件等等系统环境配置;

随后请求被分发到匹配的路由,在路由中执行「中间件」以过滤不满足校验规则的请求,只有通过「中间件」处理的请求才最终处理实际的控制器或匿名函数生成响应结果。

最后发送响应给用户,清理项目中的中间件,完成一个 「请求」 - 「响应」 的生命周期,之后我们的 Web 服务器将等待下一轮用户请求。

Laravel路由

Laravel 框架门面 Facade 源码分析

1
2
3
Route::get('/', function () {
return view('welcome');
});

简单的路由背后实现比较复杂,下面一步一步剖析。

Facade 的原理

我们以 Route 为例,来讲解一下门面 Facade 的原理与实现。我们先来看 Route 的门面类:

1
2
3
4
5
6
7
8
#vendor\laravel\framework\src\Illuminate\Support\Facades\Route.php 
class Route extends Facade
{
protected static function getFacadeAccessor()
{
return 'router';
}
}

那么当我们写出 Route::get () 这样的语句时,到底发生了什么呢?奥秘就在基类 Facade 中。

1
2
3
4
5
6
7
8
9
10
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();

if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}

return $instance->$method(...$args);
}

当运行 Route::get () 时,发现门面 Route 没有静态 get () 函数,PHP 就会调用这个魔术函数__callStatic。我们看到这个魔术函数做了两件事:获得对象实例,利用对象调用 get () 函数。首先先看看如何获得对象实例的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}

protected static function getFacadeAccessor()
{
throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}

protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}

if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}

return static::$resolvedInstance[$name] = static::$app[$name];
}

我们看到基类 getFacadeRoot () 调用了 getFacadeAccessor (),也就是我们的服务重载的函数,如果调用了基类的 getFacadeAccessor,就会抛出异常。在我们的例子里 getFacadeAccessor () 返回了 “router”,接下来 getFacadeRoot () 又调用了 resolveFacadeInstance ()。在这个函数里重点就是

1
return static::$resolvedInstance[$name] = static::$app[$name];

我们看到,在这里利用了 $app 也就是服务容器创建了 “router”,创建成功后放入 $resolvedInstance 作为缓存,以便以后快速加载。
  好了,Facade 的原理到这里就讲完了,但是到这里我们有个疑惑,为什么代码中写 Route 就可以调用 Illuminate\Support\Facades\Route 呢?这个就是别名的用途了,很多门面都有自己的别名,这样我们就不必在代码里面写 use Illuminate\Support\Facades\Route,而是可以直接用 Route 了。

别名 Aliases

为什么我们可以在 laravel 中全局用 Route,而不需要使用 use Illuminate\Support\Facades\Route? 其实奥秘在于一个 PHP 函数:class_alias,它可以为任何类创建别名。laravel 在启动的时候为各个门面类调用了 class_alias 函数,因此不必直接用类名,直接用别名即可。在 config 文件夹的 app 文件里面存放着门面与类名的映射:

1
2
3
4
5
6
7
'aliases' => [

'App' => Illuminate\Support\Facades\App::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,
...
]

启动别名 Aliases 服务

说到 laravel 的启动,我们离不开 index.php:说到 laravel 的启动,我们离不开 index.php:

1
2
3
4
5
6
7
8
9
10
require __DIR__.'/../bootstrap/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
...

第一句就是我们前面博客说的 composer 的自动加载,接下来第二句获取 laravel 核心的 Ioc 容器,第三句 “制造” 出 Http 请求的内核,第四句是我们这里的关键,这句牵扯很大,laravel 里面所有功能服务的注册加载,乃至 Http 请求的构造与传递都是这一句的功劳。

1
$request = Illuminate\Http\Request::capture()

  这句是 laravel 通过全局 $_SERVER 数组构造一个 Http 请求的语句,接下来会调用 Http 的内核函数 handle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();

$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);

$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));

$response = $this->renderException($request, $e);
}

event(new Events\RequestHandled($request, $response));

return $response;
}

  在 handle 函数方法中 enableHttpMethodParameterOverride 函数是允许在表单中使用 delete、put 等类型的请求。我们接着看 sendRequestThroughRouter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);

Facade::clearResolvedInstance('request');

$this->bootstrap();

return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] :
$this->middleware)
->then($this->dispatchToRouter());
}

  前两句是在 laravel 的 Ioc 容器设置 request 请求的对象实例,Facade 中清楚 request 的缓存实例。bootstrap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}

protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

  $bootstrappers 是 Http 内核里专门用于启动的组件,bootstrap 函数中调用 Ioc 容器的 bootstrapWith 函数来创建这些组件并利用组件进行启动服务。app->bootstrapWith:

1
2
3
4
5
6
7
8
9
10
11
12
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;

foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

$this->make($bootstrapper)->bootstrap($this);

$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}

  可以看到 bootstrapWith 函数也就是利用 Ioc 容器创建各个启动服务的实例后,回调启动自己的函数 bootstrap,在这里我们只看我们 Facade 的启动组件

1
\Illuminate\Foundation\Bootstrap\RegisterFacades::class

RegisterFacades 的 bootstrap 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
php
class RegisterFacades
{
public function bootstrap(Application $app)
{
Facade::clearResolvedInstances();

Facade::setFacadeApplication($app);

AliasLoader::getInstance($app->make('config')->get('app.aliases', []))
->register();
}
}

  可以看出来,bootstrap 做了一下几件事:

  1. 清除了 Facade 中的缓存
  2. 设置 Facade 的 Ioc 容器
  3. 获得我们前面讲的 config 文件夹里面 app 文件 aliases 别名映射数组
  4. 使用 aliases 实例化初始化 AliasLoader
  5. 调用 AliasLoader->register ()
1
2
3
4
5
6
7
8
9
10
11
12
13
public function register()
{
if (! $this->registered) {
$this->prependToLoaderStack();

$this->registered = true;
}
}

protected function prependToLoaderStack()
{
spl_autoload_register([$this, 'load'], true, true);
}

  我们可以看出,别名服务的启动关键就是这个 spl_autoload_register,这个函数我们应该很熟悉了,在自动加载中这个函数用于解析命名空间,在这里用于解析别名的真正类名。

1
2
3
4
5
6
7
8
9
10
11
12
13
public function load($alias)
{
if (static::$facadeNamespace && strpos($alias,
static::$facadeNamespace) === 0) {
$this->loadFacade($alias);

return true;
}

if (isset($this->aliases[$alias])) {
return class_alias($this->aliases[$alias], $alias);
}
}

  这个函数的下面很好理解,就是 class_alias 利用别名映射数组将别名映射到真正的门面类中去,但是上面这个是什么呢?实际上,这个是 laravel5.4 版本新出的功能叫做实时门面服务。

-------------本文结束感谢您的阅读-------------