Laravel Facade原理及使用

laravel过于庞大,加之笔者水平有限,所以后面的源码解读会按模块功能介绍,希望能帮大家稍微捋顺下思路,即使能够帮助大家回顾几个函数也好。如发现错误,还望指正。

  • facade工作方式,允许我们可以通过静态调用的方式直接使用容器中的服务
  • 原理讲解,在laravel的routes/web.php等路由文件下,经常可以看到类似的写法
<?php
Route::get('zbc', function () {
app()->make(App\Http\Controllers\Zbc\TestController::class);
});
// 可以看到通过Route::get方法添加了此路由规则(不仅如此laravel存在大量这般的静态调用方式),但是并没有看到此文件中引用Route类,并且laravel框架中并没有此Route类,
// 通过打印get_declared_classes确实存在此Route(route)类,只有一个解释,那就是laravel引导过程中‘生成了’这个类,下面就会讲解如何‘生成’的这个类,这个类是根据什么‘生成’的。
  • 上文讲到在index.php中拿到了laravel的’黑盒子‘$kernel,下面继续看kernel的handle方法
// Illuminate\Foundation\Http\Kernel文件中
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
// 这里的方法大家看名字就能大概知道什么用处,本问只讲解sendRequestThroughRouter中的facade注册部分
try {
$request->enableHttpMethodParameterOverride();
// 通过路由或者中间件处理给定的请求
// 跳转到sendRequestThroughRouter方法
$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 RequestHandled($request, $response)
); return $response;
} protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request'); // 引导app
// 跳转到bootstrap方法
$this->bootstrap(); // laravel的pipeline以后会专门讲解
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
} // $this->app是make的时候通过构造方法注入进来的(参考上篇文章)
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
// 如果laravel不能存在引导完成标志,就进行引导
// 跳转到bootstrapWith方法,传递的参数如下
// protected $bootstrappers = [
// \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
// \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
// \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
// 我们的facade'生成'(注册)就在此完成
// \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
// \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
// \Illuminate\Foundation\Bootstrap\BootProviders::class,
// ];
$this->app->bootstrapWith($this->bootstrappers());
}
} // Application下的bootstrapWith方法
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) {
$this['events']->dispatch('bootstrapping: ' . $bootstrapper, [$this]);
// 本文只讲解facaderegister
// 所以此处应该的$$bootstrapper = Illuminate\Foundation\Bootstrap\RegisterFacades
// 如果看过前面的文章可以知道容器并没有绑定过此abstract更不可能存在解析过的instance
// 所以容器的make方法走的一定是php的反射机制,然后调用bootstrap方法
// 跳转到RegisterFacades的bootstrap方法
$this->make($bootstrapper)->bootstrap($this); $this['events']->dispatch('bootstrapped: ' . $bootstrapper, [$this]);
}
} public function bootstrap(Application $app)
{
Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); // 跳转到getInstance方法
AliasLoader::getInstance(array_merge(
// 拿到config/app.php下的aliases数组 如下
// 'App' => Illuminate\Support\Facades\App::class,
// 'Arr' => Illuminate\Support\Arr::class,
// ...
// 'Route' => Illuminate\Support\Facades\Route::class,
// ...
// 'Validator' => Illuminate\Support\Facades\Validator::class,
// 'View' => Illuminate\Support\Facades\View::class,
$app->make('config')->get('app.aliases', []),
// 需要修改composer.json文件 配合包自动发现 这个类就是这个用处的
$app->make(PackageManifest::class)->aliases()
))->register();
} // Illuminate\Foundation\AliasLoader类
// 方法很简单
public static function getInstance(array $aliases = [])
{
if (is_null(static::$instance)) {
return static::$instance = new static($aliases);
} $aliases = array_merge(static::$instance->getAliases(), $aliases); static::$instance->setAliases($aliases); return static::$instance;
} // 继续看register方法
/**
* Register the loader on the auto-loader stack.
*
* @return void
*/
public function register()
{
if (!$this->registered) {
// 继续跳转prependToLoaderStack到方法
$this->prependToLoaderStack(); $this->registered = true;
}
} // 可以看到此方法在加载函数队列首部添加了一个load加载函数
// spl_autoload_register方法的参数在composer第一篇有讲解
protected function prependToLoaderStack()
{
// 跳转到load方法
spl_autoload_register([$this, 'load'], true, true);
} /**
* Load a class alias if it is registered.
*
* @param string $alias
* @return bool|null
*/
public function load($alias)
{
if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
$this->loadFacade($alias);
return true;
} // 由于
if (isset($this->aliases[$alias])) {
// 重点!!!
// 在此案例中$alias传递进来的是我们在路由文件web.php中使用的Route
return class_alias($this->aliases[$alias], $alias);
} // 为了方便理解,可做如下简单修改
// config/app.php的aliases数组中存在Route映射 条件表达式为true
if (isset($this->aliases[$alias])) {
dump($alias); // 打印为Route,就是我们在web.php中使用的Route
dump(get_declared_classes()); // 一堆类名组成的数组 但是不包括route
// 关键在此函数 class_alias 第一个参数是original class 第二个参数是给这个类起的别名
// 最重要的第三个参数默认为true 表示如果原类没找到 是否可以通过别名自动加载这个类
// class_alias返回bool
// 返回的只是bool值,load加载器并没有真正的引入实际的Illuminate\Support\Facades\Route类
// 所以php会继续调用composer的加载器真正的加载此类,此加载器只是取巧的设置了别名,方便使用
class_alias($this->aliases[$alias], $alias);
dump(get_declared_classes()); // 一堆类名组成的数组 但是包括了route
return true;
}
} // 下面看Illuminate\Support\Facades\Route类
// 非常简单只有一个方法,并没有发现web.php中的Route::get方法,便会触发php的魔术方法__callStatic(请在自省查阅手册),这是laravel facade静态调用实现的根本方式
// 跳转到父类Facade
class Route extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'router';
}
} // Illuminate\Support\Facades\Facade类
public static function __callStatic($method, $args)
{
// 使用static关键字,实现延迟绑定,此案例中代表Illuminate\Support\Facades\Route
// 跳转到getFacadeRoot方法
$instance = static::getFacadeRoot(); if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
} return $instance->$method(...$args);
} /**
* Get the root object behind the facade.
*
* @return mixed
*/
// 官方注释已经很完美了,获取门面背后的真实对象
public static function getFacadeRoot()
{
// 注意使用的是static 静态延迟绑定 此案例中代表Illuminate\Support\Facades\Route
// 跳转到resolveFacadeInstance方法
return static::resolveFacadeInstance(static::getFacadeAccessor());
// return static::resolveFacadeInstance('router');
} /**
* Resolve the facade root instance from the container.
*
* @param object|string $name
* @return mixed
*/
// 从容器中解析门面对应的根对象
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
} if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
} if (static::$app) {
// $app为Application对象但是为什么采用数组的形式进行访问呢($app['router'])
// 因为Application类继承了Container类,而Container实现了spl类库提供的ArrayAccess接口
// 关于ArrayAccess请自行查阅文档
// 当通过数组的形式访问对象的时候 会触发offsetGet方法,跳转到Container的offsetGet方法
return static::$resolvedInstance[$name] = static::$app[$name];
}
} /**
* Get the value at a given offset.
*
* @param string $key
* @return mixed
*/
public function offsetGet($key)
{
// $key在此案例中等于router
// 通过容器进行解析router
// router在Application中实例化的registerBaseServiceProvider中实现的注册,前面的文章有讲解
// 一路返回到__callStatic方法中
return $this->make($key);
} public static function __callStatic($method, $args)
{
// 使用static关键字,实现延迟绑定,此案例中代表Illuminate\Support\Facades\Route
// 跳转到getFacadeRoot方法
$instance = static::getFacadeRoot(); if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
// 到此调用$router的get方法 完美!!!
// 对应的方法看这里 Illuminate\Routing\Router::get
return $instance->$method(...$args);
}

