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 相关文章推荐
一个用php实现的获取URL信息的类
Jan 02 PHP
php 调用远程url的六种方法小结
Nov 02 PHP
php 转换字符串编码 iconv与mb_convert_encoding的区别说明
Nov 10 PHP
浅析php中如何在有限的内存中读取大文件
Jul 02 PHP
destoon安全设置中需要设置可写权限的目录及文件
Jun 21 PHP
PHP中使用GD库创建圆形饼图的例子
Nov 19 PHP
php计算一个文件大小的方法
Mar 30 PHP
基于laravel制作APP接口(API)
Mar 15 PHP
yii2 RBAC使用DbManager实现后台权限判断的方法
Jul 23 PHP
php mysql procedure实现获取多个结果集的方法【基于thinkPHP】
Nov 09 PHP
搭建自己的PHP MVC框架详解
Aug 16 PHP
php基于环形链表解决约瑟夫环问题示例
Nov 07 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
PHP 数据结构 算法 三元组 Triplet
2011/07/02 PHP
如何使用PHP计算上一个月的今天
2013/05/23 PHP
PHP下的浮点运算不准的解决方法
2016/10/27 PHP
Yii框架自定义数据库操作组件示例
2019/11/11 PHP
ThinkPHP5与单元测试PHPUnit使用详解
2020/02/23 PHP
[原创]图片分页查看
2006/08/28 Javascript
JavaScript 组件之旅(三):用 Ant 构建组件
2009/10/28 Javascript
jquery插件之easing使用
2010/08/19 Javascript
js制作的鼠标悬浮时产生的下拉框效果
2012/10/27 Javascript
JS 打印界面的CSS居中代码适用所有浏览器
2014/03/19 Javascript
node.js中的path.join方法使用说明
2014/12/08 Javascript
jquery中cookie用法实例详解(获取,存储,删除等)
2016/01/04 Javascript
基于javascript实现精确到毫秒的倒计时限时抢购
2016/04/17 Javascript
基于Jquery插件实现跨域异步上传文件功能
2016/04/26 Javascript
聊一聊jQuery插件uploadify使用方法
2016/08/24 Javascript
浅谈javascript:两种注释,声明变量,定义函数
2016/09/29 Javascript
Vue通过getAction的finally来最大程度避免影响主数据呈现问题
2020/04/24 Javascript
python 添加用户设置密码并发邮件给root用户
2016/07/25 Python
Python之str操作方法(详解)
2017/06/19 Python
Python语言描述最大连续子序列和
2017/12/05 Python
Python安装模块的常见问题及解决方法
2018/02/05 Python
TensorFlow学习之分布式的TensorFlow运行环境
2020/02/05 Python
python如何提取英语pdf内容并翻译
2020/03/03 Python
用 Python 制作地球仪的方法
2020/04/24 Python
python+playwright微软自动化工具的使用
2021/02/02 Python
美国高级音响品牌:Master&Dynamic
2018/07/05 全球购物
公司端午节活动方案
2014/02/04 职场文书
给校长的建议书600字
2014/05/15 职场文书
施工安全汇报材料
2014/08/17 职场文书
与美同行演讲稿
2014/09/13 职场文书
人事代理委托书
2014/09/27 职场文书
教师党的群众路线教育实践活动学习心得体会
2014/10/30 职场文书
新闻稿怎么写
2015/07/18 职场文书
2016五一手机促销广告语
2016/01/28 职场文书
Redis高级数据类型Hyperloglog、Bitmap的使用
2021/05/24 Redis
PostgreSQL常用字符串分割函数整理汇总
2022/07/07 PostgreSQL