Laravel中Facade的加载过程与原理详解


Posted in PHP onSeptember 22, 2017

前言

本文主要给大家介绍了关于Laravel中Facade加载过程与原理的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

简介

Facades(读音:/fəˈsäd/ )为应用程序的 服务容器 中可用的类提供了一个「静态」接口。你不必 use 一大串的命名空间,也不用实例化对象,就能访问对象的具体方法。

use Config;

class Test
{
 public function index()
 {
 return Config::get('app.name');
 }
}

Facade 的启动与注册

Facade 的启动引导是在 Illuminate\Foundation\Bootstrap\RegisterFacades 中注册的。

public function bootstrap(Application $app)
{
 Facade::clearResolvedInstances();
 Facade::setFacadeApplication($app);

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

默认的别名配置是从 app 配置文件下的 aliases 读取的,PackageManifest 是 laravel 5.5 新增的 包自动发现 规则,这里我们暂时不考虑 PackageManifest 包提供的别名。

其中,array_merge 返回如下格式的数组:

"App" => "Illuminate\Support\Facades\App"
 "Artisan" => "Illuminate\Support\Facades\Artisan"
 "Auth" => "Illuminate\Support\Facades\Auth"
 "Blade" => "Illuminate\Support\Facades\Blade"
 ...

上面代码将通过 AliasLoader 把所有的 facade 注册进自动加载。其核心就是 php 的 spl_autoload_register。

/**
 * Prepend the load method to the auto-loader stack.
 *
 * @return void
 */
 protected function register()
 {
 if (! $this->registered) {
  spl_autoload_register([$this, 'load'], true, true);

  $this->registered = true;
 }
 }

注册完成后,后续所有 use 的类都将通过 load 函数来完成类的自动加载。

注意:这里在定义 spl_autoload_register 时,最后面的参数传的是 true。当该参数是 true 时,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。(优先通过该函数来完成自动加载)

也就是说,

<?php

use Config;
use App\User;

class Test
{
 public function index()
 {
 Config::get('app.name');
 new User();
 }
}

不管我们 use 的是具体存在的类(App\User)还是别名 (Config),都将最先通过 load 函数来完成自动加载,当该函数返回 false 时,再由其他自动加载函数来完成自动加载(如 composer psr-4)。

在 AliasLoader 的 load 方法中,主要是用了 class_alias 函数来实现的别名自动加载。

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

关于 class_alias 这里帖一个官方的列子:

class foo { }

class_alias('foo', 'bar');

$a = new foo;
$b = new bar;

// the objects are the same
var_dump($a == $b, $a === $b); //true
var_dump($a instanceof $b); //false

// the classes are the same
var_dump($a instanceof foo); //true
var_dump($a instanceof bar); //true

var_dump($b instanceof foo); //true
var_dump($b instanceof bar); //true

Facade 的加载

当我们在使用 Facade 时,如:

<?php

use Config;

class Test
{
 public function index()
 {
 Config::get('app.name');
 }
}

实际上加载的是 Illuminate\Support\Facades\Config 类(因为我们已经注册了 class_alias),相当于:

<?php

use Illuminate\Support\Facades\Config;

class Test
{
 public function index()
 {
  Config::get('app.name');
 }
}

而所有的 Facade 都继承自 Illuminate\Support\Facades\Facade 类,在该基类中定义了一个 __callStatic 方法,已至于我们能够轻松地使用 Facade(不用实列化)。

<?php

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);
}

getFacadeRoot 方法用于获取别名类的具体实列,我们知道,所有的 Facade 类都需要定义一个 getFacadeAccessor 方法。该方法可能的返回值有:

  • String 类型的字符串(如 config, db)
  • String 类型的类字符串 (如 App\Service\SomeService)
  • Object 具体的实列化对象
  • Closure 闭包

如 Config Facade 的 getFacadeAccessor 方法如下:

protected static function getFacadeAccessor()
{
 return 'config';
}

getFacadeRoot 方法将根据 getFacadeAccessor() 的返回值,从容器从取出对应的实列对象。

