该篇属于[《Laravel底层核心技术实战揭秘》]; 欢迎作客我们的php&Laravel学习群:109256050(//study.163.com/course/courseMain.htm?courseId=1003575006)这一课程《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实现也就不会受到影响。
假设这是对应于我们User
model的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实现,比如也可以在装饰者中触发特定事件。