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. .Net Core 3.0依赖注入替换 Autofac

    今天早上,喜庆的更新VS2019,终于3.0正式版了呀~ 有小伙伴问了一句Autofac怎么接入,因为Startup.ConfigureServices不能再把返回值改成IServiceProvide ...

  2. JS中escape()、encodeURI()、encodeURIComponent()区别详解

    avaScript中有三个可以对字符串编码的函数,分别是: escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decode ...

  3. 用Python快速实现视频的人脸融合

  4. Java 命令行 编译、执行、打包

    Java 命令行 编译.执行.打包 一般来说 IDE 能够很方便的编译打包. 我写这篇文章是遇到了不能使用 IDE 的情况,简单记录一下,不做深入探讨. 环境 linux jdk 1.8 简单的编译执 ...

  5. Bytom 储蓄分红 DAPP 开发指南

    储蓄分红DAPP 储蓄分红合约简介 储蓄分红合约指的是项目方发起了一个锁仓计划(即储蓄合约和取现合约),用户可以在准备期自由选择锁仓金额参与该计划,等到锁仓到期之后还可以自动获取锁仓的利润.用户可以在 ...

  6. 基于Asp.net Core 3.1实现的Redis及MemoryCache缓存助手CacheHelper

    这几天在面试,这个关于Redis缓存的博客一直没空写,今天总算有点时间了. 从很久很久之前,我就一直想学Redis了,反正看到各大招聘网上都要求Redis,不学就太落后了. 一开始我是按微软官网文档那 ...

  7. Vue watch对象属性并触发多个事件

    在vue中监控一个对象的属性变化,并且触发监听事件 watch: { 'user': [ { handler: (nweVal, oldVal) => { console.info('in 1 ...

  8. 用它5分钟以后,我放弃用了四年的 Flask

    有一个非常简单的需求:编写一个 HTTP接口,使用 POST 方式发送一个 JSON 字符串,接口里面读取发送上来的参数,对其中某个参数进行处理,并返回. 如果我们使用 Flask 来开发这个接口,那 ...

  9. 微信小程序携带参数跳转页面/获取页面栈

    页面跳转携带参数(以传递两个参数为例) a.wxml 页面传递 1 <navigator url="/pages/b/b?id=1&sid='289'"> &l ...

  10. RPC 框架 Dubbo 从理解到使用(一)

    技术架构演变 单一应用架构 通俗地讲,"单体应用(monolith application)"就是将应用程序的所有功能都打包成一个独立的单元.当网站流量很小时,只需一个应用,将所有 ...