rizalfakhri / laravel-repositories
Rinvex Repository 是 Laravel 的一种简单、直观、智能的 Active Repository 实现,具有极其灵活和细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。
Requires
- php: ^7.4.0
- illuminate/container: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.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
- illuminate/database: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.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
- illuminate/support: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0|^7.0.0
Requires (Dev)
- codedungeon/phpunit-result-printer: ^0.27.0
- illuminate/config: ~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0.0
- phpunit/phpunit: ^9.0.0
Suggests
- illuminate/pagination: Required to paginate the result set.
README
⚠️ 此包已被弃用,不再维护。未建议替代包。 ⚠️
Rinvex Repository 是 Laravel 的一种简单、直观、智能的 Active Repository 实现,具有极其灵活和细粒度的缓存系统,用于抽象数据层,使应用程序更易于维护。
💡 如果您需要 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
方法设置查询的“偏移”值
$repository->offset(5);
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([...]);
- 从版本2.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: Autoloader 并期望使用它的其他包也遵循相同的标准。这对于 自动猜测 是必需的,例如,当仓库模型缺失时,它将自动猜测并相应解决,尽管完整的目录结构可能不是必需的,但它是一切 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 是好且非常易用的》的文章,您可以在此处阅读:[Active Repository 是好 & 非常易用](https://blog.omranic.com/active-repository-is-good-awesomely-usable-6991cfd58774),如果您感兴趣的话。
- Repository利用缓存标签的方式非常智能,即使您选择的缓存驱动器不支持它。Repository会自己管理它,以实现精确的缓存管理。在幕后,它使用一个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-2020 Rinvex LLC,部分版权所有。