以上便是laravel facade的基本实现方式,大家可能已经看出来这就是php实现门面(代理)模式的方法。

个人不喜欢使用门面模式,更喜欢直接调用系统函数等方式直接从容器中解析对象,感觉可以规避一下__callStatic的调用。

下面讲解如何在laravel中使用自己的facade

1. 创建契约
<?php namespace App\Contracts; interface MyFacade
{
public function thereYouGo();
} 2. 创建服务
<?php namespace App\Services; use App\Contracts\MyFacade; class TestService implements MyFacade
{
public function thereYouGo()
{
echo '春风习习可盈袖, 不及伊人半点红';
}
} 3. 创建服务提供者 php artisan make:provider MyFacadeProvider
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider;
use App\Services\TestService; class MyFacadeProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
// 注意此处的$abstract(MyLove)要和facade中getFacadeAccessor方法返回值一致
$this->app->bind('MyLove', function () {
return new TestService();
});
} /**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
} 4. 创建门面
<?php namespace App\Facades; use Illuminate\Support\Facades\Facade; class MyFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'MyLove';
}
} 5. 注册服务和门面 config/app.php下添加
'providers' => [
...
App\Providers\MyFacadeProvider::class,
],
'aliases' => [
...
'MyFacade' => App\Services\TestService::class,
] 6. 测试
use App\Facades\MyFacade;
Route::get('myfacade', function () {
MyFacade::thereYouGo();
});

可以看到实现一个facade真的费时费力,并且性能不好,不建议自行创建facade使用,更建议使用容器直接解析,当然硬编码可能更适合追求速度的开发,不管怎样开心撸码最重要。

今天没有下集预告

Laravel Facade原理及使用的更多相关文章

  1. Laravel Pipeline原理及使用

    Laravel Pipeline原理及使用 开发中可能遇到非常冗长的逻辑,以至于我们想将针对性逻辑拆分出来,但是又拿不准该如何拆分才能实现较高的扩展性并保证较高的维护性,或者说不知道如何优雅的将待处理 ...

  2. Laravel 认证原理及完全自定义认证

    Laravel 默认的 auth 功能已经是很全面了,但是我们也经常会碰到一些需要自定义的一些情况,比如验证的字段和默认的不匹配,比如需要能够同时满足 user name 和 email 认证等等.如 ...

  3. Laravel底层原理系列

    Laravel 从学徒到工匠精校版 地址:https://laravelacademy.org/laravel-from-appreciate-to-artisan Advanced Applicat ...

  4. LARAVEL 路由原理分析

    <?php class App {    protected $routes = [];    protected $responseStatus = '200 OK';    protecte ...

  5. laravel使用过程中一些总结

    推荐连接: laravel辅助函数总结:https://laravel-china.org/docs/laravel/5.5/helpers 基于 Laravel 集成的 Monolog 库对日志进行 ...

  6. laravel/lumen 单元测试

    Testing Introduction Application Testing Interacting With Your Application Testing JSON APIs Session ...

  7. 2016 版 Laravel 系列入门教程(二)【最适合中国人的 Laravel 教程】

    本教程示例代码见: https://github.com/johnlui/Learn-Laravel-5 在任何地方卡住,最快的办法就是去看示例代码. 本篇文章中,我将跟宝宝们一起学习 Laravel ...

  8. 如何使用 Laravel Facades ?

    Facade 布局是在面向对象编程中经常使用的一种软件设计布局方式.Facade 实际上是一种包括复杂函数库的类,提供了更加简洁易读的接口.Facade 布局还能为一组结构复杂.设计简陋的 API 提 ...

  9. Laravel 系列入门教程(二)【最适合中国人的 Laravel 教程】

    本篇文章中,我将跟大家一起体验 Laravel 框架最重要的部分——路由系统. 如果你读过 2015 版的教程,你会发现那篇文章里大书特书的 Auth 系统构建已经被 Laravel 捎带手给解决了. ...

随机推荐

  1. Spring的事务抽象

    Spring提供了一致的事务管理抽象,该抽象能实现为不同的事务API提供一致的编程模型.无视我们使用jdbc.hibernate.mybatis哪种方式来操作数据,无视事务是jta事务还是jdbc事务 ...

  2. AsyncTask被废弃了,换Coroutine吧

    本文主要是学习笔记,有版权问题还请告知删文 鸣谢:guolin@第一行代码(第三版) 你是否也在最近的代码中看见了 AsyncTask 被一条横杠划掉了 这表明--他要被Google放弃了 Googl ...

  3. stat 命令家族(1)- 详解 vmstat

    性能测试必备的 Linux 命令系列,可以看下面链接的文章哦 vmstat 介绍 Virtual Meomory Statistics,报告虚拟内存统计信息 会统计进程信息.内存.交换区.IO.磁盘. ...

  4. Json字符串与QVariantList 对象相互转换

    在Qt中QVariantList 使用起来很方便,如果涉及到数据的传输,需要将QVariantList 数据转换为JsonArray字符串,这个转换Qt已经实现好了,只需要调用接口就可以完成转换,代码 ...

  5. 重温这几个屌爆的Python技巧!

    我已经使用Python编程有多年了,即使今天我仍然惊奇于这种语言所能让代码表现出的整洁和对DRY编程原则的适用.这些年来的经历让我学到了很多的小技巧和知识,大多数是通过阅读很流行的开源软件,如Djan ...

  6. java 多态一

    一 多态的概述 现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学 生也是人,即出现两种形态. Java作为面向对象的语言,同样可以描述一个事物的多种形态.如Studen ...

  7. 2020-08-02:输入ping IP 后敲回车,发包前会发生什么?

    福哥答案2020-08-02: 首先根据目的IP和路由表决定走哪个网卡,再根据网卡的子网掩码地址判断目的IP是否在子网内.如果不在则会通过arp缓存查询IP的网卡地址,不存在的话会通过广播询问目的IP ...

  8. C#LeetCode刷题之#62-不同路径(Unique Paths)

    目录 问题 示例 分析 问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3680 访问. 一个机器人位于一个 m x ...

  9. C#LeetCode刷题之#258-各位相加(Add Digits)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3860 访问. 给定一个非负整数 num,反复将各个位上的数字相加 ...

  10. Vue 使用$createElement自定义文本

    有时候弹窗或者一些特殊的区域需要自定义一些html元素来显示(snabbdom) const h = this.$createElement this.$msgbox({ title: '提示', m ...