allcaretravel/laravel-repositories

Rinvex Repository 是一个简单、直观、智能的 Active Repository 实现,为 Laravel 提供了极灵活且细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。

v6.0.1 2021-02-11 09:32 UTC

This package is auto-updated.

Last update: 2024-09-11 18:44:53 UTC


README

Rinvex Repository Diagram

Rinvex Repository 是一个简单、直观、智能的 Active Repository 实现,为 Laravel 提供了极灵活且细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。

Packagist Scrutinizer Code Quality Travis StyleCI License

💡 如果你在寻找 Laravel 5.5 的支持,请使用 dev-develop 分支。它已稳定,但尚未标记,因为测试套件尚未完成。 💡

⚠️ 此包正在寻找新的维护者,如感兴趣,请阅读详情或接管! ⚠️

特性

  • 缓存,缓存,缓存!
  • 防止代码重复。
  • 减少潜在的编程错误。
  • 细粒度缓存查询,具有灵活的控制。
  • 应用集中管理、一致的访问规则和逻辑。
  • 为域模型实现并集中化缓存策略。
  • 通过将客户端对象从域模型中分离出来,提高代码的可维护性和可读性。
  • 通过自动化和将客户端对象与域模型隔离来支持单元测试,最大限度地增加可测试的代码量。
  • 将行为与相关数据关联。例如,计算字段或强制执行实体内部数据元素之间的复杂关系或业务规则。

快速示例(TL;DR)

Rinvex\Repository\Repositories\BaseRepository 是一个抽象类,具体的实现必须扩展它。

Rinvex\Repository\Repositories\EloquentRepository 是目前唯一的仓库实现(未来还将有更多,您也可以开发自己的实现),它使得创建新的 eloquent 模型实例以及轻松操作它们变得容易。要使用 EloquentRepository,您的仓库必须首先扩展它

namespace App\Repositories;

use Rinvex\Repository\Repositories\EloquentRepository;

class FooRepository extends EloquentRepository
{
    protected $repositoryId = 'rinvex.repository.uniqueid';

    protected $model = 'App\Models\User';
}

这就完成了!是的,就这么简单。

但如果您想对容器实例有更多的控制,或者想动态地传递模型名称,您可以选择以下做法

namespace App\Repositories;

use Illuminate\Contracts\Container\Container;
use Rinvex\Repository\Repositories\EloquentRepository;

class FooRepository extends EloquentRepository
{
    // Instantiate repository object with required data
    public function __construct(Container $container)
    {
        $this->setContainer($container)
             ->setModel(\App\Models\User::class)
             ->setRepositoryId('rinvex.repository.uniqueid');

    }
}

现在在您的控制器中,您可以通过 $repository = new \App\Repositories\FooRepository(); 传统地实例化仓库,或者使用 Laravel 的强大依赖注入和 IoC 来完成魔法

namespace App\Http\Controllers;

use App\Repositories\FooRepository;

class BarController
{
    // Inject `FooRepository` from the IoC
    public function baz(FooRepository $repository)
    {
        // Find entity by primary key
        $repository->find(1);

        // Find all entities
        $repository->findAll();

        // Create a new entity
        $repository->create(['name' => 'Example']);
    }
}

Rinvex Repository 工作流程 - 创建仓库 Rinvex Repository 工作流程 - 创建仓库

Rinvex 仓库工作流程 - 在控制器中使用 Rinvex 仓库工作流程 - 在控制器中使用

UML 图

任务完成!你现在可以使用这个包了!✅

除非你需要深入了解并了解一些高级内容,否则你可以跳过以下步骤!😉

目录

安装

安装此包最好的最简单的方法是通过 Composer

兼容性

此包完全兼容 Laravel 5.1.*, 5.2.*, 和 5.3.*

虽然这个包倾向于无框架,但在一定程度上它接受了 Laravel 的文化和最佳实践。它主要与 Laravel 测试,但你仍然可以使用它与其他框架或甚至没有任何框架。

