惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
O
OpenAI News
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
云风的 BLOG
云风的 BLOG
罗磊的独立博客
S
SegmentFault 最新的问题
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
雷峰网
雷峰网
V
Visual Studio Blog
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Y
Y Combinator Blog
博客园 - 【当耐特】
G
Google Developers Blog
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
Martin Fowler
Martin Fowler
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
AI
AI
Google Online Security Blog
Google Online Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - Franky
Blog — PlanetScale
Blog — PlanetScale
Webroot Blog
Webroot Blog
PCI Perspectives
PCI Perspectives
爱范儿
爱范儿
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 Hugo PaperMod 主题 去长鹿旅游休博园 1Password 自动化登录堡垒机 深入 Hyperf:Inject 注解是如何工作的? 译|理解容器镜像层 使用 Rust 实现高性能预写日志(Write Ahead Log) 译|使用 Linux 命名空间隔离系统 深入 Hyperf:HTTP 服务是如何处理请求的? Linux 文件权限学习笔记 译|了解 Bitcask:基于日志结构的 KV 存储引擎 还原 SM2 压缩公钥的几种方法 深入 Hyperf:HTTP 服务启动时发生了什么? 一次 Hyperf 注解失效问题分析 将博客迁移到又拍云 译|PHP 7 新的 Hashtable 实现 排查 ES 查询问题:深入了解 json_encode() 函数 浅析 Redlock 分布式锁实现原理 Yar 源码阅读笔记:RPC 服务端 Yar 源码阅读笔记:客户端的并行调用 Yar 源码阅读笔记:客户端的同步调用 Yar 源码阅读笔记:数据传输模块 Yar 源码阅读笔记:消息编码模块 Yar 源码阅读笔记:RPC 通信协议 Yar 源码阅读笔记:开篇 PHP 8 新特性介绍 基于 OpenSSL 实现国密 SM4 加解密 PHP 源码阅读笔记:编译与调试 PHP 什么是惊群问题 大端模式和小端模式 基于 GitHub Actions 定时推送网址到百度站长平台 聊聊五种 I/O 模型 Workerman 源码分析:文件上传 浅入浅出 HTTP 协议 PHP 网络编程:构建 MySQL 蜜罐获取攻击者微信 ID 使用 Workerman 接入 Bilibili 直播弹幕协议 使用 GitHub Actions 自动部署 Hexo 什么是二进制安全 【转载】PHP 程序员进阶之路 优化 Workerman 检查主进程是否存活的逻辑 基于 Redis 实现分布式锁 《PHP 实现 Base64 编码/解码》笔记 PHP 多进程下载必应壁纸 基于 Redis 实现延迟队列 【转载】PHP 实现 Base64 编码/解码 使用 MySQL FIELD() 函数自定义排序规则 C语言单链表实现约瑟夫环 数据结构学习笔记:顺序栈和链栈(C语言) 使用 QueryList + Redis 下载壁纸 在 Laravel 登录/注册中使用 mews/captcha 扩展包 使用 mews/captcha 扩展包为 Laravel 应用添加图片验证码 给 YOURLS 短网址系统编写插件《批量生成短网址》 给 YOURLS 短网址系统编写插件《Hello World!》 PHP 7.1 使用 json_encode 函数造成浮点类型数据出现精度问题 Laravel 授权策略(Policy)的基本使用 Ubuntu 系统下 WiFi 频繁掉线解决方法 Laravel 应用部署到 Nginx 服务器上的第一个坑 Ubuntu 16.04 LST 安装 Redis 和 Composer PHP 采集逐浪小说章节列表 Nginx 环境 SSL 强制 HTTP 301 跳转到 HTTPS 【工作&生活】第一周 ServiceStack.Redis 与多线程 Count the number of Duplicates C# Base64 加密及解密 ASP.NET MVC 使用动态类型传递数据 Django 使用 order_by() 对数据进行排序操作 ASP.NET + jQuery + AJAX 实现用户登录 ASP.NET 从客户端中检测到有潜在危险的 Request.Form 值 ASP.NET 使用 FormData 实现 AJAX 上传图片 Python 使用 BeautifulSoup 抓取网页 贪心算法之字符串的完美度 一言不和就造轮子之 CookieHelper ASP.NET 上传图片 DBHelper 类 C# 捕获屏幕源码 用 JavaScript 做一些有趣的事情 第一篇文章 好久不见。 如何防止非法调用 PHP 文件 清明小记。 PHP 生成随机字符串 Z-Blog PHP 程序做 301 跳转 PHP 处理数组常用的几个函数 Sublime Text 3 快捷键总结 PHP 使用 file_get_contents() 函数实现采集网页 使用 C# 编写学生信息管理系统 终于到了。 zblog报错: magic_quotes_gpc 和 safe_mode 世事无常。 如何使用 PHP 上传文件 安卓手机编写和运行 PHP 程序的软件 使用 AJAX 刷新、验证 PHP 图片验证码 PHP 生成图片验证码 PHP 之 session 的使用方法 PHP 之 cookie 的使用方法 PHP 实现分页原理详解 Thinkphp 框架中系统常量在 Js 文件中不解析的解决方法
Laravel 的 Facades 实现原理
她和她的猫 · 2019-12-29 · via 文章列表 - 她和她的猫

前言

在使用 Laravel 框架进行开发项目的时候,Facades 是一个经常能用到的模块,比如在使用缓存(Cache)、日志(Log) 等组件的地方。

use Illuminate\Support\Facades\Cache;

$name = Cache::get('name');

use use Illuminate\Support\Facades\Log;

Log::info('this is log content');

Facades 的主要优点就是不需要记住各个组件所在目录对应的的命名空间,因为 Illuminate\Support\Facades\ 这一段都是固定的,变化的只是后面的组件名称。

