门面

门面(Facade)为容器中的服务提供静态代理接口:以 Config::get() 这样的静态语法,实际调用容器实例上的同名方法。它让框架服务在任意位置(路由闭包、工具函数、无注入环境的类)都能便捷访问,同时保留容器按请求隔离的单例语义。

本文依据框架源码 src/Core/Facade.phpsrc/Core/Facade/* 及各模块 Facade 类编写。

工作原理

Viswoole\Core\Facade 是抽象基类,核心只有三个成员:

  1. __callStatic():拦截所有未定义的静态调用;
  2. createFacade():从容器解析门面映射的服务实例;
  3. getMappingClass():抽象方法,由子类声明映射到容器中的哪个类。
php
// 简化后的转发链路
Config::get('app.debug');
// ↓ __callStatic('get', ['app.debug'])
// ↓ createFacade() → App::factory()->make(\Viswoole\Core\Config::class)
// ↓ 调用实例方法 $config->get('app.debug')

由于解析始终走 App::factory()->make(),门面天然继承容器的请求级协程隔离Request::get() 在每个 HTTP 请求中代理的是该请求自己的 Request 实例。

基类还有一个受保护属性 $alwaysNewInstance(默认 false)。子类覆盖为 true 时,每次静态调用都通过 invokeClass() 创建新实例而不复用单例——适用于门面背后是无状态工具类的场景。

全部门面清单

框架共提供 13 个门面,分布在各功能模块中:

门面类完整命名空间映射的类说明
AppViswoole\Core\Facade\AppViswoole\Core\App应用容器:路径解析、版本号、运行时长等
ConfigViswoole\Core\Facade\ConfigViswoole\Core\Config配置读写
EnvViswoole\Core\Facade\EnvViswoole\Core\Env环境变量读取
EventViswoole\Core\Facade\EventViswoole\Core\Event事件注册与触发
MiddlewareViswoole\Core\Facade\MiddlewareViswoole\Core\Middleware中间件注册与管理
ServerViswoole\Core\Facade\ServerViswoole\Core\Server服务管理器
TaskViswoole\Core\Facade\TaskViswoole\Core\Server\TaskManager异步任务投递
CacheViswoole\Cache\Facade\CacheViswoole\Cache\CacheManager缓存读写、标签、锁
DbViswoole\Database\Facade\DbViswoole\Database\DbManager数据库查询与事务
RequestViswoole\HttpServer\Facade\RequestViswoole\HttpServer\Request当前 HTTP 请求(请求级单例)
ResponseViswoole\HttpServer\Facade\ResponseViswoole\HttpServer\Response当前 HTTP 响应(请求级单例)
LogViswoole\Log\Facade\LogViswoole\Log\LogManager日志记录
RouterViswoole\Router\Facade\RouterViswoole\Router\Router编程式路由注册

使用示例

php
use Viswoole\Cache\Facade\Cache;
use Viswoole\Core\Facade\{Config, Env, Event};
use Viswoole\Database\Facade\Db;
use Viswoole\HttpServer\Facade\Request;
use Viswoole\Log\Facade\Log;
use Viswoole\Router\Facade\Router;

$debug   = Config::get('app.debug', false);   // 读配置
$dbHost  = Env::get('DB_HOST', 'localhost');  // 读环境变量
$page    = Request::param('page', 1);          // 当前请求参数

Db::table('user')->where('id', 1)->get();     // 数据库查询
Cache::set('key', $value, 3600);               // 写缓存
Log::info('用户登录', ['uid' => 1]);            // 写日志
Event::emit('user.login', [['id' => 1]]);      // 触发事件
Router::get('/user/{id}', [UserController::class, 'show']); // 注册路由

门面没有的方法

门面通过 __callStatic 转发,只能代理映射类的实例方法。IDE 默认无法识别这些动态调用,因此框架提供了 optimize:facade 命令为门面补全 IDE 注释,见下文。

IDE 提示优化:optimize:facade

门面是动态代理,编辑器无法自动补全。框架内置 Symfony Console 命令 optimize:facade(源码位于 src/Core/Console/Commands/Optimize/Facade.php),会把映射类的全部公共方法生成 @method static 注释并写入门面类文件:

bash
php viswoole optimize:facade "Viswoole\Core\Facade\Config"
参数类型默认值说明
namespacestring必填需要优化的门面类完整限定名

命令执行流程:

  1. 反射读取门面类的 getMappingClass(),得到映射类;
  2. 遍历映射类的全部公共方法(跳过 __ 开头的魔术方法),解析参数类型、默认值、返回类型与方法注释;
  3. 在门面类文件起始行前插入 @method static ... 注释块,并回写文件。

执行后门面类头部会生成如下注释,IDE 即可提供补全与类型提示:

php
/**
 * 配置管理
 *
 * @method static mixed get(string|null $name = null, mixed $default = null) 获取配置项
 * @method static void set(string|array $key, mixed $value = null) 设置配置项
 * ...
 * 优化命令:php viswoole optimize:facade Viswoole\Core\Facade\Config
 */

命令会直接修改类文件

optimize:facade 直接改写门面源码文件,执行后请检查生成的注释语法是否正确(命令成功输出中也会提示)。通常只对框架自带或你编写的自定义门面执行一次即可。

每次新建实例:$alwaysNewInstance

覆盖 $alwaysNewInstance 后,每次静态调用都通过 App::factory()->invokeClass() 新建实例:

php
namespace App\Facade;

use App\Service\NonceGenerator;
use Viswoole\Core\Facade;

class Nonce extends Facade
{
    // 每次调用都创建新实例,不复用容器单例
    protected static bool $alwaysNewInstance = true;

    protected static function getMappingClass(): string
    {
        return NonceGenerator::class;
    }
}

Nonce::generate(); // 每次返回新的 NonceGenerator 实例

自定义门面

为自有服务创建门面只需继承基类并实现 getMappingClass()

php
namespace App\Facade;

use App\Service\PaymentService;
use Viswoole\Core\Facade;

class Payment extends Facade
{
    protected static function getMappingClass(): string
    {
        return PaymentService::class;
    }
}

// 使用:静态调用转发到容器中的 PaymentService 实例
Payment::pay(['order_no' => 'A001', 'amount' => 99.00]);

前提是 PaymentService 已可被容器解析(直接解析类名,或通过服务提供者 bind() 绑定)。

门面 vs 依赖注入

场景建议
控制器方法、路由闭包、快速原型门面/助手函数,代码更简洁
服务层、领域逻辑构造函数注入,依赖显式、便于 mock 测试
被大量复用的工具类依赖注入,避免隐藏的全局耦合

门面的调用链经由容器,测试中可以通过容器绑定替身(bind() 绑定 mock 实例)来影响门面的行为;但门面仍是隐式依赖,核心业务逻辑建议显式注入。

下一步