laravel 源码解读

date: 2017-12-10 22:24:18
title: laravel 源码解读

yii/laravel 的源码质量超高, phper 一定要好好读一读

源码解读: http://naotu.baidu.com/file/9d4b5ab081c174ebfaf2af01db81bc5b?token=9fb122830b33d1c8

最近写的几篇 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 打转, 包装一下, 实现了各种不同的书写方式.

3 种方式的对比

首先来看 DI 和 Facade, 不知道大家会不会和我一样有这样的疑问:

// 书写控制器代码时, 有 2 种方式:

public function index(Request $request)
{
    $input = $request->all();
}

public function index()
{
    $input = Request::all();
}

当我们写 Request 时, 编辑器会提示 2 个出来 \Illuminate\Http\Request\Request. 刚开始接触的时候, 这个地方疑惑了很久, 大家现在可以区分出来了吧, 一个是 DI, 一个是 Facade, 名字虽然 一样.

这个例子也可以看出 DI 和 Facade 的区别.

再来看看 Facade 和 helper function, 其实上面列举过一部分了, 至少上面的例子看起来 helper function 要简洁一点, 但是, 这个是有限的:

Cache::get($key, $timeout); // 缓存可以设置过期时间, Cache Facade 直接加一下参数就行了

cache()->get($key, $timeout); // Cache helper function 没有封装, 所有要先用 cache() 来返回 $app['cache'], 然后再调用

这么多实现方式, 你是感觉 有点晕, 还是像官方说的 艺术 ?

写法上有了更多的自由(简写), 提高生产力还是蛮明显的.

当然, 简写带来一个代码提示的问题, 会影响生产力的, 这个可以看我之前的 blog - 聊一聊 php 代码提示.

最后再看一个套路

比如说 Cache 这个服务, 可以使用多种方式, 但是我们在使用的时候, 都是一样的方式, 这个是怎么实现的呢?

  • 首先, 还是 Contract, 怎么实现我不管, 反正要按我说的来, 这个是贯穿始终的
  • 然后, 在有多种实现的方式时, laravel 惯用 manager-driver 这种方式

还是以 Cache service 为例:

// CacheServiceProvider 中这样注册的服务
public function register()
{
    $this->app->singleton('cache', function ($app) { // 这就是我们常用的 $app['cache'], 实际使用的 CacheManager
        return new CacheManager($app);
    });

    $this->app->singleton('cache.store', function ($app) { // 继续看
        return $app['cache']->driver();
    });

    ...
}

// CacheManager
// 魔法函数, 比如执行 $app['cahe']->get($key), 其实最终执行的 $this->store()->get($key)
public function __call($method, $parameters)
{
    return $this->store()->$method(...$parameters); 
}
public function store($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return $this->stores[$name] = $this->get($name);
}

继续读代码, 会发现最终通过 $name 去寻找类中 create{$ame}() 方法来实例化 $this->store()

这种 manager-driver 的方式, 在 laravel 中大量使用, 比如 filesystem, Notification, Queue

写在最后

写着写着发现又是一个长文, 简直 心累, 明明是很简单的东西呀, 看着代码一点一点读就好了, 写起文字来却要 巴拉巴拉 这么多.

明明很简单的东西呀, 弄清楚生命周期和底层实现, 剩下的一点一点读代码就好了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • 先说几句废话,调和气氛。事情的起由来自客户需求频繁变更,伟大的师傅决定横刀立马的改革使用新的框架(created ...
    wsdadan阅读 3,039评论 0 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 过去做事情急,什么东西拿起来就用,不喜欢进行系统性的学习,造成在使用过程中的错误和低效,现在感觉自己耐心多了,用之...
    马文Marvin阅读 1,965评论 0 10
  • 原文链接 必备品 文档:Documentation API:API Reference 视频:Laracasts ...
    layjoy阅读 8,603评论 0 121
  • 介绍 Facades 为应用的 IoC 服务容器 的类提供了一个静态的接口。Laravel 里面自带了一些 Fac...
    伊Summer阅读 11,031评论 0 11