Laravel 源码分析系列:Cache
缓存源码分析
Laravel的cache使用起来特别方便,支持多种缓存驱动,包括apc
,database
, file
, memcache
,redis
,xcache
,wincache
,Laravel是如何做到同时支持这么多缓存驱动的呢,又是如何做到可以多种驱动快速切换呢,如何让所有的缓存驱动都使用统一的调用方法呢,我们今天就来分析一下Laravel的cache部份源码一探究竟吧。
基本流程分析
流程图
具体分析
从get开始
我们一般获取一个cache的代码类似Cache::get('cacheKey')
,熟悉Laravel的facade外观模式的话知道这里其实就是在使用Cache这个facade。通过查看config/app.php中的aliases
,可以看到实际使用的是:
'Cache' => Illuminate\Support\Facades\Cache::class,
cache外观 - Cache Facade
Cache的外观模式,代码如下:
<?php
namespace Illuminate\Support\Facades;
class Cache extends Facade
{
/**
* 获取注册注件的名称
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'cache';
}
}
这个类比较简单,就只有一个方法,方法返回了一个cache
字符串,这里面涉及到facade
的概念,我们在本节暂时不多说,后面会单独介绍,在这里只是告诉大家这里最后会得到一个在Laravel服务容器中的cache
服务,所以我们需要去找到这个服务是在哪注册。
在config/app.php
的providers
我们可以发现有一个Illuminate\Cache\CacheServiceProvider::class
,这就是我们的cache服务提供者。
cache服务提供者 - CacheServiceProvider
文件位置:src/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php
代码如下:
<?php
namespace Illuminate\Cache;
use Illuminate\Support\ServiceProvider;
use Illuminate\Cache\Console\ClearCommand;
use Illuminate\Cache\Console\CacheTableCommand;
class CacheServiceProvider extends ServiceProvider
{
/**
* 服务提供者是否延迟加载
*
* @var bool
*/
protected $defer = true;
/**
* 注册服务提供者
*
* @return void
*/
public function register()
{
// 在服务容器中设置一个名为cache的CacheManager单例
$this->app->singleton('cache', function ($app) {
return new CacheManager($app);
});
// 在服务容器中设置一个名为cache.store的单例,比如memcache database
$this->app->singleton('cache.store', function ($app) {
return $app['cache']->driver();
});
// 在服务容中设置一个名为memcached.connector的MemcachedConnector单例,
$this->app->singleton('memcached.connector', function () {
return new MemcachedConnector;
});
// 注册缓存相关的控制台命令
$this->registerCommands();
}
/**
* 注册缓存相关的控制台命令。
*
* @return void
*/
public function registerCommands()
{
// 清除缓存
$this->app->singleton('command.cache.clear', function ($app) {
return new ClearCommand($app['cache']);
});
// 创建database缓存驱动的存储表
$this->app->singleton('command.cache.table', function ($app) {
return new CacheTableCommand($app['files'], $app['composer']);
});
// 注册这两个命令
$this->commands('command.cache.clear', 'command.cache.table');
}
/**
* 提供的服务提供者。
*
* @return array
*/
public function provides()
{
return [
'cache', 'cache.store', 'memcached.connector', 'command.cache.clear', 'command.cache.table',
];
}
}
这样我们最后通过Cache::get()
访问的实际是CacheManager类,
也就是类似
<?php
$cache = new CacheManager($app);
$cache->get('cacheKey');
Cache Manager
我们看上面的代码在调用$cache->get()
,那我们去找找
CacheManager代码看看
文件位置:src/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php
在代码中我们发现有定义一个get方法:
<?php
/**
* 从本地的stores变量中获取缓存仓库,如果没有根据名称创建仓库
*
* @param string $name
* @return \Illuminate\Contracts\Cache\Repository
*/
protected function get($name)
{
return isset($this->stores[$name]) ? $this->stores[$name] : $this->resolve($name);
}
但这个方法的是protected
的,在外部无法访问。所以这时候会调用魔术方法__call
:
<?php
/**
* 动态调用默认驱动程序实例。
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return call_user_func_array([$this->store(), $method], $parameters);
}
首先调用$this->store()
, 代码如下:
<?php
/**
* 根据名称获取缓存存储实例
*
* @param string|null $name
* @return mixed
*/
public function store($name = null)
{
// 获取默认驱动名称
$name = $name ?: $this->getDefaultDriver();
// 根据名称获取缓存仓库
return $this->stores[$name] = $this->get($name);
}
看一下这里的$this->getDefaultDriver()
,代码如下:
<?php
/**
* 获取默认的缓存驱动名称
*
* @return string
*/
public function getDefaultDriver()
{
// 实际获取的是config/cache.php中的数组,key为default,没有修改过的话默认是 'default' => env('CACHE_DRIVER', 'file'),
return $this->app['config']['cache.default'];
}
根据上面的代码可以得到最后我们的$name
默认的为.env中定义的CACHE_DRIVER
,没有修改过的时候默认是CACHE_DRIVER=file
,我们这里修改为redis
所以最后$name = 'redis'
接着跟踪$this->get($name)
,代码如下:
<?php
/**
* 从本地的stores变量中获取缓存仓库,如果没有根据名称创建仓库
*
* @param string $name
* @return \Illuminate\Contracts\Cache\Repository
*/
protected function get($name)
{
return isset($this->stores[$name]) ? $this->stores[$name] : $this->resolve($name);
}
接着跟踪$this->resolve($name)
,代码如下:
<?php
/**
* 使用给定的名称创建缓存驱动
*
* @param string $name
* @return \Illuminate\Contracts\Cache\Repository
*
* @throws \InvalidArgumentException
*/
protected function resolve($name)
{
// 根据名称获取配置信息
$config = $this->getConfig($name);
// 没有配置信息则抛异常
if (is_null($config)) {
throw new InvalidArgumentException("Cache store [{$name}] is not defined.");
}
// 判断是不是自定义的驱动类型,如果是则调用
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($config);
} else {
// 根据配置信息组装创建驱动的方法名
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
// 判断是否存在方法,存在则调用具体的方法创建驱动并返回
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($config);
} else {
throw new InvalidArgumentException("Driver [{$config['driver']}] not supported.");
}
}
}
以redis为例,最后会调用$this->createRedisDriver($config)
, 代码如下:
<?php
/**
* 创建一个redis缓存驱动实例
*
* @param array $config
* @return \Illuminate\Cache\RedisStore
*/
protected function createRedisDriver(array $config)
{
// 获取redis实例
$redis = $this->app['redis'];
// 获取redis连接信息
$connection = Arr::get($config, 'connection', 'default');
// 通过redis存储创建缓存库
return $this->repository(new RedisStore($redis, $this->getPrefix($config), $connection));
}
可以看到最后将实例化的new RedisStore
作为参数,传给$this->repository
,继续追踪代码如下:
<?php
/**
* 通过给定的缓存存储实现创建一个缓存库
*
* @param \Illuminate\Contracts\Cache\Store $store
* @return \Illuminate\Cache\Repository
*/
public function repository(Store $store)
{
// 创建缓存库
$repository = new Repository($store);
// 如果没有设置过事件调度器则设置事件调度器实例
if ($this->app->bound('Illuminate\Contracts\Events\Dispatcher')) {
$repository->setEventDispatcher(
$this->app['Illuminate\Contracts\Events\Dispatcher']
);
}
return $repository;
}
可以看到,我们最后得到了一个Repository
的实例。
我们总结一下中间的过程,伪代码如下:
$cache->get();
↓
CacheManager::__call()
↓
$this->store()
↓
$this->get($name); <- $name = $this->getDefaultDriver();
↓
$this->resolve($name);
↓
$this->createRedisDriver($config); <- $config = $this->getConfig($name);
↓
$this->repository(new RedisStore($redis, $this->getPrefix($config), $connection));
↓
return $repository = new Repository($store);
也就是说最后我们实际是$repository->get('cacheKey');
,接下来我们就需要分析Repository类了
对于阅读 Laravel 核心源码来说,很不错的突破口 :+1:
:+1:
:+1:
源码分析很好,希望后续有更多的分析
不分析 container ?
最近在看依赖注入,请问cache是在哪一步骤完成依赖注入的。
@haoyuqi vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php
@maxincai 谢谢!
牛逼
接下来我们就需要分析Repository类了
where ?
谁发个链接
一年了,楼主还没更新
@sushengbuhuo 楼主很忙