早在刚接触 Laravel 的时候,就对 Facades 充满了疑惑,为什么要这样用,直接用组件真正的命名空间不行吗,代码追踪过去又没有组件实现代码,只有一个静态方法 getFacadeAccessor 返回了组件的名称(cache、log),但是在使用的时候又能调用到组件真正的方法。

class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'cache';
    }
}

那么真相只能在父类 Facade 里面了。

但是鉴于平时工作都在搬砖,做一名快乐的 CURD Boy,秉承着实用主义又不是不能用的理念,所以当时并没有去深究代码(实际上是因为太菜了看不懂)。

那为什么现在又开始研究怎么它实现的呢?是我变强了吗?不!是因为需要用上它了…(实用主义万岁)

当时在写一个基于 overtrue/socialite 实现的 her-cat/colourlife-oauth2 扩展包,为了兼容 Laravel 的 Facades 用法,不得不了解一下 Facades 的实现原理(其实仔细看两遍文档就能知道个大概了)。

实现原理

言归正传,就拿 Cache 来举例子,Facades\Cache 的文件内容就在上面,Cache 继承了抽象类 Facade 并实现了 getFacadeAccessor 静态方法,该方法返回了 Cache 组件(就是真正的缓存类)在 Laravel 容器中注册的名称,也就是 cache,这样我们就能从容器中取出 Cache 组件 的实例对象了,整个 Facades 实现也就是从容器中取出实例对象,让实例对象执行被调用的方法。

vendor/laravel/framework/src/Illuminate/Foundation/Application.php:

    /**
     * Register the core class aliases in the container.
     *
     * @return void
     */
    public function registerCoreContainerAliases()
    {
        foreach ([
            // ...
            'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
            // ...
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {
                $this->alias($key, $alias);
            }
        }
    }

当我们运行 Cache::get('name') 的时候,会先触发父类 Facade 的魔术方法 __callStatic,因为 Facade 并没有 get 方法。

    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     *
     * @throws \RuntimeException
     */
    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);
    }

$method 是被调用的方法的名称(这个时候的值是 get),$args 就是参数数组(array(1) { [0]=> string(4) "name" }),通过 getFacadeRoot 方法获取到 Cache 组件 的实例对象,然后以 $args 作为参数,调用实例的 get 方法,最后返回方法执行结果。

以上就是方法就是 Facade 的实现原理了,接下来再看看 getFacadeRoot 方法。

    /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

这个方法本身没有什么代码,就是调用了 resolveFacadeInstance 方法,通过组件名称从 Laravel 容器中取出对应的实例对象,这个组件名称就是通过 Facades\Cache 类的 getFacadeAccessor 方法获取的,这里的返回值就是 cache

接下来就到了最后一个方法:resolveFacadeInstance

    /**
     * Resolve the facade root instance from the container.
     *
     * @param  object|string  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

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

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

此时 $name 的值是 cache。如果 $name 是一个对象的话,就直接返回 $name$resolvedInstance 是一个用来保存已解析过的实例对象的数组,判断 $name 是否已经从 Laravel 容器中解析过,如果已经解析过就直接返回。

注意:这里是的判断是在这一次请求的生命周期内是否解析过,下次请求进来的时候还是会从 Laravel 容器中取出来。

如果 Laravel 容器如果不是空的话,就通过 $name 从 Laravel 容器中取出 Cache 组件 的实例对象,将 $name 作为 key 存入解析过的实例对象的数组中,并返回。

精简版实现及测试代码

好了,Facades 的实现代码到这里就完了,最后再附上精简版的 Facades 及测试代码。

Facade 抽象类:

namespace App\Core;

abstract class Facade
{
    /**
     * 用于存放实例对象的数组(实际上在 Laravel 中并不是数组,而是 Application 对象)
     * @var array
     */
    protected static $app = [];

    /**
     * 用于保存解析过的实例对象的数组
     *
     * @var array
     */
    protected static $resolvedInstance = [];

    /**
     * 获取 facade 绑定的实例对象
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

    /**
     * 获取组件在容器中注册的名称
     *
     * @return string
     *
     * @throws \Exception
     */
    protected static function getFacadeAccessor()
    {
        throw new \Exception('Facade does not implement getFacadeAccessor method.');
    }

    /**
     * 从容器中获取组件的实例对象
     *
     * @param  object|string  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

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

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

    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     *
     * @throws \Exception
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new \Exception('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }

    /**
     * 设置组件实例对象(Laravel源码并无该方法,为了演示 Facade 加上的)
     *
     * @param $name
     * @param $obj
     */
    public static function setComponentInstance($name, $obj)
    {
        self::$app[$name] = $obj;
    }
}

继承了 Facade 的类:

namespace App\Facades;

use App\Core\Facade;

/**
 * @method static mixed get($key)
 */
class Cache extends Facade
{
    public static function getFacadeAccessor()
    {
        return 'cache';
    }
}

/**
 * @method static mixed info($key)
 */
class Log extends Facade
{
    public static function getFacadeAccessor()
    {
        // 这里与上面不一样,返回的是组件的实例
        return new \App\Components\Log();
    }
}

组件的实现:

namespace App\Components;

class Cache
{
    public function get($key)
    {
        return "key:{$key}, value: her-cat";
    }
}

class Log
{
    public function info($content)
    {
        return "记录的内容:{$content}";
    }
}

测试代码:

namespace App\Test;

// 将 Cache 组件的实例对象存入 static::$app 中
\App\Core\Facade::setComponentInstance('cache', new \App\Components\Cache());

echo \App\Facades\Cache::get('name').PHP_EOL;

// Log 组件并未注册到 static::$app 中
echo \App\Facades\Log::info('哈哈哈哈').PHP_EOL;