所需包

打开你的应用程序的 composer.json 文件,并在 require 数组中添加以下行

"rinvex/laravel-repositories": "3.0.*"

注意: 确保在执行所需更改后,通过运行 composer validate 来确保你的 composer.json 文件是有效的。

安装依赖

在你的终端运行 composer installcomposer update 命令,根据你的应用程序状态安装新的要求。

注意: 检查 Composer 的 基本用法 文档以获取更多信息。

集成

Rinvex 仓库 包是无框架的,因此可以轻松地原生或与你的首选框架集成。

原生集成

在框架之外集成此包非常简单,只需要求加载 vendor/autoload.php 文件以自动加载包。

注意: 检查 Composer 的 自动加载 文档以获取更多信息。

在你的终端上运行以下命令以发布配置文件

php artisan vendor:publish --tag="rinvex-repository-config"

注意: 检查 Laravel 的 配置 文档以获取更多信息。

您已经准备就绪。集成已完成,您现在可以使用所有可用方法,请转到使用部分查看示例。

配置

如果您遵循了之前的集成步骤,则您的已发布配置文件位于config/rinvex.repository.php

配置选项非常直观且易于理解,如下所示

return [

    /*
    |--------------------------------------------------------------------------
    | Models Directory
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default models directory, just write
    | directory name, like 'Models' not the full path.
    |
    | Default: 'Models'
    |
    */

    'models' => 'Models',

    /*
    |--------------------------------------------------------------------------
    | Caching Strategy
    |--------------------------------------------------------------------------
    */

    'cache' => [

        /*
        |--------------------------------------------------------------------------
        | Cache Keys File
        |--------------------------------------------------------------------------
        |
        | Here you may specify the cache keys file that is used only with cache
        | drivers that does not support cache tags. It is mandatory to keep
        | track of cache keys for later usage on cache flush process.
        |
        | Default: storage_path('framework/cache/rinvex.repository.json')
        |
        */

        'keys_file' => storage_path('framework/cache/rinvex.repository.json'),

        /*
        |--------------------------------------------------------------------------
        | Cache Lifetime
        |--------------------------------------------------------------------------
        |
        | Here you may specify the number of seconds that you wish the cache
        | to be remembered before it expires. If you want the cache to be
        | remembered forever, set this option to -1. 0 means disabled.
        |
        | Default: -1
        |
        */

        'lifetime' => -1,

        /*
        |--------------------------------------------------------------------------
        | Cache Clear
        |--------------------------------------------------------------------------
        |
        | Specify which actions would you like to clear cache upon success.
        | All repository cached data will be cleared accordingly.
        |
        | Default: ['create', 'update', 'delete']
        |
        */

        'clear_on' => [
            'create',
            'update',
            'delete',
        ],

        /*
        |--------------------------------------------------------------------------
        | Cache Skipping URI
        |--------------------------------------------------------------------------
        |
        | For testing purposes, or maybe some certain situations, you may wish
        | to skip caching layer and get fresh data result set just for the
        | current request. This option allows you to specify custom
        | URL parameter for skipping caching layer easily.
        |
        | Default: 'skipCache'
        |
        */

        'skip_uri' => 'skipCache',

    ],

];

用法

详细文档

setContainer(), getContainer()

setContainer方法设置IoC容器实例,而getContainer返回它

// Set the IoC container instance
$repository->setContainer(new \Illuminate\Container\Container());

// Get the IoC container instance
$container = $repository->getContainer();

setConnection(), getConnection()

setConnection方法设置与存储库关联的连接,而getConnection返回它

// Set the connection associated with the repository
$repository->setConnection('mysql');

// Get the current connection for the repository
$connection = $repository->getConnection();

注意:传递给setConnection方法的名称应与您的config/database.php配置文件中列出的连接之一相匹配。

setModel(), getModel()

setModel方法设置存储库模型,而getModel返回它

