koala-labs / pouch
将 Laravel 的 Eloquent 模型作为可注入、掩码资源仓库的神奇实现。
Requires
- php: ^7.4|^8.0
- illuminate/database: ^8|^9|^10
Requires (Dev)
- friendsofphp/php-cs-fixer: 3.4.*
- mockery/mockery: ^1
- orchestra/testbench: ^6|^7|^8
- phpunit/phpunit: ^9
README
Koala Pouch 是 Fuzz Production 的 Magic Box 的分支。
Koala Pouch 有两个目标
- 创建一个双向交换格式,以便 API 传输的模型的 JSON 表示可以重新应用到原始模型上,以更新现有资源和创建新资源。
- 为 API 客户端提供一个接口,以便它们可以以他们想要的方式请求确切的数据。
安装/设置
-
composer require koala/pouch
-
将
Koala\Pouch\Middleware\RepositoryMiddleware
用于您的项目,并在app/Http/Kernel.php
中的$routeMiddleware
数组下注册您的类。RepositoryMiddleware
包含可以覆盖的各种配置选项。 -
如果您正在使用
fuzz/api-server
,可以通过更新app/Providers/RouteServiceProvider.php
中的RouteServiceProvider@map
来使用魔法路由,包括:/** * Define the routes for the application. * * @param \Illuminate\Routing\Router $router * @return void */ public function map(Router $router) { // Register a handy macro for registering resource routes $router->macro('restful', function ($model_name, $resource_controller = 'ResourceController') use ($router) { $alias = Str::lower(Str::snake(Str::plural(class_basename($model_name)), '-')); $router->resource($alias, $resource_controller, [ 'only' => [ 'index', 'store', 'show', 'update', 'destroy', ], ]); }); $router->group(['namespace' => $this->namespace], function ($router) { require app_path('Http/routes.php'); }); }
-
在分配给您的
RepositoryMiddleware
类的中间件键下设置您的 Pouch 资源路由。 -
设置一个
YourAppNamespace\Http\Controllers\ResourceController
,这里是一个 ResourceController 的示例。 -
根据
Model Setup
部分设置模型。
测试
在执行 composer install
后,只需运行 phpunit
。
Eloquent 仓库
Koala\Pouch\EloquentRepository
实现了一个 CRUD 仓库,它通过关系级联,无论相关模型是否已创建。
考虑一个简单的模型,其中用户有多个帖子。EloquentRepository 的基本用法如下:
创建一个用户,用户名为 Steve,他有一个标题为 Stuff 的帖子。
$repository = (new EloquentRepository) ->setModelClass('User') ->setInput([ 'username' => 'steve', 'nonsense' => 'tomfoolery', 'posts' => [ 'title' => 'Stuff', ], ]); $user = $repository->save();
当调用 $repository->save()
时,将创建一个用户,用户名为 "Steve",并创建一个具有属于该用户的 user_id
的帖子。无意义的 "nonsense" 属性被简单地忽略,因为它实际上并不存在于存储用户的表中。
Eloquent HasManyThrough 关系的限制
定义通过 HasManyThrough
关系关联的模型的输入必须引用一个已经存在的模型。在这个例子中,一个 User
通过一个 Post
有多个 Reaction
。为了正确关联到 User
,Reaction
模型必须存在并且与一个 Post
关联。
$repository = (new EloquentRepository) ->setModelClass('User') ->setInput([ 'username' => 'steve', 'posts' => [ 'title' => 'Stuff', ], 'reactions' => [ [ 'id': 1 //The Reaction model must already exist, and relate to a Post ] ] ]); $user = $repository->save();
对于附加到 User
的新 Reaction
和 Post
模型的解决方案是,将 Reaction
模型数据嵌套在相关的 Post
下。
$repository = (new EloquentRepository) ->setModelClass('User') ->setInput([ 'username' => 'steve', //The Post is related to the User, and the Reaction is related to the Post. User Reactions are related through the Post. 'posts' => [ 'title' => 'Stuff', 'reactions' => [ [ 'name' => 'John Doe', 'icon' => 'thumbs-up' ] ] ], ]); $user = $repository->save();
将很快支持通过 HasThroughMany
关系定义的新模型。
仅就其本身而言,EloquentRepository 是一把没有访问控制的钝器,在任何公共 API 中都应该避免使用。它会无偏见地覆盖它接触到的每一个关系。例如,以下是一个向刚刚创建的用户添加新帖子的不良方式。
$repository ->setInput([ 'id' => $user->id, 'posts' => [ ['title' => 'More Stuff'], ], ]) ->save();
这将删除可怜的 Steve 的第一条帖子——这不是预期的效果。安全(更安全)地添加帖子的一种方式是以下两种之一
$repository ->setInput([ 'id' => $user->id, 'posts' => [ ['id' => $user->posts->first()->id], ['title' => 'More Stuff'], ], ]) ->save();
$post = $repository ->setModelClass('Post') ->setInput([ 'title' => 'More Stuff', 'user' => [ 'id' => $user->id, ], ]) ->save();
一般来说,后者更受欢迎,并且不太可能在你面前爆炸。
从仓库返回模型的公共 API 方法有
创建
读取
更新
删除
save
,根据输入状态将调用create
或update
find
,通过ID查找模型findOrFail
,通过ID查找模型,如果未找到则抛出\Illuminate\Database\Eloquent\ModelNotFoundException
返回\Illuminate\Database\Eloquent\Collection
的公共API方法有
all
过滤
Koala\Pouch\Filter
根据通过filters
参数传递的过滤值处理Eloquent查询构建器的修改。
令牌和用法
通过关系过滤
假设我们有用户及其相关的表格,结构如下
[ 'username' => 'Bobby', 'profile' => [ 'hobbies' => [ ['name' => 'Hockey'], ['name' => 'Programming'], ['name' => 'Cooking'] ] ] ]
我们可以使用users?filters[profile.hobbies.name]=^Cook
通过爱好过滤用户。
此过滤器可以读作选择其profile.hobbies.name以"Cook"开头的用户
关系可以是任意深度。
过滤连接词
我们可以使用AND
和OR
语句构建过滤器,例如users?filters[username]==Bobby&filters[or][username]==Johnny&filters[and][profile.favorite_cheese]==Gouda
。从此过滤器构建的PHP数组是
[ 'username' => '=Bobby', 'or' => [ 'username' => '=Johnny', 'and' => [ 'profile.favorite_cheese' => '=Gouda', ] ] ]
此过滤器可以读作选择(具有用户名Bobby的用户)或(具有用户名Johnny且其profile.favorite_cheese属性为Gouda的用户)
。
其他参数
选择
我们可以在URL中添加pick
来限制查询返回的数据量。用法
https://api.yourdomain.com/1.0/users?pick=id,username,occupation
https://api.yourdomain.com/1.0/users?pick[]=id&pick[]=username&pick[]=occupation
模型设置
模型需要在Pouch允许它们作为Pouch资源暴露之前实现Koala\Pouch\Contracts\PouchResource
接口。这样做是为了使暴露成为一个显式的过程,并且不会暴露比所需更多的内容。
模型还需要定义自己的$fillable
数组,包括可以通过此模型填充的属性和关系。例如,如果用户有多个帖子且有多个评论,但API消费者应该只能通过用户更新评论,则$fillable
数组将如下所示
protected $fillable = ['username', 'password', 'name', 'comments'];
Pouch将仅修改显式定义的属性/关系。
解析模型
Pouch很棒,但我们不想在实例化存储库之前自己解析模型类...
如果您已配置了一个具有复数资源的RESTful URI结构(即https://api.mydowmain.com/1.0/users
映射到User模型),则可以使用Koala\Pouch\Utility\Modeler
从路由名称解析模型类名称。
测试
phpunit
:)
待办事项
- 路由服务提供商应预先设置
- 通过级联保存支持更多关系(特别是多态关系)
- 支持分页嵌套关系