kyromacie / l5repository
用于以友好仓库模式管理数据对象查询的Laravel/Lumen 5.7包
Requires
- php: ^7.1.0
- illuminate/cache: 5.7.*|5.8.*
- illuminate/config: 5.7.*|5.8.*
- illuminate/contracts: 5.7.*|5.8.*
- illuminate/database: 5.7.*|5.8.*
- illuminate/support: 5.7.*|5.8.*
- nette/php-generator: ^3.2
Requires (Dev)
- fzaninotto/faker: *
- orchestra/database: 3.7.x-dev@dev|3.8.x-dev@dev
- orchestra/testbench: 3.7.*|3.8.*
- phpunit/phpunit: ^7.5
- predis/predis: *
This package is not auto-updated.
Last update: 2024-09-30 19:11:31 UTC
README
不喜欢杂乱的控制器?使用此包,您可以简单地在一行代码中将查询使用分离。此包基于仓库模式解决方案,并允许您从您的仓库方法或特别创建的操作中运行查询。此外,它提供了缓存机制,以减轻您的相关数据库存储。
内容
需求
此包是用PHP 7.2为Laravel 5.7和Lumen 5.7编写的,因此我不保证在旧版本上此包能稳定运行。
对于此解决方案,您只能使用基于标签的缓存机制,如Redis/Memcache。
设置
要下载此包,只需在您的项目终端中写下
$ composer require kromacie/l5repository
当包被下载后,注册服务提供者。
对于Lumen
$app->register(\Kromacie\L5Repository\RepositoryServiceProvider::class);
对于Laravel,这应该会自动发现,但如果不会,请使用上面的相同命名空间在config/app.php中注册它。
接下来,复制或提供配置文件。您可以在"vendor/kromacie/l5repository/config"目录中找到它。
配置
此包为所有必要的案例提供了配置。
return array(
/*
* Here youe should add classnames of all created cachable repositories
* to enable finding all related queries with any of these repositories, it allows to
* automatically flush cache when model is creating, deleting or updating.
*/
'repositories' => [
],
/*
* You can change repository resolver here to use your own if you need other caching mechanism
*/
'resolver' => \Kromacie\L5Repository\RepositoryResolver::class,
'cache' => [
/*
* Here is cache prefix for all created keys and tags. You should set something unique
* to avoid overwriting your cache keys by other values. It may cause some unexpected errors.
*/
'prefix' => 'l5repository',
/*
* Set the default store for all repositories
* Remember, you can ovverride this in your repository or by setting this directly for method
*/
'store' => 'redis',
/*
* Set the default time in minutes for all repositories. It cant be forever to not
* choke your cache memory. Preffered time should be something like 10 minutes, becouse
* if your database will grow up, it may fast overfill your memory.
*/
'time' => 10,
/*
* Set if scopes from repositories should be cached as one of tag and part of builded cache key.
* If you are using parameters in your scopes, it should be enabled. Otherwise, it will return
* last cached query for your repository method regardless of set parameters.
*/
'scopes' => true,
/*
* Set true if u want to enable caching, or false if u want to be disabled.
* You can disable/enable this directly for repository or method.
*/
'enabled' => true,
]
);
使用
基础
假设您需要为用户创建一个新的仓库,只需创建一个名为UserRepository的新类(或您想要的任何名称)。它需要扩展AbstractRepository类。
namespace App\Http\Repositories;
use Kromacie\L5Repository\Repositories\AbstractRepository;
use App\Http\Models\User;
class UserRepository extends AbstractRepository
{
public static function getClass(): String
{
return User::class;
}
}
抽象仓库实现了一个名为getClass的抽象静态函数,这是定义该仓库将要使用哪个模型的好地方。
最初没有发生特殊的事情。只是一个类和一个方法。但在后台,您可以找到由AbstractRepository实现的一些基本方法。
- public function get(array $columns = ['*'])
- public function sum(string $column)
- public function count(string $columns = '*')
- public function exists()
- public function first(array $columns = ['*'])
- public function delete()
- public function updateOrCreate(array $attributes)
- public function updateOrInsert(array $attributes)
- public function insert(array $values)
- public function insertGetId(array $values, string $sequence = null)
- public function update(array $values)
- public function create(array $attributes)
- public function paginate(int $perPage = null, array $columns = ['*'], string $pageName = 'page', int $page = null)
- public function perform(ActionInterface $action)
几乎所有这些方法的使用都与Eloquent模型提供的方法相同。但是这里有一些新内容。名为perform的方法。
使用操作
为了最好地解释,让我们创建另一个类 - 一个名为UserLogin的操作。
namespace App\Http\Actions;
use Kromacie\L5Repository\Contracts\ActionInterface;
use Kromacie\L5Repository\Repositories\AbstractRepository;
class UserLogin implements ActionInterface
{
public function perform(AbstractRepository $repository)
{
// TODO: Implement logic of user login
}
}
此类实现了名为perform的方法。所以现在我们有一个没有任何逻辑的操作。让我们写一些内容。
作用域
下一步是创建作用域。它将是提供的逻辑的最低级别。
namespace App\Http\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Kromacie\L5Repository\Contracts\ScopeInterface;
class UserLoginData implements ScopeInterface
{
protected $login;
protected $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function scope(Builder $builder)
{
$builder->where('login', '=', $this->login)
->where('password', '=', $this->password);
}
}
此类将把所有丑陋的逻辑分离到一个可重用的组件中,这是我们之前已经创建的。名为scope的方法将在我们的仓库执行我们的操作时始终被处理。所以让我们实现它。
namespace App\Http\Actions;
use Kromacie\L5Repository\Contracts\ActionInterface;
use Kromacie\L5Repository\Repositories\AbstractRepository;
class UserLogin implements ActionInterface
{
protected $login;
protected $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function perform(AbstractRepository $repository)
{
$repository->scope(new UserLoginData($this->login, $this->password));
return $repository->first();
}
}
看看它看起来多么简单,只需将我们的作用域添加到我们要使用的仓库中。但为什么我们需要这么多类呢?这只是为了让您的工作更舒适。小的独立类只做一件简单的事情。在这种情况下,这只是我们的用户登录,但如果您的应用程序逻辑更复杂,它可能更难以阅读。
控制器使用
但现在在您的控制器中,您将只有一行代码需要使用。
public function login(UserLoginRequest $request) {
$login = $request->validated()['login'];
$password = $request->validated()['password'];
$user = $this->repository->perform(new UserLogin($login, $password));
}
好吧,它不止一行,但看起来还是很漂亮,不是吗?
使用方法
但假设我很懒,不想为所有查询创建操作?我可以在我们的仓库中创建方法。它将非常类似于我们的操作。
namespace App\Http\Repositories;
use Kromacie\L5Repository\Repositories\AbstractRepository;
use Kromacie\L5Repository\Traits\CachableRepository;
use App\Http\Models\User;
use App\Http\Scopes\UserLoginData;
class UserRepository extends AbstractRepository
{
use CachableRepository;
public function getCachableMethods(): array
{
return [
'login'
];
}
public static function getClass(): String
{
return User::class;
}
public function login($login, $password)
{
$this->scope(new UserLoginData($login, $password));
return $this->first();
}
}
现在,控制器中的使用方法如下
namespace App\Http\Controllers;
use App\Http\Repositories\UserRepository;
use App\Http\Requests\UserLoginRequest;
class UserController extends Controller
{
private $users;
public function __contruct(UserRepository $users)
{
$this->users = $users;
}
public function login(UserLoginRequest $request) {
$login = $request->validated()['login'];
$password = $request->validated()['password'];
$user = $this->users->login($login, $password);
}
}
非常相似。
缓存
几天过去了,我们的代码在增长,我们可以感觉到数据库过载的初步症状。在很短的时间内进行了太多查询。我们不必寻找像“如何缓存数据库查询”这样的解决方案,因为这个包提供了一个经过测试的解决方案来解决这个问题。
它将是CachableRepositoryTrait。
所以让我们实现它,并使用之前示例中的仓库。
namespace App\Http\Repositories;
use Kromacie\L5Repository\Repositories\AbstractRepository;
use Kromacie\L5Repository\Traits\CachableRepository;
use App\Http\Models\User;
use App\Http\Actions\UserLogin;
class UserRepository extends AbstractRepository
{
use CachableRepository;
public function getCachableMethods(): array
{
return [
UserLogin::class
];
}
public static function getClass(): String
{
return User::class;
}
}
新方法命名为getCachableMethods()。在这里,我们应该定义需要缓存哪些方法或操作。对于这个示例,我们想缓存UserLogin类。假设我们使用没有会话的API进行登录,我们想将数据库连接的数量限制到最少。
缓存配置
我们可以通过三种方式配置这个Action。在l5repository.php配置文件中,仓库或直接在getCachableMethod中。
配置文件总是具有最低的优先级,用于覆盖配置,因此如果为仓库设置配置,它将永远不会被配置文件覆盖。同样,如果为操作设置配置,它将永远不会被覆盖,因为它的优先级最高。
让我们看看示例。
/*
config/l5repository.php
*/
'cache' => [
'prefix' => 'l5repository', // <- this shouldn't be overwritten
'connection' => 'default',
'time' => 10,
'scopes' => true,
'enabled' => true,
]
/*-------------------------*/
class UserRepository extends AbstractRepository
{
use CacheableRepository;
/*
These three parameters will overwrite default configuration from file
*/
protected $cacheScopes = false;
protected $store = 'default';
protected $time = 5;
protected $enabled = true;
/*
But this implementation will always have priority over parameters and config file.
*/
public function getCachableMethods(): array
{
return [
UserLogin::class => [
'time' => 15,
'cacheScopes' => true,
'enabled' => true,
'store' => 'default'
/*
For example, if u dont want to define any of these parameters,
you can not to do this. Nothing bad will happen.
* /
],
'getUserById',
'getUsersByPost'
];
}
}
好的,我希望配置优先级如何工作的这部分已经足够清晰。
运行时
现在,我想创建PostRepository。我可以假设在我的页面上可能有许多帖子,所以我应该分页它们。但我不想缓存所有的帖子。只缓存第一页和第二页,以免缓存溢出。那怎么做?CachableRepository实现了运行时方法。它允许我们覆盖任何参数。
让我们看看这个。
假设我们在PostRepository中。
public function getPosts($page) {
if($page > 2) {
$this->runtime('enabled', false);
}
return $this->paginate(10, ['*'], 'myPage', $page);
}
这将只缓存第一页和第二页。其他页面将直接从数据库获取。
使用标签
到目前为止,我们还有一件事还没有解释。
如果我想加载一些关系到我的仓库怎么办?如果那里有东西改变怎么办?这个解决方案是:TaggableInterface。如果我把这个添加到使用关系的Scope中,任何使用这个关系的对象被删除、更新或创建时,所有与这个关系标记的缓存查询都将被清除。
实现示例
namespace App\Http\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Kromacie\L5Repository\Contracts\ScopeInterface;
use Kromacie\L5Repository\Contracts\TaggableInterface;
use App\Http\Models\Permission;
class WithPermissions implements ScopeInterface, TaggableInterface
{
protected $login;
protected $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function tags(): array
{
return [
Permission::class
];
}
public function scope(Builder $builder)
{
$builder->with('permissions');
}
}
但要求是,你必须注册一个使用这个关系的仓库。并将这个仓库的类添加到配置中。
/* config/l5repository.php */
'repositories' => [
\App\Http\Repositories\PermissionRepository::class,
\App\Http\Repositories\UserRepository::class,
],
所以,对于这个示例,通过UserRepository和PermissionRepository(通过标签关联)删除、创建和更新模型将清除它们的缓存。
生成仓库
现在,你可以使用这个包的代码生成来自动化工作流程。
你可以输入一个命令,该命令将创建仓库、CRUD操作的动作,并在配置文件中注册新的仓库。
使用
php artisan repository:create {name} {--model=} {--crud} {--cache} {--action_dir=}
我将给出一个示例来帮助你理解这个命令是如何工作的。
假设,你会使用这个命令如下
php artisan repository:create User --model=User --crud --cache
这个命令应该为你创建UserRepository.php文件,并自动实现带有完全限定名称空间的方法"getClass"名为User的模型。
因为使用了--cache选项,UserRepository将使用CachableRepository作为Trait,并实现方法"getCachableMethods"(空体)。
因为使用了--crud选项,User目录中的UserRepository将创建基本的CRUD操作。默认实现假设有5个操作
- 创建
- 更新
- 删除
- 显示
- 显示所有
为了更好地理解,我将给出下一个更复杂的示例
php artisan repository:create QuestionHasContentRepository --model=QuestionContent --crud --cache --action_dir=Question/Content
该命令将在您的 Repositories 目录中创建 QuestionHasContentRepository.php。同时,"getClass" 也会被实现。那么,为什么我给 actions 设置了自定义目录呢?因为存储库的名称是 QuestionHasContent,它将被映射到目录 Question/Has/Content。我不想有 "Has" 目录,所以我可以设置自定义的,以防止出现不期望的行为。
希望您会喜欢这个包,并且它将帮助您解决缓存问题,了解文件和类应该如何结构化。
如果您发现任何问题,请为我报告。我会尽快修复这些问题。