在我們日常寫Laravel程式的過程中,不可避免的要使用到cache
那麼你知道cache
有幾種寫法麼?
基本用法
方法一
我們可以使用Facades
來處理,具體如下:
use Illuminate\Support\Facades\Cache;
Cache::get('xxx');
方法二
我們還可以通過Contracts(契約)
來得到同樣的結果:
app('Illuminate\Contracts\Cache\Factory')->get('xxx');
方法三
除了使用上面的契約外,我們還可以使用實現這個契約的類,即:
app('Illuminate\Cache\CacheManager')->get('xxx');
方法四
我們還可以使用簡稱來獲得同樣的效果:
app('cache')->get('xxx');
方法五
其實上面的寫法還有一個更騷的寫法:
app()['cache']->get('xxx');
方法六
最後,Laravel為我們提供了一個輔助方法來實現同樣的效果:
cache('xxx');
原理分析
我們主要分析上面的方法二,即app('Illuminate\Contracts\Cache\Factory')->get('xxx')
。更準確的說是檢視原始碼,研究app('Illuminate\Contracts\Cache\Factory')
怎樣解析的,這其中主要用到了服務容器以及依賴注入的相關知識。
app方法
Laravel為我們提供的輔助方法app()
如下:
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract)) {
return Container::getInstance();
}
return Container::getInstance()->make($abstract, $parameters);
}
當我們呼叫app('Illuminate\Contracts\Cache\Factory')
方法的時候會首先生成Illuminate\Container\Container
例項,接著呼叫其中的make
方法。
make方法
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
if ($raiseEvents) {
$this->fireResolvingCallbacks($abstract, $object);
}
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}
下面讓我們來依次分析其中的程式碼。$abstract = $this->getAlias($abstract);
這行程式碼的作用是將Illuminate\Contracts\Cache\Factory
字串替換為cache
字串,這其中的原理如下,在Laravel程式啟動的時候Laravel會設定一系列的陣列,具體程式碼在Illuminate\Foundation\Application
下的registerCoreContainerAliases
方法中,其返回的結果為:
[
.
.
.
"Illuminate\Cache\CacheManager" => "cache",
"Illuminate\Contracts\Cache\Factory" => "cache"
.
.
.
]
所以,我們傳入的Illuminate\Contracts\Cache\Factory
就被替換成了cache
。
PS: 其實這一步在
Application
的make
方法中已經替換好了,由於用途一致,所以直接在這裡講了。
讓我們接著往下看程式碼,needsContextualBuild
開始直到$this->with[] = $parameters;
的這段程式碼目前對我們來說用處不大,可以先跳過。接著我們看$concrete = $this->getConcrete($abstract);
這一行程式碼。
但是在我們講這個之前,讓我們先跳出去,瞭解一些其他概念。
服務提供者(ServiceProvicer)
我們都知道,在Laravel中推薦在服務提供者的register()
方法中進行繫結服務,cache
也是如此。
// Illuminate\Cache\CacheServiceProvider
public function register()
{
$this->app->singleton('cache', function ($app) {
return new CacheManager($app);
});
...
}
在CacheServiceProvider
中,我們將cache
繫結到一個回撥函式中,並且將其設為singleton
。那麼,讓我們看看singleton
方法是怎樣實現的。
繫結
singleton
方法同樣位於Illuminate\Container\Container
中。
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
在bind
方法中,我們可以看到最終的結果是將回撥函式新增到私有變數$this->bindings
中,其結果如下:
array:2 [▼
"concrete" => Closure($app) {#266 ▼
class: "Illuminate\Cache\CacheServiceProvider"
this: CacheServiceProvider {#258 …}
file: "/Users/haoruijie/Documents/Code/avenger/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php"
line: "17 to 19"
}
"shared" => true
]
其中,shared
如果為true的話,表示這是一個單例(singleton)。
單例和普通繫結的區別在於,普通繫結每呼叫一次都需要新建一個對應的類,而單例則不需要
回到make方法
好了,讓我們回到make
方法中,接著往下看程式碼,之前我們說到$concrete = $this->getConcrete($abstract);
這裡,那麼這一步$concrete
變數返回的就是CacheServiceProvider
中的回撥方法。
接著下一段程式碼則會執行回撥函式,返回Illuminate\Cache\CacheManager
類:
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
這個就是我們通過app('Illuminate\Contracts\Cache\Factory')
方法最終得到Illuminate\Cache\CacheManager
類的過程。最後我們就能夠使用CacheManager
中的相關方法了。
總結
通過檢視原始碼我們可以發現其實不管是app('Illuminate\Contracts\Cache\Factory')
還是app('Illuminate\Cache\CacheManager')
在make
方法中一開始就被轉化成了app('cache')
,所以一開始的方法二,三,四的實現原理都是一樣的。而最後的輔助方法cache()
則是封裝了一層的app('cache')
方法。
function cache()
{
$arguments = func_get_args();
if (empty($arguments)) {
return app('cache');
}
if (is_string($arguments[0])) {
return app('cache')->get(...$arguments);
}
if (! is_array($arguments[0])) {
throw new Exception(
'When setting a value in the cache, you must pass an array of key / value pairs.'
);
}
if (! isset($arguments[1])) {
throw new Exception(
'You must specify an expiration time when setting a value in the cache.'
);
}
return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1]);
至於app()['cache']
的原理,是由於Container
繼承了[ArrayAccess](https://www.php.net/manual/zh/class.arrayaccess.php)
介面。這個介面提供了像訪問陣列一樣訪問物件的能力。在Container
中的程式碼如下:
public function offsetGet($key)
{
return $this->make($key);
}
所以app()['cache']
的實現原理你也懂了。至於第一個方法中 Facade 的實現,那是另一個故事了。
彩蛋
你們可以試試resolve('cache')
方法,有驚喜哦。