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 读取的,PackageManifestlaravel 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 注册进自动加载。其核心就是 phpspl_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)。

AliasLoaderload 方法中,主要是用了 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 方法。该方法可能的返回值有:

  1. String 类型的字符串(如 config, db
  2. String 类型的类字符串 (如 App\Service\SomeService
  3. Object 具体的实列化对象
  4. Closure 闭包

Config FacadegetFacadeAccessor 方法如下:

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') 方法。

本作品采用《CC 协议》,转载必须注明作者和本文链接
二愣的闲谈杂鱼
本帖由系统于 4年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

var_dump($a instanceof $b); //false 这里是true

5年前 评论
未进化的类人猿

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

此话和解?

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!