门面
门面(Facade)为容器中的服务提供静态代理接口:以 Config::get() 这样的静态语法,实际调用容器实例上的同名方法。它让框架服务在任意位置(路由闭包、工具函数、无注入环境的类)都能便捷访问,同时保留容器按请求隔离的单例语义。
本文依据框架源码
src/Core/Facade.php、src/Core/Facade/*及各模块 Facade 类编写。
工作原理
Viswoole\Core\Facade 是抽象基类,核心只有三个成员:
__callStatic():拦截所有未定义的静态调用;createFacade():从容器解析门面映射的服务实例;getMappingClass():抽象方法,由子类声明映射到容器中的哪个类。
// 简化后的转发链路
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 个门面,分布在各功能模块中:
| 门面类 | 完整命名空间 | 映射的类 | 说明 |
|---|---|---|---|
App | Viswoole\Core\Facade\App | Viswoole\Core\App | 应用容器:路径解析、版本号、运行时长等 |
Config | Viswoole\Core\Facade\Config | Viswoole\Core\Config | 配置读写 |
Env | Viswoole\Core\Facade\Env | Viswoole\Core\Env | 环境变量读取 |
Event | Viswoole\Core\Facade\Event | Viswoole\Core\Event | 事件注册与触发 |
Middleware | Viswoole\Core\Facade\Middleware | Viswoole\Core\Middleware | 中间件注册与管理 |
Server | Viswoole\Core\Facade\Server | Viswoole\Core\Server | 服务管理器 |
Task | Viswoole\Core\Facade\Task | Viswoole\Core\Server\TaskManager | 异步任务投递 |
Cache | Viswoole\Cache\Facade\Cache | Viswoole\Cache\CacheManager | 缓存读写、标签、锁 |
Db | Viswoole\Database\Facade\Db | Viswoole\Database\DbManager | 数据库查询与事务 |
Request | Viswoole\HttpServer\Facade\Request | Viswoole\HttpServer\Request | 当前 HTTP 请求(请求级单例) |
Response | Viswoole\HttpServer\Facade\Response | Viswoole\HttpServer\Response | 当前 HTTP 响应(请求级单例) |
Log | Viswoole\Log\Facade\Log | Viswoole\Log\LogManager | 日志记录 |
Router | Viswoole\Router\Facade\Router | Viswoole\Router\Router | 编程式路由注册 |
使用示例
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 注释并写入门面类文件:
php viswoole optimize:facade "Viswoole\Core\Facade\Config"| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
namespace | string | 必填 | 需要优化的门面类完整限定名 |
命令执行流程:
- 反射读取门面类的
getMappingClass(),得到映射类; - 遍历映射类的全部公共方法(跳过
__开头的魔术方法),解析参数类型、默认值、返回类型与方法注释; - 在门面类文件起始行前插入
@method static ...注释块,并回写文件。
执行后门面类头部会生成如下注释,IDE 即可提供补全与类型提示:
/**
* 配置管理
*
* @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() 新建实例:
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():
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 实例)来影响门面的行为;但门面仍是隐式依赖,核心业务逻辑建议显式注入。
