关联
设置控制器
定义模型关联控制器与定义模型控制器的方式非常相似。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\RelationController;
class PostCommentsController extends RelationController
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
/**
* 在 Post 模型上定义的关联名称
*/
protected $relation = 'comments';
}
此时你无需关心不同的关联类型——所有类型的关联,其控制器的定义方式都是相同的。
可填充的中间表字段与类型转换
如果你为 belongsToMany 或 morphToMany 关联类型定义控制器,并且中间表上有额外字段,则需要注意两个额外的属性——protected $pivotFillable 和 protected $pivotJson。
$pivotFillable 属性需要包含可通过 attach、sync、toggle 和 updatePivot 端点更新的中间表字段列表。
$pivotJson 属性应包含中间表上需要自动与数组互相转换的 json 字段列表。如果你已在相关的 Pivot 模型上定义了 $casts 属性,则可以跳过它。
- 模型关联控制器始终继承
Orion\Http\Controllers\RelationController $model属性设置为完全限定的模型类名$relation属性设置为模型上定义的确切关联名称
设置路由
与控制器不同,每种关联类型的路由定义方式各不相同。
<?php
use Illuminate\Support\Facades\Route;
use Orion\Facades\Orion;
use App\Http\Controllers\ProfileImageController;
...
Route::group(['as' => 'api.'], function() {
...
Orion::hasOneResource('profiles', 'image', ProfileImageController::class);
Orion::hasManyResource('users', 'posts', UserPostsController::class);
Orion::belongsToResource('posts', 'user', PostUserController::class);
Orion::belongsToManyResource('users', 'roles', UserRolesController::class);
Orion::hasOneThroughResource('posts', 'meta', PostMetaController::class);
Orion::hasManyThroughResource('users', 'comments', UserCommentsController::class);
Orion::morphOneResource('posts', 'image', PostImageController::class);
Orion::morphManyResource('posts', 'comments', PostCommentsController::class);
Orion::morphToResource('images', 'post', ImagePostController::class);
Orion::morphToManyResource('posts', 'tags', PostTagsController::class);
Orion::morphedByManyResource('tags', 'posts', TagsPostsController::class);
...
});
软删除
如果你的关联模型使用了 SoftDeletes trait,并且你希望通过 API 暴露相同的功能,请在注册资源时调用 withSoftDeletes 方法。
<?php
use Illuminate\Support\Facades\Route;
use Orion\Facades\Orion;
use App\Http\Controllers\UserPostsController;
Route::group(['as' => 'api.'], function() {
...
Orion::hasManyResource('users', 'posts', UserPostsController::class)->withSoftDeletes();
...
});
这会引入 restore 和 batchRestore 端点。要了解如何通过 API 永久删除资源(强制删除),请参阅相关的查询参数一节。
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
...
| | POST | api/users/{user}/posts/{post}/restore | api.users.relation.posts.restore | App\Http\Controllers\Api\UserPostsController@restore | api |
| | POST | api/users/{user}/posts/batch/restore | api.users.relation.posts.batchRestore | App\Http\Controllers\Api\UserPostsController@batchRestore | api |
一对一
以下关联被视为一对一关联:
hasOnehasOneThroughmorphOnebelongsTo(hasMany关联的反向关联)morphTo(morphMany关联的反向关联)
对于一对一关联,Orion 提供 4 个端点(即 CRUD 操作的端点):store、show、update、destroy
belongsTo 和 morphTo 关联不提供 store 端点。路由注册示例
Orion::hasOneResource('profiles', 'image' , ProfileImageController::class);
可用端点示例
+-----------+-------------------------------------------------+------------------------------------------+---------------------------------------------------------------------------+
| Method | URI | Name | Action |
+-----------+-------------------------------------------------+------------------------------------------+---------------------------------------------------------------------------+
| POST | api/profiles/{profile}/image | api.profiles.relation.image.store | App\Http\Controllers\Api\ProfileImageController@store |
| GET|HEAD | api/profiles/{profile}/image/{image?} | api.profiles.relation.image.show | App\Http\Controllers\Api\ProfileImageController@show |
| PATCH|PUT | api/profiles/{profile}/image/{image?} | api.profiles.relation.image.update | App\Http\Controllers\Api\ProfileImageController@update |
| DELETE | api/profiles/{profile}/image/{image?} | api.profiles.relation.image.destroy | App\Http\Controllers\Api\ProfileImageController@destroy |
hasOne、hasOneThrough、morphOne、belongsTo 和 morphTo 关联不需要提供关联资源的键。一对多
以下关联被视为一对多关联:
hasManyhasManyThroughmorphMany
对于一对多关联,Orion 提供 11 个端点(用于 CRUD 操作、搜索、关联和取消关联的端点):index、search、store、show、update、destroy、associate、dissociate、batchStore、batchUpdate、batchDestroy
路由注册示例
Orion::hasManyResource('users', 'posts' , UserPostsController::class);
可用端点示例
+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+
| Method | URI | Name | Action |
+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+
| GET|HEAD | api/users/{user}/posts | api.users.relation.posts.index | App\Http\Controllers\Api\UserPostsController@index |
| POST | api/users/{user}/posts/search | api.users.relation.posts.search | App\Http\Controllers\Api\UserPostsController@index |
| POST | api/users/{user}/posts | api.users.relation.posts.store | App\Http\Controllers\Api\UserPostsController@store |
| GET|HEAD | api/users/{user}/posts/{post} | api.users.relation.posts.show | App\Http\Controllers\Api\UserPostsController@show |
| PATCH | api/users/{user}/posts/{post} | api.users.relation.posts.update | App\Http\Controllers\Api\UserPostsController@update |
| PUT | api/users/{user}/posts/{post} | api.users.relation.posts.update | App\Http\Controllers\Api\UserPostsController@update |
| DELETE | api/users/{user}/posts/{post} | api.users.relation.posts.destroy | App\Http\Controllers\Api\UserPostsController@destroy |
| POST | api/users/{user}/posts/associate | api.users.relation.posts.associate | App\Http\Controllers\Api\UserPostsController@associate |
| DELETE | api/users/{user}/posts/{post}/dissociate | api.users.relation.posts.dissociate | App\Http\Controllers\Api\UserPostsController@dissociate |
| POST | api/users/{user}/posts/batch | api.users.relation.posts.batchStore | App\Http\Controllers\Api\UserPostsController@batchStore |
| PATCH | api/users/{user}/posts/batch | api.users.relation.posts.batchUpdate | App\Http\Controllers\Api\UserPostsController@batchUpdate |
| DELETE | api/users/{user}/posts/batch | api.users.relation.posts.batchDestroy | App\Http\Controllers\Api\UserPostsController@batchDestroy |
关联
一对多关联资源提供 associate 端点,用于将关联模型与父模型建立关联。
该端点的请求载荷只有一个字段——related_key。在我们的示例中,related_key 是要与用户关联的文章的 ID。
请求示例:
// (POST) https://myapp.com/api/users/{user}/posts/associate
{
"related_key" : 5
}
取消关联
一对多关联资源还提供 dissociate 端点,用于解除关联模型与其父模型之间的关联。
该端点的请求没有载荷;不过,请注意上面示例路由中的 {post} 路由参数——它是要与用户解除关联的文章的 ID。
多对多
以下关联被视为多对多关联:
belongsToManymorphToMany
对于多对多关联,Orion 提供 14 个端点(用于 CRUD 操作、搜索、附加、分离、同步、切换和更新中间表的端点):index、search、store、show、update、destroy、attach、detach、sync、toggle、pivot、batchStore、batchUpdate、batchDestroy
路由注册示例
Orion::belongsToManyResource('users', 'roles' , UserRolesController::class);
可用端点示例
+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+
| Method | URI | Name | Action |
+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+
| GET|HEAD | api/users/{user}/roles | api.users.relation.roles.index | App\Http\Controllers\Api\UserRolesController@index |
| POST | api/users/{user}/roles/search | api.users.relation.roles.search | App\Http\Controllers\Api\UserRolesController@index |
| POST | api/users/{user}/roles | api.users.relation.roles.store | App\Http\Controllers\Api\UserRolesController@store |
| GET|HEAD | api/users/{user}/roles/{role} | api.users.relation.roles.show | App\Http\Controllers\Api\UserRolesController@show |
| PATCH | api/users/{user}/roles/{role} | api.users.relation.roles.update | App\Http\Controllers\Api\UserRolesController@update |
| PUT | api/users/{user}/roles/{role} | api.users.relation.roles.update | App\Http\Controllers\Api\UserRolesController@update |
| DELETE | api/users/{user}/roles/{role} | api.users.relation.roles.destroy | App\Http\Controllers\Api\UserRolesController@destroy |
| POST | api/users/{user}/roles/attach | api.users.relation.roles.attach | App\Http\Controllers\Api\UserRolesController@attach |
| DELETE | api/users/{user}/roles/detach | api.users.relation.roles.detach | App\Http\Controllers\Api\UserRolesController@detach |
| PATCH | api/users/{user}/roles/sync | api.users.relation.roles.sync | App\Http\Controllers\Api\UserRolesController@sync |
| PATCH | api/users/{user}/roles/toggle | api.users.relation.roles.toggle | App\Http\Controllers\Api\UserRolesController@toggle |
| PATCH | api/users/{user}/roles/{role}/pivot | api.users.relation.roles.pivot | App\Http\Controllers\Api\UserRolesController@updatePivot |
| POST | api/users/{user}/roles/batch | api.users.relation.roles.batchStore | App\Http\Controllers\Api\UserRolesController@batchStore |
| PATCH | api/users/{user}/roles/batch | api.users.relation.roles.batchUpdate | App\Http\Controllers\Api\UserRolesController@batchUpdate |
| DELETE | api/users/{user}/roles/batch | api.users.relation.roles.batchDestroy | App\Http\Controllers\Api\UserRolesController@batchDestroy |
attach、sync、toggle 和 updatePivot 端点可能无法按预期处理这些字段。附加
多对多关联资源提供 attach 端点,用于将一个或多个关联模型附加到另一个模型。关于 Laravel 中关联模型附加/分离的工作原理,请查看 Laravel 文档中的 Attaching / Detaching 章节。
请求载荷由必填的 resources 字段和可选的 duplicates 字段组成。注意 duplicates 字段也可以作为查询参数提供。
resources 字段可以是数组或对象。如果是数组,数组项为要附加的关联模型的 ID。如果是对象,其键为关联模型的 ID,值为对象——其中包含附加关联模型时要设置的中间表字段。
默认情况下,duplicates 参数为 false。如果设置为 true,多次附加同一个关联模型会导致中间表中出现重复条目。
请求示例(数组版本):
// (POST) https://myapp.com/api/users/{user}/roles/attach
{
"resources" : [3,4,7]
}
请求示例(对象版本):
// (POST) https://myapp.com/api/users/{user}/roles/attach
{
"resources" : {
"3" : {
"example_pivot_field" : "value A",
...
},
"4" : {
"example_pivot_field" : "value B",
...
},
"7" : {
"example_pivot_field" : "value C",
...
}
}
}
分离
多对多关联资源提供 detach 端点,用于将一个或多个关联模型从其附加的模型上分离。
请求载荷只包含一个字段 resources。
与 attach 端点类似,resources 字段可以是数组或对象。此方法支持 resources 字段的对象表示,使前端能够以标准化的方式附加/分离关联资源。你还可以选择在这些对象中存储额外数据,供 beforeDetach 或 afterDetach 钩子使用。
请求示例(数组版本):
// (DELETE) https://myapp.com/api/users/{user}/roles/detach
{
"resources" : [3,4,7]
}
请求示例(对象版本):
// (DELETE) https://myapp.com/api/users/{user}/roles/detach
{
"resources" : {
"3" : {},
"4" : {
"some_field" : "some value",
...
},
"7" : {},
}
}
同步
多对多关联资源提供 sync 端点,用于同步一个或多个关联模型与另一个模型之间的关联。关于 Laravel 中关联模型同步的工作原理,请查看 Laravel 文档中的 Syncing Associations 章节。
请求载荷由必填的 resources 字段和可选的 detaching 字段组成。注意 detaching 字段也可以作为查询参数提供。
resources 字段可以是数组或对象。如果是数组,数组项为要同步的关联模型的 ID。如果是对象,其键为关联模型的 ID,值为对象——其中包含同步关联模型时要设置的中间表字段。
默认情况下,detaching 参数为 true。如果设置为 false,载荷中缺失但存在于中间表中的关联模型不会被分离。
请求示例(数组版本):
// (PATCH) https://myapp.com/api/users/{user}/roles/sync
{
"resources" : [3,4]
}
请求示例(对象版本):
// (PATCH) https://myapp.com/api/users/{user}/roles/sync
{
"resources" : {
"3" : {
"example_pivot_field" : "value A",
...
},
"4" : {
"example_pivot_field" : "value B",
...
},
}
}
切换
多对多关联资源提供 toggle 端点,用于「切换」一个或多个关联模型的附加状态。关于 Laravel 中关联模型「切换」的工作原理,请查看 Laravel 文档中的 Toggling Associations 章节。
请求载荷只包含一个字段 resources。与 sync 端点相同,resources 字段可以是数组或对象。
请求示例(数组版本):
// (PATCH) https://myapp.com/api/users/{user}/roles/toggle
{
"resources" : [3,4]
}
请求示例(对象版本):
// (PATCH) https://myapp.com/api/users/{user}/roles/toggle
{
"resources" : {
"3" : {
"example_pivot_field" : "value A",
...
},
"4" : {
"example_pivot_field" : "value B",
...
},
}
}
更新中间表
多对多关联资源提供 pivot 端点,用于更新某个关联模型的中间表记录。关于中间表记录如何更新,请查看 Laravel 文档中的 Updating A Record On A Pivot Table 章节。
请求载荷只包含一个字段 pivot。其属性是将为该关联模型更新的中间表字段。
请求示例:
// (PATCH) https://myapp.com/api/users/{user}/roles/{role}/pivot
{
"pivot" : { // 属性对应中间表中的列
"example_pivot_field" : "updated value",
"another_pivot_field" : "new value"
...
}
}
自定义键名
与模型资源一样,关联资源使用主键从数据库中获取资源。
自定义关联资源键名
<?php
namespace App\Http\Controllers\Api;
use App\Models\Team;
use Orion\Http\Controllers\RelationController;
class TeamPostsController extends RelationController
{
/**
* 模型的完全限定类名
*/
protected $model = Team::class; // 或 "App\Models\Team"
/**
* 在 Post 模型上定义的关联名称
*/
protected $relation = 'posts';
/**
* 用于从数据库中获取资源的字段名称。
*
* @return string
*/
protected function keyName(): string
{
return 'slug'; // 这里的 "slug" 是 Post 模型(posts 关联)上的字段
}
}
自定义父资源键名
如果父资源和关联资源都使用自定义键名,你还需要重写 parentKeyName 方法:
<?php
namespace App\Http\Controllers\Api;
use App\Models\Team;
use Orion\Http\Controllers\RelationController;
class TeamPostsController extends RelationController
{
/**
* 模型的完全限定类名
*/
protected $model = Team::class; // 或 "App\Models\Team"
/**
* 在 Post 模型上定义的关联名称
*/
protected $relation = 'posts';
/**
* 用于从数据库中获取父资源的字段名称。
*
* @return string
*/
protected function parentKeyName(): string
{
return 'short_name'; // 这里的 "short_name" 是 Team 模型上的字段
}
/**
* 用于从数据库中获取资源的字段名称。
*
* @return string
*/
protected function keyName(): string
{
return 'slug'; // 这里的 "slug" 是 Post 模型(posts 关联)上的字段
}
}
自定义查询
与模型控制器一样,你可以为每个端点重新定义 Eloquent 查询。唯一的主要区别在于:关联控制器中的每个端点还提供用于获取关联父模型的 "build" 和 "run" 方法。
标准操作方法
| 方法 | 构建(父级) | 运行(父级) | 构建 | 运行 | 执行 |
|---|---|---|---|---|---|
| index | buildIndexParentFetchQuery | runIndexParentFetchQuery | buildIndexFetchQuery | runIndexFetchQuery | - |
| store | buildStoreParentFetchQuery | runStoreParentFetchQuery | buildStoreFetchQuery | runStoreFetchQuery | performStore |
| show | buildShowParentFetchQuery | runShowParentFetchQuery | buildShowFetchQuery | runShowFetchQuery | - |
| update | buildUpdateParentFetchQuery | runUpdateParentFetchQuery | buildUpdateFetchQuery | runUpdateFetchQuery | performUpdate |
| destroy | buildDestroyParentFetchQuery | runDestroyParentFetchQuery | buildDestroyFetchQuery | runDestroyFetchQuery | performDestroy |
| restore | buildRestoreParentFetchQuery | runRestoreParentFetchQuery | buildRestoreFetchQuery | runRestoreFetchQuery | performRestore |
一对多操作方法
| 方法 | 构建(父级) | 运行(父级) | 构建 | 运行 | 执行 |
|---|---|---|---|---|---|
| associate | buildAssociateParentFetchQuery | runAssociateParentFetchQuery | buildAssociateFetchQuery | runAssociateFetchQuery | performAssociate |
| dissociate | buildDissociateParentFetchQuery | runDissociateParentFetchQuery | buildDissociateFetchQuery | runDissociateFetchQuery | performDissociate |
多对多操作方法
| 方法 | 构建(父级) | 运行(父级) | 构建 | 运行 | 执行 |
|---|---|---|---|---|---|
| attach | buildAttachParentFetchQuery | runAttachParentFetchQuery | - | - | performAttach |
| detach | buildDetachParentFetchQuery | runDetachParentFetchQuery | - | - | performDetach |
| sync | buildSyncParentFetchQuery | runSyncParentFetchQuery | - | - | performSync |
| toggle | buildToggleParentFetchQuery | runToggleParentFetchQuery | - | - | performToggle |
| updatePivot | buildUpdatePivotParentFetchQuery | runUpdatePivotParentFetchQuery | - | - | performUpdatePivot |
performFill 方法来自定义模型属性的填充方式。