Laravel Facade加载以及原理

Hello,我是Rocket

这是我参与更文挑战的第4天,活动详情查看:更文挑战

引言

  • 知识是自己的,不要畏惧去看源码,因为它比你想象的更简单
  • 今天带着大家来揭开Laravel Facade神秘的面纱,从他启动注册,到解析加载一点点
  • 学完估计你会想我曹原来这么简单,其实是你想复杂了
  • 动态facede感觉有点多余,有那功夫写个命令生成Facade

1、Facade 是什么

LaravelFacade 是典型的静态代理模式,他可以让你以静态的方式来调用存放在容器中任何对象的任何方法

2、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 包提供的别名。

public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }
    
protected function prependToLoaderStack()
    {
        spl_autoload_register([$this, 'load'], true, true);
    }
复制代码

通过Illuminate\Foundation\AliasLoader把所有的facade注册进自动加载,基于php的spl_autoload_register
注册完成后,后续所有 use 的类都将通过 load 函数来完成类的自动加载。并且优先走load方法

3、如何定义Facade

需要继承Illuminate\Support\Facades\Facade类,并实现getFacadeAccessor静态方法

use Illuminate\Support\Facades\Facade;

class Cache extends Facade
{
    /**
     * 获取组件注册名称
     *
     * @return string
     */
    protected static function getFacadeAccessor() { 
        return 'cache'; 
    }
}
复制代码

4、Facade 是如何实现的

我们平时调用facade总会发现这个类其实是没有对应的方法的,那么facade是怎么找到具体的实现类

<?php

use Illuminate\Support\Facades\Config;

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


复制代码

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

abstract class Facade
{
    public static function getFacadeRoot()
    {
        
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }
    
    //static::getFacadeAccessor() 被子类重写
    protected static function getFacadeAccessor()
    {
        throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
    }

    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

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

        return static::$resolvedInstance[$name] = static::$app[$name];
    }
    //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);
    }
}
复制代码

static::$app[$name] 大家可能好奇怎么能把对象当数组取值呢?实际上app继承了Illuminate\Container\ContainerIOC容器,同时容器还实现了PHP的ArrayAccessstatic::$app[$name]这种方式等同于app->make($name)

5、动态Facade实现的原理

public function load($alias)
    {
        //检测到命名空间包含 Facades\\
        if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
            //这块是实现动态Facade的关键
            $this->loadFacade($alias);

            return true;
        }

        if (isset($this->aliases[$alias])) {
            //注册类别名
            return class_alias($this->aliases[$alias], $alias);
        }
    }

protected function loadFacade($alias)
    {
        require $this->ensureFacadeExists($alias);
    }
    
protected function ensureFacadeExists($alias)
    {
        if (file_exists($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) {
            return $path;
        }

        //实际上的处理会写一个缓存文件
        file_put_contents($path, $this->formatFacadeStub(
            $alias, file_get_contents(__DIR__.'/stubs/facade.stub')
        ));

        return $path;
    }
复制代码

6、结尾

与诸君共勉之,希望对您有所帮助
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享