rinvex / repository
Rinvex Repository 是 Active Repository 的一种简单、直观且智能的实现,为 Laravel 提供了极其灵活和细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。
Requires
- php: >=5.5.9
- illuminate/container: 5.1.*|5.2.*|5.3.*
- illuminate/contracts: 5.1.*|5.2.*|5.3.*
- illuminate/database: 5.1.*|5.2.*|5.3.*
- illuminate/events: 5.1.*|5.2.*|5.3.*
- illuminate/support: 5.1.*|5.2.*|5.3.*
Suggests
- illuminate/pagination: Required to paginate the result set.
README
Rinvex Repository 是 Active Repository 的一种简单、直观且智能的实现,为 Laravel 提供了极其灵活和细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。
⚠️ 此包已被重命名,现在在 rinvex/laravel-repositories 上维护,作者建议使用新包。旧包支持 Laravel v5.6,而新包支持 Laravel v5.7+。
💡 如果您需要 Laravel 5.5 支持,请使用 dev-develop 分支。它是稳定的,但尚未标记,因为测试套件尚未完成。 💡
⚠️ 此包正在寻找新的维护者,有兴趣的话请阅读详情或接管。 ⚠️
目录
- 功能
- 安装
- 集成
- 配置
- 使用
- 快速示例
- 详细文档
setContainer(),getContainer()setModel(),getModel()setRepositoryId(),getRepositoryId()setCacheLifetime(),getCacheLifetime()setCacheDriver(),getCacheDriver()enableCacheClear(),isCacheClearEnabled()createModel()forgetCache()with()where()whereIn()whereNotIn()offset()limit()orderBy()find()findBy()findAll()paginate()simplePaginate()findWhere()findWhereIn()findWhereNotIn()create()update()delete()
- 将代码转换为接口
- 添加自定义实现
- EloquentRepository 触发的事件
- 强制仓库约定
- 自动猜测
- 灵活且细粒度缓存
- 总结
- 变更日志
- 支持
- 贡献与协议
- 安全漏洞
- 关于 Rinvex
- 许可证
功能
- 缓存,缓存,缓存!
- 防止代码重复。
- 减少潜在的编程错误。
- 以灵活的方式对查询进行细粒度缓存。
- 应用集中管理的、一致的访问规则和逻辑。
- 为领域模型实现和集中化缓存策略。
- 通过将客户端对象与领域模型分离来提高代码的可维护性和可读性。
- 通过自动化和隔离客户端对象以及领域模型来最大化可测试的代码量,以支持单元测试。
- 将行为与相关数据关联起来。例如,计算字段或执行实体内数据元素之间的复杂关系或业务规则。
安装
安装此包的最佳和最简单的方法是通过 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 install 或 composer update 命令,根据您应用程序的状态安装新的要求。
注意:查看 Composer 的 基本用法 文档以获取更多信息。
集成
Rinvex Repository 包是框架无关的,因此可以轻松地与您喜欢的框架集成。
本地集成
在框架外集成此包非常简单,只需引入 vendor/autoload.php 文件来自动加载该包。
注意:查看 Composer 的 自动加载 文档以获取更多信息。
Laravel 集成
Rinvex Repository 包默认支持 Laravel,并附带一个 Service Provider 以便于与框架集成。
安装包后,打开位于 config/app.php 的 Laravel 配置文件,并将以下服务提供者添加到 $providers 数组中
Rinvex\Repository\Providers\RepositoryServiceProvider::class,
在您的终端运行以下命令以发布配置文件
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']); } }
您可以开始了!关于使用此包的知识已经足够了。
一个好的程序员在穿过单行道之前总是两边看。 -Doug Linder
所以,您决定继续前进,哈哈?!太棒了!! :D
详细文档
setContainer(),getContainer()
setContainer 方法设置IoC容器实例,而 getContainer 返回它
// Set the IoC container instance $repository->setContainer(new \Illuminate\Container\Container()); // Get the IoC container instance: $container = $repository->getContainer();
setModel(),getModel()
setModel 方法设置仓库模型,而 getModel 返回它
// Set the repository model $repository->setModel(\App\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 方法设置应该预加载的关系
$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);
注意:所有的
where、whereIn和whereNotIn方法都是可链式的,可以在单个请求中多次调用。它会在内部保持一个数组以存储所有 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);
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 版本以来,所有
findWhere、findWhereIn和findWhereNotIn方法的签名都已更改。- 所有
findWhere、findWhereIn和findWhereNotIn方法分别利用where、whereIn和whereNotIn方法,因此第一个参数是后面所需相同参数的数组。
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*方法返回当前存储库的实例,因此可以链式调用。create、update和delete方法总是返回一个包含两个值的数组,第一个值是操作状态(成功或失败)作为布尔值,第二个值是刚刚操作的模型实例。- 建议像上述示例一样,通过您的存储库构造函数显式设置 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 Service Provider 的 register 方法中,将它们两个绑定到 IoC。
$this->app->bind(UserRepositoryContract::class, UserEloquentRepository::class)
这样我们就无需手动实例化存储库,而且很容易在多个实现之间切换。IoC 容器将处理所需的依赖。
添加自定义实现
由于我们专注于抽象数据层,并且将抽象接口与具体实现分开,因此很容易添加您自己的实现。
假设您的领域模型使用 Web 服务或文件系统数据存储作为其数据源,您只需要扩展 BaseRepository 类即可。见
class FilesystemRepository extends BaseRepository { // Implement here all `RepositoryContract` methods that query/persist data to & from filesystem or whatever datastore }
EloquentRepository 触发的事件
存储库在每次操作时都会触发事件,例如 create、update、delete。所有触发的事件都以存储库的标识符(您在 存储库构造函数 中设置的)为前缀,如下例所示
- 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');
除非明确禁用,否则默认为所有仓库启用缓存,并保持为您的 rinvex.repository.cache.lifetime 配置值,使用默认应用程序的缓存驱动程序 cache.default(这也可以按查询更改)。
// 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: 自动加载的PSR-4: 自动加载,以确保共享PHP代码之间的高互操作性。
- 目前,我认为通过实现
Criteria Pattern来增加一个更复杂的层进行过滤的好处不大,我更愿意保持它像现在这样简单,使用传统的where子句,因为我们可以达到相同的结果。(你有不同的看法吗?请解释。)
变更日志
有关项目的完整历史,请参阅变更日志。
支持
以下支持渠道都在您的指尖:
贡献 & 协议
感谢您考虑为这个项目做出贡献!贡献指南可以在CONTRIBUTING.md中找到。
欢迎提交错误报告、功能请求和拉取请求。
安全漏洞
如果您在此项目中发现安全漏洞,请发送电子邮件至help@rinvex.com。所有安全漏洞都将得到及时处理。
关于Rinvex
Rinvex是一家成立于2016年6月的埃及亚历山大的软件解决方案初创公司,专注于为中小企业提供集成企业解决方案。我们相信,我们的动力是价值、触达和影响,这是我们与众不同的地方,通过软件的力量,我们释放了我们的哲学的无穷可能性。我们喜欢称之为“以生活速度的创新”。这就是我们为推进人类事业所做的一份贡献。
许可证
本软件根据MIT许可证(MIT)发布。
(c) 2016 Rinvex LLC,部分权利保留。