kyromacie/l5repository

用于以友好仓库模式管理数据对象查询的Laravel/Lumen 5.7包

1.3.0 2019-09-22 18:44 UTC

This package is not auto-updated.

Last update: 2024-09-30 19:11:31 UTC


README

pipeline status coverage report License: MIT

不喜欢杂乱的控制器?使用此包,您可以简单地在一行代码中将查询使用分离。此包基于仓库模式解决方案,并允许您从您的仓库方法或特别创建的操作中运行查询。此外,它提供了缓存机制,以减轻您的相关数据库存储。

内容

  1. 需求
  2. 设置
  3. 配置
  4. 使用

需求

此包是用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" 目录,所以我可以设置自定义的,以防止出现不期望的行为。

希望您会喜欢这个包,并且它将帮助您解决缓存问题,了解文件和类应该如何结构化。

如果您发现任何问题,请为我报告。我会尽快修复这些问题。