mtferreira/repository

Rinvex Repository 是 Laravel 的一种简单、直观且智能的 Active Repository 实现,具有极其灵活且粒度化的缓存系统,用于抽象数据层,使应用程序更易于维护。

此包的官方仓库似乎已不存在,因此该包已被冻结。

v2.0.5 2017-09-29 18:56 UTC

README

Rinvex Repository Diagram

Rinvex Repository 是 Laravel 的一种简单、直观且智能的 Active Repository 实现,具有极其灵活且粒度化的缓存系统,用于抽象数据层,使应用程序更易于维护。

Packagist License VersionEye Dependencies Scrutinizer Code Quality Code Climate StyleCI SensioLabs Insight

目录

特性

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

安装

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

兼容性

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

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

先决条件

"php": ">=5.5.9",
"illuminate/events": "5.1.*|5.2.*|5.3.*",
"illuminate/support": "5.1.*|5.2.*|5.3.*",
"illuminate/database": "5.1.*|5.2.*|5.3.*",
"illuminate/container": "5.1.*|5.2.*|5.3.*",
"illuminate/contracts": "5.1.*|5.2.*|5.3.*"

需要包

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

"rinvex/repository": "2.0.*"

注意:确保运行composer validate后,您的composer.json文件有效。

安装依赖项

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

注意:有关更多详细信息,请查阅Composer的基本用法文档。

集成

Rinvex Repository包是框架无关的,因此可以轻松地本地或使用您喜欢的框架进行集成。

本地集成

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

注意:有关更多详细信息,请查阅Composer的自动加载文档。

Laravel集成

Rinvex Repository包默认支持Laravel,并附带一个服务提供程序,可轻松与框架集成。

安装包后,打开位于config/app.php的Laravel配置文件,并将以下服务提供程序添加到$providers数组中

Rinvex\Repository\Providers\RepositoryServiceProvider::class,

注意:有关更多详细信息,请查阅Laravel的服务提供程序服务容器文档。

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

php artisan vendor:publish --provider="Rinvex\Repository\Providers\RepositoryServiceProvider" --tag="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 minutes 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',

    ],

];

用法

快速示例

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\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\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 仓库工作流程 - 创建仓库 Rinvex Repository Workflow - Create Repository

Rinvex 仓库工作流程 - 在控制器中使用 Rinvex Repository Workflow - Use In Controller

UML 图

您可以开始了!关于使用这个包的知识已经足够了。

一个好的程序员在穿过单行道之前总是两边看。 -Doug Linder

所以,您决定继续前进,哈哈?!太棒了!! :D

详细文档

setContainergetContainer

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

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

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

setModelgetModel

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

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

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

setRepositoryIdgetRepositoryId

setRepositoryId 方法设置仓库标识符,而 getRepositoryId 返回它(它可以是你想要的任何东西,但必须是每个仓库的 唯一标识符

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

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

setCacheLifetimegetCacheLifetime

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

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

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

setCacheDrivergetCacheDriver

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

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

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

enableCacheClearisCacheClearEnabled

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 方法设置应该预先加载的关系

$repository->with(['relationship']);

where()

with 方法将基本的 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);

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

offset()

offset 方法设置查询的 "偏移量" 值

$repository->offset(5);

limit()

limit 方法设置查询的 "限制" 值

$repository->limit(9);

orderBy()

orderBy 方法将 "order by" 子句添加到查询中

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

find()

find 方法通过其主键查找实体

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

findBy()

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

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

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);

说明

  • v2.0.0 开始,所有 findWherefindWhereInfindWhereNotIn 方法的签名已更改。
  • 所有的 findWherefindWhereInfindWhereNotIn 方法分别使用 wherewhereInwhereNotIn 方法,因此第一个参数是后续方法所需参数数组的数组。

create()

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

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

// Assign created entity status and instance variables
list($status, $instance) = $createdEntity;

update()

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

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

// Assign updated entity status and instance variables
list($status, $instance) = $updatedEntity;

delete()

delete 方法用于根据给定的 id 删除实体

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

// Assign deleted entity status and instance variables
list($status, $instance) = $deletedEntity;

说明

  • 所有 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的服务提供程序服务容器文档。

添加自定义实现

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

例如,如果你的领域模型使用 Web 服务或文件系统数据存储作为数据源,你所需要做的只是扩展 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 Repository 的包的默认目录结构如下

├── config                  --> config files
|
├── database
|   ├── factories           --> database factory files
|   ├── migrations          --> database migration files
|   └── seeds               --> database seed files
|
├── resources
|   └── lang
|       └── en              --> English language files
|
├── src                     --> self explanatory directories
|   ├── Console
|   |   └── Commands
|   |
|   ├── Http
|   |   ├── Controllers
|   |   ├── Middleware
|   |   ├── Requests
|   |   └── routes.php
|   |
|   ├── 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 的点命名,但如果缺少,将使用完全限定的仓库类名称(实际上是 get_called_class() 函数的结果)。
  • 仓库模型 通常,仓库按照以下方式命名空间:Rinvex\Demos\Repositories\ItemRepository,因此相应的模型应该按照以下方式命名空间:Rinvex\Demos\Models\Item。这就是此包如果缺少模型时如何根据 默认目录结构 进行猜测的方式。

灵活且细粒度缓存

Rinvex 仓库 拥有一个强大、简单且细粒度的缓存系统,可以处理几乎所有的边缘情况。虽然你可以全局启用/禁用应用程序的缓存,但你也有权限对每个单独的查询进行细粒度的启用/禁用缓存!这让你有能力排除某些查询的缓存,即使方法默认或以其他方式被缓存。

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

整个应用程序缓存

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

单个查询缓存

按查询更改缓存或禁用它

// Set cache lifetime for this individual query to 123 minutes
$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》的文章,您可以在此处阅读:[Active Repository is good & Awesomely Usable](https://blog.omranic.com/active-repository-is-good-awesomely-usable-6991cfd58774)。
  • 存储库非常聪明地使用缓存标签,即使您选择的缓存驱动程序不支持它。存储库将自行管理以实现精确的缓存管理。在幕后,它使用json文件来存储缓存键。请检查rinvex.repository.cache.keys_file配置选项以更改文件路径。
  • Rinvex Repository遵循符合PSR-1: 基本编码标准PSR-2: 编码风格指南PSR-4: 自动加载的FIG PHP标准建议,以确保共享PHP代码之间具有高度互操作性。
  • 目前,我认为通过实现Criteria Pattern来添加更复杂的层以进行过滤并没有看到其好处,相反,我更喜欢保持它现在这样简单,使用传统的where子句,因为我们可以达到相同的结果。(你有不同的想法吗?请解释。)

变更日志

有关项目的完整历史,请参阅变更日志

支持

以下支持渠道随时可供您使用:

贡献 & 协议

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

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

安全漏洞

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

关于Rinvex

Rinvex是一家成立于2016年6月的开罗,埃及的软件解决方案初创公司,专注于为中小企业提供集成企业解决方案。我们相信,我们的动力是“价值、范围和影响”,这是我们与众不同的地方,并通过软件的力量释放我们哲学的无限可能性。我们喜欢称它为“与生活同步的创新”。这就是我们为推进人类事业贡献我们的一份力量。

许可协议

本软件在MIT许可协议(MIT)下发布。

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