cache 有几种写法,你都会了么?

在我们日常写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')方法,有惊喜哦。

本作品采用《CC 协议》,转载必须注明作者和本文链接
There's nothing wrong with having a little fun.
Epona
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 4

花里胡哨的,难道结果不一样 :smirk:

4年前 评论
Epona

@james_xue 所以只用最简单的写法就好了😂

4年前 评论

哈哈哈,孔乙己表示不服。

4年前 评论
Epona

@mamahaha 回字的写法比我 cache 写法少,他不服不行!

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!