指南

关联

了解如何通过 API 暴露模型关联。

设置控制器

定义模型关联控制器与定义模型控制器的方式非常相似。

<?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';
}

此时你无需关心不同的关联类型——所有类型的关联,其控制器的定义方式都是相同的。

可填充的中间表字段与类型转换

如果你为 belongsToManymorphToMany 关联类型定义控制器,并且中间表上有额外字段,则需要注意两个额外的属性——protected $pivotFillableprotected $pivotJson

$pivotFillable 属性需要包含可通过 attachsynctoggleupdatePivot 端点更新的中间表字段列表。

$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();
    ...
});

这会引入 restorebatchRestore 端点。要了解如何通过 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                                             |

一对一

以下关联被视为一对一关联:

  • hasOne
  • hasOneThrough
  • morphOne
  • belongsTohasMany 关联的反向关联)
  • morphTomorphMany 关联的反向关联)

对于一对一关联,Orion 提供 4 个端点(即 CRUD 操作的端点):storeshowupdatedestroy

belongsTomorphTo 关联不提供 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                   |
注意最后一个参数被标记为可选。因为这是一对一关联,你可以在不提供关联键的情况下访问端点——Orion 会自动处理。

hasOnehasOneThroughmorphOnebelongsTomorphTo 关联不需要提供关联资源的键。

一对多

以下关联被视为一对多关联:

  • hasMany
  • hasManyThrough
  • morphMany

对于一对多关联,Orion 提供 11 个端点(用于 CRUD 操作、搜索、关联和取消关联的端点):indexsearchstoreshowupdatedestroyassociatedissociatebatchStorebatchUpdatebatchDestroy

路由注册示例

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。

多对多

以下关联被视为多对多关联:

  • belongsToMany
  • morphToMany

对于多对多关联,Orion 提供 14 个端点(用于 CRUD 操作、搜索、附加、分离、同步、切换和更新中间表的端点):indexsearchstoreshowupdatedestroyattachdetachsynctogglepivotbatchStorebatchUpdatebatchDestroy

路由注册示例

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                 |
如果你的中间表上有额外字段,请不要忘记按照可填充的中间表字段与类型转换章节所述定义可填充的中间表字段,否则 attachsynctoggleupdatePivot 端点可能无法按预期处理这些字段。

附加

多对多关联资源提供 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 字段的对象表示,使前端能够以标准化的方式附加/分离关联资源。你还可以选择在这些对象中存储额外数据,供 beforeDetachafterDetach 钩子使用。

请求示例(数组版本):

// (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" 方法。

标准操作方法

方法构建(父级)运行(父级)构建运行执行
indexbuildIndexParentFetchQueryrunIndexParentFetchQuerybuildIndexFetchQueryrunIndexFetchQuery-
storebuildStoreParentFetchQueryrunStoreParentFetchQuerybuildStoreFetchQueryrunStoreFetchQueryperformStore
showbuildShowParentFetchQueryrunShowParentFetchQuerybuildShowFetchQueryrunShowFetchQuery-
updatebuildUpdateParentFetchQueryrunUpdateParentFetchQuerybuildUpdateFetchQueryrunUpdateFetchQueryperformUpdate
destroybuildDestroyParentFetchQueryrunDestroyParentFetchQuerybuildDestroyFetchQueryrunDestroyFetchQueryperformDestroy
restorebuildRestoreParentFetchQueryrunRestoreParentFetchQuerybuildRestoreFetchQueryrunRestoreFetchQueryperformRestore

一对多操作方法

方法构建(父级)运行(父级)构建运行执行
associatebuildAssociateParentFetchQueryrunAssociateParentFetchQuerybuildAssociateFetchQueryrunAssociateFetchQueryperformAssociate
dissociatebuildDissociateParentFetchQueryrunDissociateParentFetchQuerybuildDissociateFetchQueryrunDissociateFetchQueryperformDissociate

多对多操作方法

方法构建(父级)运行(父级)构建运行执行
attachbuildAttachParentFetchQueryrunAttachParentFetchQuery--performAttach
detachbuildDetachParentFetchQueryrunDetachParentFetchQuery--performDetach
syncbuildSyncParentFetchQueryrunSyncParentFetchQuery--performSync
togglebuildToggleParentFetchQueryrunToggleParentFetchQuery--performToggle
updatePivotbuildUpdatePivotParentFetchQueryrunUpdatePivotParentFetchQuery--performUpdatePivot
你还可以通过重写 performFill 方法来自定义模型属性的填充方式。