public static function getFacadeRoot()
{
 $name = static::getFacadeAccessor();
 
 if (is_object($name)) {
  return $name;
 }

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

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

由于 APP 容器中已经注册过 config 的实列

<?php
//Illuminate\Foundation\Bootstrap/LoadConfiguration

$app->instance('config', $config = new Repository($items));

所以 \Config::get('app.name', 'dafault) 实际访问的是 Repository 实列的 get('app.name', 'default') 方法。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
浅谈Windows下 PHP4.0与oracle 8的连接设置
Oct 09 PHP
PHP6 mysql连接方式说明
Feb 09 PHP
php设计模式 Strategy(策略模式)
Jun 26 PHP
PHP表单提交表单名称含有点号(.)则会被转化为下划线(_)
Dec 14 PHP
完整删除ecshop中获取店铺信息的API
Dec 24 PHP
Yii学习总结之安装配置
Feb 22 PHP
PHP多线程编程之管道通信实例分析
Mar 07 PHP
CodeIgniter视图使用注意事项
Jan 20 PHP
PHP getallheaders无法获取自定义头(headers)的问题
Mar 23 PHP
PHP插件PHPMailer发送邮件功能
Feb 28 PHP
Laravel 实现关系模型取出需要的字段
Oct 10 PHP
laravel框架创建授权策略实例分析
Nov 22 PHP
laravel实现分页样式替换示例代码(增加首、尾页)
Sep 22 #PHP
深入理解PHP的远程多会话调试
Sep 21 #PHP
Laravel中日期时间处理包Carbon的简单使用
Sep 21 #PHP
简单实现php上传文件功能
Sep 21 #PHP
Laravel中七个非常有用但很少人知道的Carbon方法
Sep 21 #PHP
如何通过View::first使用Laravel Blade的动态模板详解
Sep 21 #PHP
基于Laravel实现的用户动态模块开发
Sep 21 #PHP
You might like
php5.2.0内存管理改进
2007/01/22 PHP
input file获得文件根目录简单实现
2013/04/26 PHP
php图片的二进制转换实现方法
2014/12/15 PHP
开发跨浏览器javascript常见注意事项
2009/01/01 Javascript
用Javascript 获取页面元素的位置的代码
2009/09/25 Javascript
写了10年的Javascript也未必全了解的连续赋值运算
2011/03/25 Javascript
用Javascript评估用户输入密码的强度实现代码
2011/11/30 Javascript
Jquery加载时从后台读取数据绑定到dropdownList实例
2013/06/09 Javascript
EasyUI中datagrid在ie下reload失败解决方案
2015/03/09 Javascript
Jquery效果大全之制作电脑健康体检得分特效附源码下载
2015/11/02 Javascript
基于JavaScript实现全屏透明遮罩div层锁屏效果
2016/01/26 Javascript
onclick和onblur冲突问题的快速解决方法
2016/04/28 Javascript
使用jquery获取url及url参数的简单实例
2016/06/14 Javascript
深入理解jquery的$.extend()、$.fn和$.fn.extend()
2017/07/08 jQuery
详解使用webpack构建多页面应用
2017/12/21 Javascript
解决jquery的ajax调取后端数据成功却渲染失败的问题
2018/08/08 jQuery
详解KOA2如何手写中间件(装饰器模式)
2018/10/11 Javascript
在vue中使用setInterval的方法示例
2019/04/16 Javascript
原生js+css调节音量滑块
2020/01/15 Javascript
Vue自定义render统一项目组弹框功能
2020/06/07 Javascript
vue:el-input输入时限制输入的类型操作
2020/08/05 Javascript
[06:11]2014DOTA2国际邀请赛 专访团结一心的VG战队
2014/07/21 DOTA
Python常用的爬虫技巧总结
2016/03/28 Python
Django如何实现内容缓存示例详解
2017/09/24 Python
Python判断两个对象相等的原理
2017/12/12 Python
python使用Matplotlib画饼图
2018/09/25 Python
python时间序列按频率生成日期的方法
2019/05/14 Python
python3中类的继承以及self和super的区别详解
2019/06/26 Python
一张图片能隐含千言万语之隐藏你的程序代码
2012/12/13 HTML / CSS
华美博弈C/VC工程师笔试试题
2012/07/16 面试题
高三英语教学反思
2014/01/13 职场文书
领导干部对照检查材料
2014/08/24 职场文书
王亚平太空授课观后感
2015/06/12 职场文书
Python 解决空列表.append() 输出为None的问题
2021/05/23 Python
nginx常用配置conf的示例代码详解
2022/03/21 Servers
Python 绘制多因子柱状图
2022/05/11 Python