cache 有幾種寫法,你都會了麼?

Epona發表於2019-09-06

在我們日常寫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: 其實這一步在Applicationmake方法中已經替換好了,由於用途一致,所以直接在這裡講了。

讓我們接著往下看程式碼,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')方法,有驚喜哦。

There's nothing wrong with having a little fun.

相關文章