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代码
Mar 03 PHP
PHP之生成GIF动画的实现方法
Jun 07 PHP
深入PHP5中的魔术方法详解
Jun 17 PHP
一个简单的php加密解密函数(动态加密)
Jun 19 PHP
Windows下的PHP安装文件线程安全和非线程安全的区别
Apr 23 PHP
php实现网站顶踩功能的完整前端代码
Jul 19 PHP
PHP基于简单递归函数求一个数阶乘的方法示例
Apr 26 PHP
php实现留言板功能(会话控制)
May 23 PHP
PHPMailer使用QQ邮箱实现邮件发送功能
Aug 18 PHP
PHP实现的数据对象映射模式详解
Mar 20 PHP
PHP cookie与session会话基本用法实例分析
Nov 18 PHP
thinkphp5实现微信扫码支付
Dec 23 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版(4)
2006/10/09 PHP
亲密接触PHP之PHP语法学习笔记1
2006/12/17 PHP
几个php应用技巧
2008/03/27 PHP
非常实用的PHP常用函数汇总
2014/12/17 PHP
php采集自中央气象台范围覆盖全国的天气预报代码实例
2015/01/04 PHP
thinkphp在低版本Nginx 下支持PATHINFO的方法分享
2016/05/27 PHP
php版交通银行网银支付接口开发入门教程
2016/09/26 PHP
PHP实现的登录页面信息提示功能示例
2017/07/24 PHP
win10 apache配置虚拟主机后localhost无法使用的解决方法
2018/01/27 PHP
jQuery对象和DOM对象相互转化
2009/04/24 Javascript
window.js 主要包含了页面的一些操作
2009/12/23 Javascript
javascript实现的在当前窗口中漂浮框的代码
2010/03/15 Javascript
javascript中的prototype属性使用说明(函数功能扩展)
2010/08/16 Javascript
JavaScript高级程序设计 读书笔记之九 本地对象Array
2012/02/27 Javascript
js工具方法弹出蒙版
2013/05/08 Javascript
禁止空格提交表单的js代码
2013/11/17 Javascript
js仿微博实现统计字符和本地存储功能
2015/12/22 Javascript
微信小程序教程系列之视图层的条件渲染(10)
2017/04/19 Javascript
addeventlistener监听scroll跟touch(实例讲解)
2017/08/04 Javascript
[01:43]倾听DOTA2英雄之声 魅惑魔女国服配音鉴赏
2013/06/06 DOTA
Python random模块用法解析及简单示例
2017/12/18 Python
Python原始套接字编程实例解析
2020/01/29 Python
python实现网页录音效果
2020/10/26 Python
用CSS3写的模仿iPhone中的返回按钮
2015/04/04 HTML / CSS
eBay法国购物网站:eBay.fr
2017/10/21 全球购物
高三地理教学反思
2014/01/11 职场文书
我的中国梦演讲稿1000字
2014/08/19 职场文书
物流管理专业推荐信
2014/09/06 职场文书
名人演讲稿范文
2014/09/16 职场文书
基层党员对照检查材料
2014/09/24 职场文书
2015年医院创卫工作总结
2015/04/22 职场文书
建党伟业的观后感
2015/06/01 职场文书
职工食堂管理制度
2015/08/06 职场文书
感恩老师主题班会
2015/08/12 职场文书
Python time库的时间时钟处理
2021/05/02 Python
Redis中有序集合的内部实现方式的详细介绍
2022/03/16 Redis