门面模式

概念说明

门面(Facade)为框架的核心服务提供了 静态代理接口,允许以静态方法的方式调用容器中服务的实例方法。它是连接开发者与框架内部实现的桥梁,兼顾了易用性和灵活性。

Viswoole 的 Facade 通过 PHP 的 __callStatic 魔术方法实现:当调用 Facade 上的静态方法时,它会从容器中获取对应的真实服务实例,然后将调用转发至该实例的同名方法。

工作原理

调用转发机制

text
Facade::methodName($args)


 __callStatic('methodName', $args)


 getMappingClass()  →  获取容器中的真实服务名称


 app()->make('服务名')  →  从容器解析实例


 $instance->methodName($args)  →  调用真实方法

内置门面列表

门面类对应服务说明
Facade\AppApp应用实例,提供工厂方法、路径解析等
Facade\ConfigConfig配置读取与管理
Facade\EnvEnv环境变量读取
Facade\EventEvent事件系统的注册与触发
Facade\MiddlewareMiddleware中间件管理
Facade\ServerServerHTTP Server 管理
Facade\TaskTask异步任务投递

全局辅助函数

除了 Facade 类,框架还提供了全局辅助函数(定义在 helper.php),它们本质上是 Facade 的快捷包装:

函数等价调用说明
app()App::factory()获取应用/容器实例
config($key)Config::get($key)获取配置值
env($key, $default)Env::get($key, $default)获取环境变量
cache(...)CacheManager 实例或 get获取缓存值或缓存管理器实例
invoke(...)Container::invoke()调用方法并注入参数
bind(...)Container::bind()绑定服务
make(...)Container::make()解析实例
getVersion()获取框架版本号-

代码示例

使用内置门面

php
use Viswoole\Core\Facade\Config;
use Viswoole\Core\Facade\Env;
use Viswoole\Core\Facade\Event;
use Viswoole\Core\Facade\App;

// 读取配置
$timeZone = Config::get('app.default_timezone', 'UTC');
$isDebug = Config::get('app.debug', false);

// 读取环境变量
$dbHost = Env::get('DB_HOST', 'localhost');
$appKey = Env::get('APP_KEY');

// 事件操作
Event::on('user.login', function ($userId) { /* ... */ });
Event::emit('user.login', [1]);

// 应用信息
$rootPath = App::getRootPath();
$version = App::getVersion();

使用全局辅助函数

php
// 辅助函数无需 use 导入,在任何地方可直接使用

// 获取容器实例
$app = app();

// 配置与环境
$timezone = config('app.default_timezone');
$debug = env('APP_DEBUG', false);

// 容器操作
$logger = make(Logger::class);
bind(CacheInterface::class, RedisCache::class);

// 带参数注入的方法调用
$result = invoke([new UserService(), 'find'], ['id' => 1]);

自定义门面

为自有服务创建 Facade,只需继承基类并指定服务名称:

php
use Viswoole\Core\Facade;

class Payment extends Facade
{
    /**
     * 获取门面对应的容器服务标识
     */
    protected static function getMappingClass(): string
    {
        return PaymentService::class;
    }
}

// 使用自定义门面
Payment::pay(['amount' => 99.00, 'order_no' => 'ORD001']);
Payment::refund('ORD001');

门面 vs 直接注入

php
// 方式一:使用门面(适合控制器、路由闭包等场景)
class OrderController
{
    public function create()
    {
        // 直接通过门面调用,简洁明了
        $result = Payment::process($this->request->all());
        return json($result);
    }
}

// 方式二:构造函数注入(适合 Service 层等需要测试的场景)
class OrderService
{
    public function __construct(
        private PaymentServiceInterface $payment,
    ) {}

    public function createOrder(array $data): array
    {
        // 通过注入的依赖调用,便于 mock 测试
        return $this->payment->process($data);
    }
}

最佳实践

1. 控制器与路由中使用门面

在控制器和路由闭包中,门面是最便捷的选择:

php
// ✅ 推荐:在控制器中使用门面
class UserController extends BaseController
{
    public function show(int $id): ResponseInterface
    {
        $user = Db::table('users')->find($id);
        Cache::put("user:{$id}", $user, 3600);
        return json($user);
    }
}

// ✅ 推荐:路由闭包中使用辅助函数
Route::get('/api/info', function () {
    return [
        'version' => getVersion(),
        'debug' => config('app.debug'),
    ];
});

2. 服务层使用依赖注入

在 Service 等核心业务层,优先使用构造函数注入以保证可测试性:

php
// ✅ 推荐:Service 层通过构造函数声明依赖
class OrderService
{
    public function __construct(
        private PaymentServiceInterface $payment,
        private InventoryServiceInterface $inventory,
    ) {}

    public function placeOrder(array $data): Order
    {
        $this->inventory->reserve($data['items']);
        return $this->payment->charge($data);
    }
}

// 便于编写单元测试时 mock 依赖
$service = new OrderService(
    payment: new MockPaymentService(),
    inventory: new MockInventoryService(),
);

3. 不要滥用门面

门面虽然方便,但会增加隐式依赖,降低代码的可追踪性:

php
// ❌ 不推荐:在一个方法中混用大量门面,隐式依赖过多
class ReportService
{
    public function generate()
    {
        $users = Db::table('users')->get();      // 隐式依赖 DB
        $orders = Db::table('orders')->get();     // 隐式依赖 DB
        Cache::put('report', $data, 3600);        // 隐式依赖 Cache
        Event::emit('report.generated', [$data]); // 隐式依赖 Event
        Log::info('报告生成完成');                 // 隐式依赖 Log
    }
}

// ✅ 推荐:显式声明核心依赖,辅助性操作可用门面
class ReportService
{
    public function __construct(
        private DatabaseManager $db,
    ) {}

    public function generate()
    {
        $users = $this->db->table('users')->get();
        $orders = $this->db->table('orders')->get();
        Cache::put('report', $data, 3600);       // 辅助操作,可用门面
        Event::emit('report.generated', [$data]); // 辅助操作,可用门面
    }
}

4. 自定义门面的命名规范

自定义门面类命名应与其代理的服务对应,放在 App\Facade 命名空间下:

php
namespace App\Facade;

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

/**
 * 通知服务门面
 * 提供 NotificationService 的静态代理访问
 */
class Notification extends Facade
{
    protected static function getMappingClass(): string
    {
        return NotificationService::class;
    }
}