星澜

天接云涛连晓雾,星河欲转千帆舞

Laravel 契约和门面简单解读


前言

最近在深入理解 Laravel 的服务容器服务提供者门面契约之间的关系。本文将以 Laravel 自带的 Cache 模块为例,详细讲解这些概念是如何协作的。

一、服务提供者注册

首先打开 config/app.php,在 providers 数组中可以找到 Cache 的服务提供者:

'providers' => [
    // ...
    Illuminate\Cache\CacheServiceProvider::class,
    // ...
],

CacheServiceProvider 的实现

查看 CacheServiceProviderregister 方法:

public function register()
{
    // 注册 cache 单例,返回 CacheManager 实例
    $this->app->singleton('cache', function ($app) {
        return new CacheManager($app);
    });

    // 注册 cache.store 单例
    $this->app->singleton('cache.store', function ($app) {
        return $app['cache']->driver();
    });

    // 注册 PSR-6 缓存适配器
    $this->app->singleton('cache.psr6', function ($app) {
        return new Psr16Adapter($app['cache.store']);
    });

    // 注册 Memcached 连接器
    $this->app->singleton('memcached.connector', function () {
        return new MemcachedConnector;
    });
}

关键点cache 这个服务名称绑定到了 CacheManager 类,这是整个 Cache 门面的核心。

二、CacheManager 源码分析

CacheManager 实现了 FactoryContract 契约(接口),负责管理不同的缓存驱动。

核心结构

<?php

namespace Illuminate\Cache;

use Illuminate\Contracts\Cache\Factory as FactoryContract;
use Illuminate\Contracts\Cache\Store;

/**
 * @mixin \Illuminate\Contracts\Cache\Repository
 */
class CacheManager implements FactoryContract
{
    protected $app;              // 应用实例
    protected $stores = [];      // 已解析的缓存驱动实例
    protected $customCreators = []; // 自定义驱动创建器

    public function __construct($app)
    {
        $this->app = $app;
    }

    // 获取指定的缓存驱动(默认使用配置中的默认驱动)
    public function store($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();
        return $this->stores[$name] = $this->get($name);
    }

    // driver 方法是 store 的别名
    public function driver($driver = null)
    {
        return $this->store($driver);
    }

    // 从本地缓存或动态解析获取驱动
    protected function get($name)
    {
        return $this->stores[$name] ?? $this->resolve($name);
    }

    // 解析具体的驱动实现
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Cache store [{$name}] is not defined.");
        }

        // 动态调用 createXxxDriver 方法(如 createFileDriver)
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($config);
        }

        throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
    }

    // 创建 File 驱动实例
    protected function createFileDriver(array $config)
    {
        return $this->repository(new FileStore($this->app['files'], $config['path']));
    }

    // 将 Store 包装到 Repository 中(依赖注入)
    public function repository(Store $store)
    {
        return tap(new Repository($store), function ($repository) {
            $this->setEventDispatcher($repository);
        });
    }

    // 读取配置
    protected function getConfig($name)
    {
        return $this->app['config']["cache.stores.{$name}"];
    }

    // 魔术方法:将方法调用代理到默认驱动
    public function __call($method, $parameters)
    {
        return $this->store()->$method(...$parameters);
    }
}

关键设计

  1. 契约实现CacheManager implements FactoryContract
  2. 驱动管理:通过 $stores 数组缓存已创建的驱动实例
  3. 动态解析:通过反射调用 createXxxDriver 方法
  4. 依赖注入Repository 构造函数注入 Store 契约实现
  5. 代理模式__call 方法将调用转发到实际的驱动实例

三、完整调用流程

以 Laravel 默认的 File 存储为例,当我们在控制器中调用 Cache::get('key') 时:

第 1 步:门面层(Facade)

// 控制器代码
Cache::get('key');

// Cache 门面定义
namespace Illuminate\Support\Facades;

class Cache extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'cache'; // 返回服务容器中的绑定名称
    }
}

第 2 步:容器解析

// Facade 基类的 __callStatic 方法(简化版)
public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot(); // 从容器解析 'cache' 绑定
    return $instance->$method(...$args); // 调用实例方法
}

第 3 步:CacheManager 代理

// 调用 CacheManager->get('key')
// CacheManager 没有 get() 方法,触发 __call
public function __call($method, $parameters)
{
    return $this->store()->$method(...$parameters);
}

第 4 步:驱动创建

public function store($name = null)
{
    $name = $name ?: $this->getDefaultDriver(); // 获取默认驱动名(如 'file')
    return $this->stores[$name] = $this->get($name);
}

protected function get($name)
{
    // 如果已存在则直接返回,否则调用 resolve
    return $this->stores[$name] ?? $this->resolve($name);
}

protected function resolve($name)
{
    $config = $this->getConfig($name); // 读取 config/cache.php 配置
    $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; // createFileDriver
    return $this->{$driverMethod}($config); // 动态调用
}

protected function createFileDriver(array $config)
{
    // 创建 FileStore 实例并包装到 Repository 中
    return $this->repository(new FileStore($this->app['files'], $config['path']));
}

流程说明:

第 5 步:依赖注入

public function repository(Store $store)
{
    return tap(new Repository($store), function ($repository) {
        $this->setEventDispatcher($repository);
    });
}

// Repository 构造函数
class Repository implements CacheContract
{
    protected $store;

    public function __construct(Store $store)
    {
        $this->store = $store; // 注入 Store 契约实现
    }

    public function get($key, $default = null)
    {
        return $this->store->get($key); // 调用具体驱动的方法
    }
}

第 6 步:实际执行

// __call 返回 Repository 实例后,调用其 get 方法
$repository->get('key');

// Repository 将调用委托给 FileStore
$this->store->get('key'); // FileStore->get('key')

四、架构图示

控制器
  ↓ Cache::get('key')
门面层 (Facade)
  ↓ __callStatic
服务容器
  ↓ 解析 'cache' 绑定
CacheManager(实现 FactoryContract)
  ↓ __call → store()
驱动解析
  ↓ resolve() → createFileDriver()
Repository(实现 CacheContract)
  ↓ 构造函数注入 Store
FileStore(实现 Store 契约)
  ↓ get() 方法
文件系统

五、设计模式总结

1. 门面模式(Facade)

2. 契约模式(Contract / Interface)

3. 工厂模式(Factory)

4. 依赖注入(Dependency Injection)

5. 代理模式(Proxy)

六、核心优势

1. 上层无感知切换

控制器不需要关心使用的是 File、Redis 还是 Memcached:

// 修改 config/cache.php 的 default 配置即可切换
'default' => env('CACHE_DRIVER', 'redis'), // 从 file 切换到 redis

2. 易于扩展

添加新的缓存驱动只需:

3. 测试友好

可以轻松 Mock Store 契约进行单元测试:

$mockStore = Mockery::mock(Store::class);
$repository = new Repository($mockStore);

4. 符合 SOLID 原则

七、总结

Laravel 的 Cache 模块完美展示了如何通过门面契约依赖注入等设计模式,构建一个灵活、可扩展的架构:

  1. 服务提供者注册服务到容器
  2. 门面提供简洁的静态调用接口
  3. CacheManager 作为工厂管理不同驱动
  4. **契约(接口)**规范各层的行为
  5. 依赖注入实现上层对底层的解耦

这种设计使得: