模型
设置控制器
要通过 API 暴露一个模型,首先需要为它创建一个控制器。正如你在快速上手 - 简单 CRUD一节中所看到的,定义模型控制器非常简单。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
}
禁用分页
默认情况下,index 端点返回的资源会进行分页。要禁用分页并返回全部资源,可以使用 DisablePagination trait。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
use Orion\Concerns\DisablePagination;
class PostsController extends Controller
{
use DisablePagination;
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
}
- 模型控制器始终继承
Orion\Http\Controllers\Controller $model属性设置为完全限定的模型类名- 使用
DisablePaginationtrait 可以同时为模型资源和关联资源禁用分页
设置路由
控制器创建完成后,就可以注册路由了。
<?php
use Illuminate\Support\Facades\Route;
use Orion\Facades\Orion;
use App\Http\Controllers\PostsController;
Route::group(['as' => 'api.'], function() {
...
Orion::resource('posts', PostsController::class);
...
});
本质上,Orion::resource 方法与 Laravel 默认的 Route::apiResource 相同——它会创建多条路由,用于处理针对资源的各种操作。
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
...
| | GET|HEAD | api/posts | api.posts.index | App\Http\Controllers\Api\PostsController@index | api |
| | POST | api/posts/search | api.posts.search | App\Http\Controllers\Api\PostsController@index | api |
| | POST | api/posts | api.posts.store | App\Http\Controllers\Api\PostsController@store | api |
| | GET|HEAD | api/posts/{post} | api.posts.show | App\Http\Controllers\Api\PostsController@show | api |
| | PUT|PATCH | api/posts/{post} | api.posts.update | App\Http\Controllers\Api\PostsController@update | api |
| | DELETE | api/posts/{post} | api.posts.destroy | App\Http\Controllers\Api\PostsController@destroy | api |
| | POST | api/posts/batch | api.posts.batchStore | App\Http\Controllers\Api\PostsController@batchStore | api |
| | PATCH | api/posts/batch | api.posts.batchUpdate | App\Http\Controllers\Api\PostsController@batchUpdate | api |
| | DELETE | api/posts/batch | api.posts.batchDestroy | App\Http\Controllers\Api\PostsController@batchDestroy | api |
软删除
如果你的模型使用了 SoftDeletes trait,并且希望通过 API 暴露相同的功能,请在注册资源时调用 withSoftDeletes 方法。
<?php
use Illuminate\Support\Facades\Route;
use Orion\Facades\Orion;
Route::group(['as' => 'api.'], function() {
...
Orion::resource('posts', 'Api\PostsController')->withSoftDeletes();
...
});
这会引入 restore 和 batchRestore 端点。要了解如何通过 API 永久删除资源(强制删除),请参阅相关的查询参数一节。
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+-------------------------------------------------+----------------------------------------+---------------------------------------------------------------------------+-------------------------------------------------+
...
| | POST | api/posts/{post}/restore | api.posts.restore | App\Http\Controllers\Api\PostsController@restore | api |
| | POST | api/posts/batch/restore | api.posts.batchRestore | App\Http\Controllers\Api\PostsController@batchRestore | api |
自定义键名
默认情况下,端点使用主键(通常是 id)从数据库中检索模型。但在某些情况下,你可能希望在保持主键不变的同时,使用其他字段来获取模型。为此,请在控制器上重写 keyName 方法:
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
/**
* 用于从数据库中获取资源的字段名称。
*
* @return string
*/
protected function keyName(): string
{
return 'slug';
}
}
自定义查询
Orion 非常灵活,允许你重新定义每个端点的 Eloquent 查询是如何构建和运行的。
针对单个端点
构建查询
假设你希望 index 端点只返回已发布的博客文章。为此,请在控制器上重写 buildIndexFetchQuery 方法:
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
/**
* 构建用于在 index 方法中获取实体的 Eloquent 查询。
*
* @param Request $request
* @param array $requestedRelations
* @return Builder
*/
protected function buildIndexFetchQuery(Request $request, array $requestedRelations): Builder
{
$query = parent::buildIndexFetchQuery($request, $requestedRelations);
$query->whereNotNull('published_at');
return $query;
}
}
运行查询
一个常见的例子是在获取模型列表时只选取特定的列。为此,请重写 runIndexFetchQuery 方法:
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
...
/**
* 运行给定的查询以在 index 方法中获取实体。
*
* @param Request $request
* @param Builder $query
* @param int $paginationLimit
* @return LengthAwarePaginator
*/
protected function runIndexFetchQuery(Request $request, Builder $query, int $paginationLimit): LengthAwarePaginator
{
return $query->paginate($paginationLimit, ['id', 'title', 'published_at']);
}
}
执行操作
index、show 等端点的主要目的是从数据库中检索数据,而不是修改数据。但 store、update 等端点会对数据库进行更改。你同样可以自定义某些操作(例如存储模型)的执行方式。
在下面的示例中,如果当前认证用户是管理员(这里的角色实现是虚构的),我们会对文章的属性进行强制填充:
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
...
/**
* 填充给定实体的属性并将其存储到数据库中。
*
* @param Request $request
* @param Model $entity
* @param array $attributes
*/
protected function performStore(Request $request, Model $entity, array $attributes): void
{
if ($this->resolveUser()->hasRole('admin')) {
$entity->forceFill($attributes);
} else {
$entity->fill($attributes);
}
$entity->save();
}
}
performFill 方法来自定义属性在模型上的填充方式。针对一组端点
很好,现在 index 端点只返回已发布的文章。但如果你希望对 show 端点也应用相同的约束呢?当然,我们可以复制这段逻辑并重写 buildShowFetchQuery 方法,但还有更好的办法。
构建查询
如果你查看 buildIndexFetchQuery 方法的实现,会注意到它使用了 buildFetchQuery 方法。实际上,index、show、update、destroy 和 restore 端点都使用这个方法来构建获取模型的查询,而你同样可以重写它!
如果你希望同时对 index 和 show 端点(以及 update、destroy 和 restore)应用相同的约束,代码如下:
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
/**
* 构建用于获取实体的 Eloquent 查询。
*
* @param Request $request
* @param array $requestedRelations
* @return Builder
*/
protected function buildFetchQuery(Request $request, array $requestedRelations): Builder
{
$query = parent::buildFetchQuery($request, $requestedRelations);
$query->whereNotNull('published_at');
return $query;
}
...
}
运行查询
在运行查询方面,index 端点算是一个例外(因为它获取的是模型列表,而不是单个模型),而 show、update、destroy 和 restore 端点共享相同的逻辑,底层都使用 runFetchQuery。
稍加重构后,我们就得到了这样一个控制器:只获取已发布的博客文章,并且只从数据库中检索 id、title 和 published_at 这几个列(属性)。
<?php
namespace App\Http\Controllers\Api;
use App\Models\Post;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
/**
* 模型的完全限定类名
*/
protected $model = Post::class; // 或 "App\Models\Post"
/**
* 要从数据库中选择的属性列表
*/
protected $attributes = ['id', 'title', 'published_at'];
/**
* 构建用于获取实体的 Eloquent 查询。
*
* @param Request $request
* @param array $requestedRelations
* @return Builder
*/
protected function buildFetchQuery(Request $request, array $requestedRelations): Builder
{
$query = parent::buildFetchQuery($request, $requestedRelations);
$query->whereNotNull('published_at');
return $query;
}
/**
* 运行给定的查询以获取实体。
*
* @param Request $request
* @param Builder $query
* @param int|string $key
* @return Model
*/
protected function runFetchQuery(Request $request, Builder $query, $key): Model
{
return $query->select($this->attributes)->findOrFail($key);
}
/**
* 运行给定的查询以在 index 方法中获取实体。
*
* @param Request $request
* @param Builder $query
* @param int $paginationLimit
* @return LengthAwarePaginator
*/
protected function runIndexFetchQuery(Request $request, Builder $query, int $paginationLimit): LengthAwarePaginator
{
return $query->paginate($paginationLimit, $this->attributes);
}
/**
* 填充给定实体的属性并将其存储到数据库中。
*
* @param Request $request
* @param Model $post
* @param array $attributes
*/
protected function performStore(Request $request, Model $post, array $attributes): void
{
if ($this->resolveUser()->hasRole('admin')) {
$post->forceFill($attributes);
} else {
$post->fill($attributes);
}
$post->save();
}
}
标准操作方法
| 方法 | 构建 | 运行 | 执行 |
|---|---|---|---|
| index | buildIndexFetchQuery | runIndexFetchQuery | - |
| store | buildStoreFetchQuery | runStoreFetchQuery | performStore |
| show | buildShowFetchQuery | runShowFetchQuery | - |
| update | buildUpdateFetchQuery | runUpdateFetchQuery | performUpdate |
| destroy | buildDestroyFetchQuery | runDestroyFetchQuery | performDestroy |
| restore | buildRestoreFetchQuery | runRestoreFetchQuery | performRestore |