Laravel 契约和门面简单解读
mingzaily / 2020-03-30
前言
最近在深入理解 Laravel 的服务容器、服务提供者、门面、契约之间的关系。本文将以 Laravel 自带的 Cache 模块为例,详细讲解这些概念是如何协作的。
一、服务提供者注册
首先打开 config/app.php,在 providers 数组中可以找到 Cache 的服务提供者:
'providers' => [
// ...
Illuminate\Cache\CacheServiceProvider::class,
// ...
],
CacheServiceProvider 的实现
查看 CacheServiceProvider 的 register 方法:
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);
}
}
关键设计
- 契约实现:
CacheManager implements FactoryContract - 驱动管理:通过
$stores数组缓存已创建的驱动实例 - 动态解析:通过反射调用
createXxxDriver方法 - 依赖注入:
Repository构造函数注入Store契约实现 - 代理模式:
__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'; // 返回服务容器中的绑定名称
}
}
- Laravel 找到
Illuminate\Support\Facades\Cache门面 - 调用
getFacadeAccessor()返回'cache' - 触发 Facade 基类的
__callStatic()魔术方法
第 2 步:容器解析
// Facade 基类的 __callStatic 方法(简化版)
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot(); // 从容器解析 'cache' 绑定
return $instance->$method(...$args); // 调用实例方法
}
- 从服务容器解析
'cache'绑定 - 返回
CacheManager实例(通过CacheServiceProvider注册的单例)
第 3 步:CacheManager 代理
// 调用 CacheManager->get('key')
// CacheManager 没有 get() 方法,触发 __call
public function __call($method, $parameters)
{
return $this->store()->$method(...$parameters);
}
CacheManager没有定义get()方法- 触发
__call('get', ['key'])魔术方法 - 调用
$this->store()获取驱动实例
第 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']));
}
流程说明:
- 获取默认驱动名(从配置文件
config/cache.php读取,默认为'file') - 检查
$stores['file']是否已存在 - 如果不存在,调用
resolve('file') - 动态调用
createFileDriver($config) - 创建
FileStore实例(实现了Store契约)
第 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); // 调用具体驱动的方法
}
}
createFileDriver返回Repository实例Repository构造函数注入FileStore(实现了Store契约)- 这是依赖注入和面向接口编程的体现
第 6 步:实际执行
// __call 返回 Repository 实例后,调用其 get 方法
$repository->get('key');
// Repository 将调用委托给 FileStore
$this->store->get('key'); // FileStore->get('key')
Repository->get('key')最终调用FileStore->get('key')FileStore从文件系统读取缓存数据
四、架构图示
控制器
↓ Cache::get('key')
门面层 (Facade)
↓ __callStatic
服务容器
↓ 解析 'cache' 绑定
CacheManager(实现 FactoryContract)
↓ __call → store()
驱动解析
↓ resolve() → createFileDriver()
Repository(实现 CacheContract)
↓ 构造函数注入 Store
FileStore(实现 Store 契约)
↓ get() 方法
文件系统
五、设计模式总结
1. 门面模式(Facade)
Cache门面提供简洁的静态接口- 隐藏了底层复杂的实例化和调用逻辑
2. 契约模式(Contract / Interface)
FactoryContract:规范 CacheManager 的接口Store:规范缓存驱动的接口(get、put、forget 等)CacheContract:规范 Repository 的接口
3. 工厂模式(Factory)
CacheManager作为工厂,根据配置创建不同的驱动实例createFileDriver、createRedisDriver等方法是具体的工厂方法
4. 依赖注入(Dependency Injection)
Repository的构造函数注入Store契约- 上层代码依赖抽象(接口)而不是具体实现
5. 代理模式(Proxy)
CacheManager的__call方法代理到实际的驱动Facade的__callStatic代理到容器中的实例
六、核心优势
1. 上层无感知切换
控制器不需要关心使用的是 File、Redis 还是 Memcached:
// 修改 config/cache.php 的 default 配置即可切换
'default' => env('CACHE_DRIVER', 'redis'), // 从 file 切换到 redis
2. 易于扩展
添加新的缓存驱动只需:
- 创建实现
Store契约的类 - 在
CacheManager中添加createXxxDriver方法
3. 测试友好
可以轻松 Mock Store 契约进行单元测试:
$mockStore = Mockery::mock(Store::class);
$repository = new Repository($mockStore);
4. 符合 SOLID 原则
- 单一职责:每个类职责明确
- 开闭原则:对扩展开放,对修改封闭
- 依赖倒置:依赖抽象(契约)而非具体实现
七、总结
Laravel 的 Cache 模块完美展示了如何通过门面、契约、依赖注入等设计模式,构建一个灵活、可扩展的架构:
- 服务提供者注册服务到容器
- 门面提供简洁的静态调用接口
- CacheManager 作为工厂管理不同驱动
- **契约(接口)**规范各层的行为
- 依赖注入实现上层对底层的解耦
这种设计使得:
- 控制器代码简洁优雅
- 底层驱动可随意切换
- 系统易于测试和维护
- 符合面向对象设计原则