allcaretravel / laravel-repositories
Rinvex Repository 是一个简单、直观、智能的 Active Repository 实现,为 Laravel 提供了极灵活且细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。
Requires
- php: ^7.4|^8.0
- illuminate/container: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.0.0|^8.0.0
- illuminate/contracts: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.0.0|^8.0.0
- illuminate/database: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.0.0|^8.0.0
- illuminate/events: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.0.0|^8.0.0
- illuminate/support: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.0.0|^8.0.0
- laravel/helpers: ^1.1.0
Requires (Dev)
- codedungeon/phpunit-result-printer: ^0.26.0
- illuminate/config: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.0.0|^8.0.0
- phpunit/phpunit: ^8.3.0
Suggests
- illuminate/pagination: Required to paginate the result set.
README
Rinvex Repository 是一个简单、直观、智能的 Active Repository 实现,为 Laravel 提供了极灵活且细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。
💡 如果你在寻找 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']); } }
任务完成!你现在可以使用这个包了!✅
除非你需要深入了解并了解一些高级内容,否则你可以跳过以下步骤!😉
目录
- 安装
- 集成
- 配置
- 用法
- 快速示例
- 详细文档
setContainer()
,getContainer()
setConnection()
,getConnection()
setModel()
,getModel()
setRepositoryId()
,getRepositoryId()
setCacheLifetime()
,getCacheLifetime()
setCacheDriver()
,getCacheDriver()
enableCacheClear()
,isCacheClearEnabled()
createModel()
forgetCache()
with()
where()
whereIn()
whereNotIn()
whereHas()
offset()
limit()
orderBy()
find()
findBy()
findFirst()
findAll()
paginate()
simplePaginate()
findWhere()
findWhereIn()
findWhereNotIn()
findWhereHas()
create()
update()
store()
delete()
beginTransaction()
commit()
rollBack()
- 将代码转换为接口
- 添加自定义实现
- EloquentRepository 触发的事件
- 必须的仓库约定
- 自动猜测
- 灵活且细粒度缓存
- 最后的想法
- 变更日志
- 支持
- 贡献 & 协议
- 安全漏洞
- 关于 Rinvex
- 许可
安装
安装此包最好的最简单的方法是通过 Composer。
兼容性
此包完全兼容 Laravel 5.1.*
, 5.2.*
, 和 5.3.*
。
虽然这个包倾向于无框架,但在一定程度上它接受了 Laravel 的文化和最佳实践。它主要与 Laravel 测试,但你仍然可以使用它与其他框架或甚至没有任何框架。
所需包
打开你的应用程序的 composer.json
文件,并在 require
数组中添加以下行
"rinvex/laravel-repositories": "3.0.*"
注意: 确保在执行所需更改后,通过运行
composer validate
来确保你的composer.json
文件是有效的。
安装依赖
在你的终端运行 composer install
或 composer 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
以来,所有findWhere
、findWhereIn
和findWhereNotIn
方法的签名都已更改。- 所有
findWhere
、findWhereIn
和findWhereNotIn
方法分别使用where
、whereIn
和whereNotIn
方法,因此第一个参数是后面所需参数的数组。- 所有
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']);
注意: 此方法只是
create
和update
方法的别名。当单表用于创建和更新过程时,它非常有用。
delete()
delete
方法删除具有给定 id 的实体
$deletedEntity = $repository->delete(1);
beginTransaction()
beginTransaction
方法开始数据库事务
$repository->beginTransaction();
commit()
commit
方法提交数据库事务
$repository->commit();
rollBack()
rollback
方法回滚数据库事务
$repository->rollBack();
注意事项
- 所有
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 服务提供者中将它们两个绑定到 IoC(在 register
方法内部)
$this->app->bind(UserRepositoryContract::class, UserEloquentRepository::class)
这样我们就不必手动实例化存储库,并且可以轻松地在多个实现之间切换。IoC 容器将处理所需的依赖项。
添加自定义实现
由于我们专注于抽象数据层,并且将抽象接口与具体实现分开,因此很容易添加自己的实现。
比如说,你的领域模型使用网络服务或文件系统数据存储作为其数据源,你所需要做的就是扩展 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 仓库 的如下
├── 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');
两者 setCacheLifetime
和 setCacheDriver
方法都是可链式的
// 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,部分权利保留。