正文
date: 2017-12-10 22:24:18 title: laravel 源码解读
yii/laravel 的源码质量超高, phper 一定要好好读一读
源码解读:
naotu.baidu.com/file/9d4b5a…
最近写的几篇 blog 都很长, 事无巨细, 基础差一点的同学看起来会
舒服
一些, 但是基础好一些的同学就会感觉不够深入,
干货
不够. 而且写长文确实挺累的, 所以这篇尽量抓框架结构, 整体架构和思路, 细节地方的点, 以后再和大家慢慢分享啦.
生命周期 lifecycle
熟悉我的同学, 就知道我特别强调这个概念. 生命周期是连接具体业务与底层实现很好的桥梁, 熟悉生命周期, 既可以培养软件工程能力, 也可以培养对业务的理解能力.
重要的事情无限循环: 生命周期.
laravel 的生命周期:
初始化
-
使用
$basePath
初始化(
new
)
Illuminate\Foundation\Application
, Application 类其实一个容器, 用来解决类的依赖关系, 遵循
PSR
标准
-
Application 绑定(
bind
) 3 个单例:
App\Http\Kernel
,
App\Console\Kernel
,
App\Exceptions\Handler
, 请求由具体的 Kernel 来执行. 容器用来解决类的依赖关系是
按需
的, 这里都是申明的
单例
方式. 这里要注意 laravel
中亮眼的点: 绑定是按照
Illuminate\Contracts\Xxx -> App\xxx
的约束来实现, 全局统一. Contract 我们后面会继续讲到.
具体处理
-
Application 根据场景(web / console), 实例化(
make
)化相应的 Kernel
-
Kernel 实例化的过程, 先进入 bootstrap 阶段: 注册(
register
) ServiceProvider, 执行由 Application 进行, 注意
注册
是必须的
关系申明
, 实际还是由容器来完成
按需
加载.
-
使用 php Global 变量里信息来初始化 Request 对象, 比如
$_SERVER
-
Kernel 处理 Request 对象, 返回 Response 对象
-
最后 Kernel 执行 terminate() 收尾
Kernel 处理 Request 流程
-
先执行定义的 Middleware
-
交给 Router 对象进行处理: 进行路由匹配, 匹配到 闭包 / Controller 进行逻辑处理, 返回数据
-
组装上面返回的数据, 并根据一些其他配置, 比如格式(format), 拼装成 Response 对象
大家会发现没有讲到自己熟悉的 MVC, 因为 MVC 也是有 Application 来进行管理一个个对象而已, 其他的所有服务也是.
有了 Container 这个基础, 再加上对生命周期的理解, 就可以根据业务按需填充代码进来了.
合约 Contract
生命周期和容器对于现代化框架来说, 基本是大同小异, 我们说说 laravel 不同的地方 -- Contract.
先介绍 2 个编程思想:
面向接口编程
约定大于配置
这 2 个思想其实是有重叠, 强调了在系统设计规范中,
定义
与
实现
应该分离.
laravel 始终在践行这一思想, 永远
定义
先行, 框架提供的所有功能, 都能在这里找到
源头
. 有人也喜欢说是
影子
.
更通俗一点: 怎么实现我不管, 反正要按照我说的来.
没错, 现实其实就是这样的, 需要大家
带着镣铐跳舞
.
在 laravel 中, 定义一个 功能/服务, 需要经历下面这一系列步骤:
-
Contract 中定义接口
-
Foundation 中定义基础实现, 大量的 Trait 和与这个服务相关的基本功能
-
具体的 功能/服务 实现, 需要按照 Contract 中的定义进行实现, 并且需要添加 ServiceProvider, 将服务注册到 Container 上
-
Support 中提供一些通用, 与具体 业务/服务 无关的辅助方法, 供具体 功能/服务 实现时使用
这一套做完, 我们就可以按照
XxxServiceProvider
中 register() 方法中定义, 使用
$app['xxx']
这种形式来访问我们需要的服务.
比如:
// AuthServiceProvider
protected function registerAuthenticator()
{
$this->app->singleton('auth', function ($app) {
// Once the authentication service has actually been requested by the developer
// we will set a variable in the application indicating such. This helps us
// know that we need to set any queued cookies in the after event later.
$app['auth.loaded'] = true;
return new AuthManager($app);
});
$this->app->singleton('auth.driver', function ($app) {
return $app['auth']->guard();
});
}
// 需要使用 Auth 服务
$auth = $app['auth']
这里提示一点
, 通常都会设置一个类属性
$app
, 赋值为框架初始化时的 Application, 这样所有的依赖关系, 都可以使用
$app
变量来统一处理, 既简洁又简单.
为什么 laravel 写起来这么舒服
这个标题其实是为了解释 laravel 的 slogan:
Love beautiful code? We do too. The PHP Framework For Web Artisans
有了上面介绍的部分, 我们需要的核心功能都已经用了, 剩下的就看我们打算怎么用了. 其实已经介绍了一种方式:
$app['auth']
, 调用注册的 auth 服务.
DI
DI, dependency injection, 依赖注入. 这种方式大家比较常见:
// 框架提供的注册功能
/**
* Handle a registration request for the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
直接在函数参数中传递我们需要的类
\Illuminate\Http\Request $request
, 然后容器就会自动提供这个类给我们使用
helper function
先说说广义上的 helper function, 通过 composer 的自动加载来实现:
// laravel/framework/src/composer.json
"autoload": {
"files": [
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
],
"psr-4": {
"Illuminate\\": "src/Illuminate/"
}
},
可以看到这里, laravel 注册了 2 类 helper function:
-
Foundation/helpers.php
: 框架功能有关的帮助函数
-
Support/helpers.php
: 框架功能无关的通用帮助函数, 比如数组的处理, 字符串的处理
这里就来讲讲框架功能有关的帮助函数:
// 核心函数
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract)) {
return Container::getInstance(); // 相当于上面的 $app
}
return Container::getInstance()->make($abstract, $parameters); // 相当于上面的 $app['auth']
}
// 使用效果
app() -> $app
app('auth') -> $app['auth']
看看这个有多么方便, 以 cache service 为例:
// 实现
function cache()
{
$arguments = func_get_args();
if (empty($arguments)) {
return app('cache');
}
if (is_string($arguments[0])) {
return app('cache')->get($arguments[0], $arguments[1] ?? null);
}
if (! is_array($arguments[0])) {
throw new Exception(
'When setting a value in the cache, you must pass an array of key / value pairs.'
);
}
if (! isset($arguments[1])) {
throw new Exception(
'You must specify an expiration time when setting a value in the cache.'
);
}
return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1]);
}
// 使用效果
cache($key); // 获取缓存
cache($key, $value); // 设置缓存
是不是 hin 方便?
Facade
Facade 设计模式: 通过外观包装, 隐藏具体细节对象, 降低应用程序的复杂度
先看效果, 同样用上面的 Cache 作为例子:
\Cache::get($key);
\Cache::set($key, $value);
好像比 helper function 还是要复杂一点, 对比我们最后再做. 先看看 Facade 是怎么实现的.
先根据官方文档设置一个新的 Facede:
-
先到
config/app.php
中注册, ServiceProvider 的注册也在这里
-
新建一个 Facede 类, 继承自
Illuminate\Support\Facades\Facade
比如这里的 Cache Facade:
namespace Illuminate\Support\Facades;
class Cache extends Facade
{
protected static function getFacadeAccessor() // 只需要实现这个函数就好
{
return 'cache';
}
}
那原理是啥呢? 继续看
Illuminate\Support\Facades\Facade
的代码:
// 魔法函数
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot(); // 上面实现的 getFacadeAccessor() 就在这里使用到, 最后其实返回的 $app['cache']
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
所以, 执行
\Cache::get($key);
最终执行了
$app['cache']->get($key);
分析到这里, 大家就会发现, 其实都是围绕着
$app
打转,
包装一下
, 实现了各种不同的书写方式.