该篇属于《Laravel底层核心技术实战揭秘》这一课程《laravel底层核心概念解析》这一章的扩展阅读。考虑到学员们的基础差异,为了避免视频当中过于详细而连篇累牍,故将一些laravel底层实现相关的PHP知识点以文章形式呈现,供大家预习和随时查阅。

像我们之前在课程里提到的,当开发任何正式的laravel项目时,将controller与我们的Eloquent ORM(或者其他的数据来源)进行解耦,向来是老练明智之举。通常我们会创建一个interface,然后再写一个实现了这个interface的repository,然后再通过laravel服务容器将对该interface的依赖解析到这个具体的repository上,具体的数据操作我们都是在repository当中进行。

同样的道理也适用于cache。我们都知道,数据查询往往是我们一个web应用里的主要性能瓶颈,所以难免要为数据查询创建相应的缓存(cache)。同样的,在你的controller里面具体地实现数据cache,也是非常糟糕的做法——这样呢,你就将controller与某一种特定的cache实现给强行绑到一块了,后期如果你想着换一种实现方式,比如从memcache换成redis,那么你就不得不大量修改你controller里的逻辑。而且,当某一个数据查询在多个地方出现时,你就不得不写重复的cache实现的代码。

或许呢,你可以在你的repository里写cache相关的实现逻辑,对相对较小的项目,这样倒也行得通。但是呢,这也意味着你的repository就既依赖于ORm,又依赖于你所选择的cache。这个时候如果你想着更换其中任何一种依赖形式,都很可能意味着你得重新写一整个新的repository,这期间又要重复很多之前的代码。

当然,肯定有一个更优雅的方式——通过使用“修饰者模式”(decorator pattern),我们可以再创建一个repository,让其实现同一个interface,同时呢,把之前那个repository里的实现给“包裹”起来,或者说“修饰”一下。在这个cache相关的repository里,每一个方法内都相应调取之前那个数据repository里的逻辑,然后相应地返回cache过后的response。这样的话,我们cache的逻辑,就跟我们数据操作的逻辑,相对分离开了,假设后期我们想换一种数据来源,那么我们的cache实现也就不会受到影响。

假设这是对应于我们Usermodel的interface:

<?php
namespace App\Repositories\Interfaces;

interface UserRepositoryInterface {
    public function all();
    public function findOrFail($id);
    public function create($input);
}

接下来呢是其对应的数据操作的repository:

<?php
namespace App\Repositories;

use App\User;
use App\Repositories\Interfaces\UserRepositoryInterface;
use Hash;

class EloquentUserRepository implements UserRepositoryInterface {
    private $model;
    public function __construct(User $model)
    {
        $this->model = $model;
    }
    public function all()
    {
        return $this->model->all();
    }
    public function findOrFail($id)
    {
        return $this->model->findOrFail($id);
    }
    public function create($input)
    {
        $user = new $this->model;
        $user->email = $input['email'];
        $user->name = $input['name'];
        $user->password = Hash::make($input['password']);
        $user->save();
        return $user;
    }
}

我们可以这样来定义一个cache repository:

<?php
namespace App\Repositories\Decorators;

use App\Repositories\Interfaces\UserRepositoryInterface;
use Illuminate\Contracts\Cache\Repository as Cache;

class CachingUserRepository implements UserRepositoryInterface {
    protected $repository;
    protected $cache;
    public function __construct(UserRepositoryInterface $repository, Cache $cache)
    {
        $this->repository = $repository;
        $this->cache = $cache;
    }
    public function all()
    {
        return $this->cache->tags('users')->remember('all', 60, function () {
            return $this->repository->all();
        });
    }
    public function findOrFail($id)
    {
        return $this->cache->tags('users')->remember($id, 60, function () use ($id) {
            return $this->repository->findOrFail($id);
        });
    }
    public function create($input)
    {
        $this->cache->tags('users')->flush();
        return $this->repository->create($input);
    }
}

可以看到我们的这个cache repository里每个方法,实际上并不负责任何的数据查询操作。我们通过constructor接收一个同样实现了该interface的具体实现类,也即之前的数据查询的repository,同时接收cache服务,这样呢,我们不需要触碰任何的数据查询逻辑,直接调取数据相关的repository里相应的方法,然后给它相应地“包裹”上cache即可,或者说用cache实现“修饰”了一下之前的数据查询结果。

那么要实际地调用到我们的这个cache实现,我们还需要更新一下我们的service provider,好让它把这个“修饰者”,也即我们的cache repository,给相应地解析出来:

<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{ 
	 public function register()
    {
        $this->app->singleton('App\Repositories\Interfaces\UserRepositoryInterface', function () {
            $baseRepo = new \App\Repositories\EloquentUserRepository(new \App\User);
            $cachingRepo = new \App\Repositories\Decorators\CachingUserRepository($baseRepo, $this->app['cache.store']);
            return $cachingRepo;
        });
    }
}

可以看到我们实例化了数据查询的repository,也即$baseRepo,然后实例化cache repository,将数据repository和cache服务传递进去,然后返回这个cache repository的实例。这样了以后,我们就可以在controller里实际调用了。

注意的是,用cache repository来“装饰”原本的数据repository,这只是一个例子、一种用法而已,希望通过这个,你能学会的是如何“装饰、修饰”你的已有的数据repository,不止是cache实现,比如也可以在装饰者中触发特定事件。