// Set the repository model
$repository->setModel(\App\Models\User::class);

// Get the repository model
$repositoryModel = $repository->getModel();

setRepositoryId(), getRepositoryId()

setRepositoryId方法设置存储库标识符,而getRepositoryId返回它(它可以是您想要的任何内容,但必须是每个存储库的唯一

// Set the repository identifier
$repository->setRepositoryId('rinvex.repository.uniqueid');

// Get the repository identifier
$repositoryId = $repository->getRepositoryId();

setCacheLifetime(), getCacheLifetime()

setCacheLifetime方法设置存储库缓存生命周期,而getCacheLifetime返回它

// Set the repository cache lifetime
$repository->setCacheLifetime(123);

// Get the repository cache lifetime
$cacheLifetime = $repository->getCacheLifetime();

setCacheDriver(), getCacheDriver()

setCacheDriver方法设置存储库缓存驱动程序,而getCacheDriver返回它

// Set the repository cache driver
$repository->setCacheDriver('redis');

// Get the repository cache driver
$cacheDriver = $repository->getCacheDriver();

enableCacheClear(), isCacheClearEnabled()

enableCacheClear方法启用存储库缓存清除,而isCacheClearEnabled确定其状态

// Enable repository cache clear
$repository->enableCacheClear(true);

// Disable repository cache clear
$repository->enableCacheClear(false);

// Determine if repository cache clear is enabled
$cacheClearStatus = $repository->isCacheClearEnabled();

createModel()

createModel()方法创建一个新的存储库模型实例

$repositoryModelInstance = $repository->createModel();

forgetCache()

forgetCache()方法遗忘存储库缓存

$repository->forgetCache();

with()

with方法设置应预加载的关系

// Pass a string
$repository->with('relationship');

// Or an array
$repository->with(['relationship1', 'relationship2']);

where()

where方法向查询添加基本的where子句

$repository->where('slug', '=', 'example');

whereIn()

whereIn方法向查询添加“where in”子句

$repository->whereIn('id', [1, 2, 5, 8]);

whereNotIn()

whereNotIn方法向查询添加“where not in”子句

$repository->whereNotIn('id', [1, 2, 5, 8]);

whereHas()

whereHas方法向查询添加“where has relationship”子句

use Illuminate\Database\Eloquent\Builder;

$repository->whereHas('attachments', function (Builder $builder) use ($attachment) {
    $builder->where('attachment_id', $attachment->id);
});

注意:所有的where*方法都是可链式的,可以在单个请求中多次调用。它会在内部保留一个包含所有where子句的数组,并在执行查询之前应用它们。

offset()

offset方法设置查询的“offset”值

$repository->offset(5);

limit()

limit方法设置查询的“limit”值

$repository->limit(9);

orderBy()

orderBy方法向查询添加“order by”子句

$repository->orderBy('id', 'asc');

find()

find方法通过主键查找实体

$entity = $repository->find(1);

findOrFail()

findOrFail()方法通过主键查找实体或抛出异常

$entity = $repository->findOrFail(1);

findOrNew()

findOrNew()方法通过主键查找实体或返回新的实体实例

$entity = $repository->findOrNew(1);

findBy()

findBy方法通过其中一个属性查找实体

$entity = $repository->findBy('id', 1);

findFirst()

findFirst方法查找第一个实体

$firstEntity = $repository->findFirst();

findAll()

findAll方法查找所有实体

$allEntities = $repository->findAll();

paginate()

paginate方法分页所有实体

$entitiesPagination = $repository->paginate(15, ['*'], 'page', 2);

正如您所猜想的,这个查询是第二页的前15条记录。

simplePaginate()

simplePaginate方法将所有实体分页到一个简单的分页器

$entitiesSimplePagination = $repository->simplePaginate(15);

findWhere()

findWhere方法查找所有匹配where条件的实体

// Matching values with equal '=' operator
$repository->findWhere(['slug', '=', 'example']);

findWhereIn()

findWhereIn方法查找所有匹配whereIn条件的实体

$includedEntities = $repository->findwhereIn(['id', [1, 2, 5, 8]]);

findWhereNotIn()

findWhereNotIn方法查找所有匹配whereNotIn条件的实体

$excludedEntities = $repository->findWhereNotIn(['id', [1, 2, 5, 8]]);

findWhereHas()

findWhereHas方法查找所有匹配whereHas条件的实体

use Illuminate\Database\Eloquent\Builder;

$entities = $repository->findWhereHas(['attachments', function (Builder $builder) use ($attachment) {
    $builder->where('attachment_id', $attachment->id);
}]);

注意事项

  • findWhereHas方法将返回匹配闭包内部条件的实体集合。如果您需要嵌入attachments关系,在这种情况下,您需要在使用findWhereHas()之前调用with()方法,如下所示:$repository->with('attachments')->findWhereHas([...]);
  • v2.0.0以来,所有findWherefindWhereInfindWhereNotIn方法的签名都已更改。
  • 所有 findWherefindWhereInfindWhereNotIn 方法分别使用 wherewhereInwhereNotIn 方法,因此第一个参数是后面所需参数的数组。
  • 所有 find* 方法都可以通过前面的 where 子句进行过滤,而且可以链式调用。所有 where 子句都存储在一个数组中,并在执行查询之前应用。请查看以下示例

findAll 方法的过滤示例

$allFilteredEntities = $repository->where('slug', '=', 'example')->findAll();

带有链式子句的过滤 findFirst 方法的另一个示例

$allFilteredEntities = $repository->where('name', 'LIKE', '%TEST%')->where('slug', '=', 'example')->findFirst();

create()

create 方法使用给定的属性创建一个新的实体

$createdEntity = $repository->create(['name' => 'Example']);

update()

update 方法使用给定的属性更新实体

$updatedEntity = $repository->update(1, ['name' => 'Example2']);

store()

store 方法使用给定的属性存储实体

// Existing Entity
$storedEntity = $repository->store(1, ['name' => 'Example2']);

// New Entity
$storedEntity = $repository->store(null, ['name' => 'Example2']);

注意: 此方法只是 createupdate 方法的别名。当单表用于创建和更新过程时,它非常有用。

delete()

delete 方法删除具有给定 id 的实体

$deletedEntity = $repository->delete(1);

beginTransaction()

beginTransaction 方法开始数据库事务

$repository->beginTransaction();

commit()

commit 方法提交数据库事务

$repository->commit();

rollBack()

rollback 方法回滚数据库事务

$repository->rollBack();

注意事项

  • 所有 find* 方法还接受一个可选参数来选择属性。
  • 所有 set* 方法返回当前存储库的实例,因此可以链式调用。
  • createupdatedelete 方法始终返回一个包含两个值的数组,第一个是操作状态(成功或失败)的布尔值,第二个是刚刚操作的模型实例。
  • 建议像上面示例那样通过存储库构造函数显式设置 IoC 容器实例、存储库模型和存储库标识符。但此包足够智能,可以猜测任何缺失的需求。请参阅自动猜测部分

将代码转换为接口

作为最佳实践,建议针对接口进行编码,特别是对于可扩展的项目。以下示例说明了如何这样做。

首先,为每个实体创建一个接口(抽象)

use Rinvex\Repository\Contracts\CacheableContract;
use Rinvex\Repository\Contracts\RepositoryContract;

interface UserRepositoryContract extends RepositoryContract, CacheableContract
{
    //
}

其次,为每个实体创建一个存储库(具体实现)

use Rinvex\Repository\Repositories\EloquentRepository;

class UserEloquentRepository extends EloquentRepository implements UserRepositoryContract
{
    //
}

现在在一个 Laravel 服务提供者中将它们两个绑定到 IoC(在 register 方法内部)

$this->app->bind(UserRepositoryContract::class, UserEloquentRepository::class)

这样我们就不必手动实例化存储库,并且可以轻松地在多个实现之间切换。IoC 容器将处理所需的依赖项。

注意: 请查阅 Laravel 的服务提供者服务容器文档以获取更多详细信息。

添加自定义实现

由于我们专注于抽象数据层,并且将抽象接口与具体实现分开,因此很容易添加自己的实现。

比如说,你的领域模型使用网络服务或文件系统数据存储作为其数据源,你所需要做的就是扩展 BaseRepository 类,就是这样。请参阅

class FilesystemRepository extends BaseRepository
{
    // Implement here all `RepositoryContract` methods that query/persist data to & from filesystem or whatever datastore
}

EloquentRepository 触发的事件

存储库在每次操作时都会触发事件,如 createupdatedelete。所有触发的事件都以存储库标识符(你在你的 存储库构造函数 中设置的)为前缀,如下例所示

  • rinvex.repository.uniqueid.entity.created
  • rinvex.repository.uniqueid.entity.updated
  • rinvex.repository.uniqueid.entity.deleted

为了方便起见,后缀为 .entity.created.entity.updated.entity.deleted 的事件具有相应的监听器。通常,我们需要在每次成功操作后刷新缓存(如果已启用且存在)。

还有一个事件 rinvex.repository.uniqueid.entity.cache.flushed,它在刷新缓存时触发。默认情况下,它没有监听器,但你可能需要监听它,如果你有模型关系需要进一步操作。

必须的仓库约定

在使用此包时,以下是一些重要的约定。此包遵循最佳实践,旨在使网页工匠的开发更加容易,因此它具有一些标准化和互操作性的约定。

  • 所有已触发的事件都有一个唯一的后缀,例如,例如 .entity.created。请注意,.entity. 是自动事件监听器订阅所必需的。

  • 任何包的默认目录结构,使用 Rinvex 仓库 的如下

├── config                  --> config files
|
├── database
|   ├── factories           --> database factory files
|   ├── migrations          --> database migration files
|   └── seeds               --> database seed files
|
├── resources
|   └── lang
|       └── en              --> English language files
|
├── routes                  --> Routes files
|   ├── api.php
|   ├── console.php
|   └── web.php
|
├── src                     --> self explanatory directories
|   ├── Console
|   |   └── Commands
|   |
|   ├── Http
|   |   ├── Controllers
|   |   ├── Middleware
|   |   └── Requests
|   |
|   ├── Events
|   ├── Exceptions
|   ├── Facades
|   ├── Jobs
|   ├── Listeners
|   ├── Models
|   ├── Overrides
|   ├── Policies
|   ├── Providers
|   ├── Repositories
|   ├── Scopes
|   ├── Support
|   └── Traits
|
└── composer.json           --> composer dependencies file

注意: Rinvex 仓库 遵循 PSR-4:自动加载器 并期望使用它的其他包也遵循相同的标准。它是 自动猜测 所必需的,例如,当仓库模型缺失时,它将自动猜测并相应地解决,尽管完整的目录结构可能不是必需的,但它是所有 Rinvex 包的标准。

自动猜测

虽然建议明确设置 IoC 容器、仓库标识符和仓库模型;但此包足够智能,可以在缺失时猜测任何这些所需数据。

  • IoC 容器 app() 助手用作未提供显式 IoC 容器实例时的后备。
  • 仓库标识符 建议将仓库标识符设置为点名称,如 rinvex.repository.uniqueid,但如果它缺失,将使用完全限定的仓库类名称(实际上是 static::class 的值)。
  • 仓库模型 传统上,仓库被命名空间如下 Rinvex\Demos\Repositories\ItemRepository,因此相应的模型应被命名空间如下 Rinvex\Demos\Models\Item。这就是此包根据 默认目录结构 猜测模型的方式,如果它缺失。

灵活且细粒度缓存

Rinvex 仓库 具有强大、简单且粒度化的缓存系统,几乎处理了所有边缘情况。虽然您可以整体启用/禁用应用程序的缓存,但您有权为每个单独的查询粒度启用/禁用缓存!这使您能够从缓存中排除某些查询,即使方法是默认缓存的或否则。

让我们看看我们可以控制哪些缓存级别

整个应用程序缓存

有关更多详细信息,请参阅 Laravel 的 缓存 文档。

单个查询缓存

按查询更改缓存或禁用它

// Set cache lifetime for this individual query to 123 seconds
$repository->setCacheLifetime(123);

// Set cache lifetime for this individual query to forever
$repository->setCacheLifetime(-1);

// Disable cache for this individual query
$repository->setCacheLifetime(0);

按查询更改缓存驱动程序

// Set cache driver for this individual query to redis
$repository->setCacheDriver('redis');

两者 setCacheLifetimesetCacheDriver 方法都是可链式的

// Change cache lifetime & driver on runtime
$repository->setCacheLifetime(123)->setCacheDriver('redis')->findAll();

// Use default cache lifetime & driver
$repository->findAll();

除非明确禁用,否则默认为所有仓库启用缓存,并保持与您的 rinvex.repository.cache.lifetime 配置值一样长,使用默认应用程序的缓存驱动程序 cache.default(这也可以按查询更改)。

缓存结果完全取决于您,尽管所有检索 find* 方法默认启用缓存,但您可以为单个查询启用/禁用缓存或控制其如何被缓存、保持多久以及使用哪个驱动程序。

临时跳过单个 HTTP 请求缓存

最后,您可以通过在 URL 中传递以下查询字符串来为单个请求跳过缓存 skipCache=true。您可以通过 rinvex.repository.cache.skip_uri 配置选项修改此参数以适应您可能需要的任何名称。

最后的想法

  • 由于这是一个不断发展的实现,可能会根据实际用例相应地更改。
  • 仓库智能地将缺失的调用方法传递给底层模型,因此您实际上可以通过利用仓库模型来实现任何类型的逻辑,甚至是复杂的查询。
  • 关于 Active Repository 实现的更多见解,我发布了一篇题为 Active Repository is good & Awesomely Usable 的文章,如果您感兴趣,请阅读。
  • 仓库以非常智能的方式利用缓存标签,即使你选择的缓存驱动程序不支持它。仓库将自行管理,实现精确的缓存管理。在幕后,它使用一个json文件来存储缓存键。检查rinvex.repository.cache.keys_file配置选项以更改文件路径。
  • Rinvex 仓库遵循符合PSR-1: 基本编码标准PSR-2: 编码风格指南PSR-4: 自动加载PSR-4: 自动加载,以确保共享PHP代码之间的高互操作性。
  • 目前我认为通过实现Criteria Pattern来添加更复杂的层进行过滤没有太多好处,我更愿意保持现在的简单性,使用传统的where子句,因为我们可以达到同样的效果。(你有不同的看法吗?请解释。)

变更日志

请参阅变更日志以了解项目的完整历史。

支持

以下支持渠道随时可用:

贡献 & 协议

感谢您考虑为此项目做出贡献!贡献指南可在CONTRIBUTING.md中找到。

非常欢迎提交错误报告、功能请求和拉取请求。

安全漏洞

如果您在此项目中发现安全漏洞,请发送电子邮件至help@rinvex.com。所有安全漏洞都将得到及时处理。

关于 Rinvex

Rinvex是一家成立于2016年6月的开罗,埃及的软件解决方案初创公司,专注于为中小企业提供集成企业解决方案。我们相信,我们的驱动力——价值、触达和影响力——是我们与众不同的地方,通过软件的力量释放我们哲学的无尽可能性。我们喜欢称之为“生活的速度创新”。这就是我们如何为推进人类文明做出我们的一份贡献。

许可

本软件根据MIT许可(MIT)发布。

(c)2016-2020 Rinvex LLC,部分权利保留。