编写 JSON API —— 基于资源控制器和 API 资源类快速构建 API 接口
在上篇教程中学院君给大家简单介绍 RESTful 风格 API 的设计原则和最佳实践,接下来,我们将在 Laravel 项目中通过实例来演示如何编写遵循 RESTful 风格的 JSON API 接口。
注册遵循 REST 规范的 API 路由
其实我们之前在待办任务项目中已经将这样的 API 接口的骨架都搭建起来了,Laravel 框架对 RESTful 风格 API 支持非常友好,可以通过框架内置的资源控制器结合资源路由方法快速构建起遵循 REST 规范的 API 路由,这只需要两步操作即可办到:
- 首先通过
php artisan make:controller TaskController --resource
创建资源控制器; - 然后在路由文件
routes/api.php
中通过Route::resource('tasks', 'TaskController');
注册映射上述资源控制器的 API 路由。
这样,我们就构建起了遵循 REST 规范的 API 骨架:
然后,对于 API 接口而言,创建资源表单页面和编辑资源表单页面无需额外的路由,所以我们需要在资源控制器 app/Http/Controllers/TaskController.php
中将对应的 create
方法和 edit
方法删除,同时在注册资源路由的时候将这两个路由排除:
Route::resource('tasks', 'TaskController', ['except' => ['create', 'edit']]);
这样,最终的 API 路由信息如下:
返回 JSON 响应
继续后面的操作之前,假设你之前已经编写好了待办任务项目(如果没有的话就从 Github 上将该项目克隆到本地并进行初始化,该项目的 Github 地址是:https://github.com/nonfu/todoapp),为相应的业务逻辑准备好了模型和数据库,并且编译过前端资源,可以在前端页面对任务数据进行增删改查操作。
此外,Laravel 框架对 JSON 格式的 HTTP 响应支持也非常有好,如果响应返回的数据格式是数组、集合或则模型实例,Laravel 默认都会将其转化为 JSON 响应,而不需要你手动调用 json_encode
函数进行显式转化(对应的底层处理逻辑可以参考 Laravel 响应类 Response 剖析这篇教程)。此外,如果想要对返回的资源数据格式进行更多的自定义,还可以借助 API 资源类对模型数据进行转化再返回 JSON 响应。
构建简单的 JSON 响应
接下来,我们不借助 API 资源类完成一些简单的 JSON 响应业务代码编写,关于新增、更新和删除操作,之前在待办任务项目中已经编写过相应的代码了,我们只需要编写获取任务列表和任务详情 API 接口数据的代码即可:
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return Task::where('user_id', auth('api')->user()->id)->get();
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
return Task::findOrFail($id);
}
然后我们注册一个新用户登录后跳转到 http://todo.test/home
页面添加几个新任务:
然后通过 http://todo.test/api/tasks
即可获取 JSON 格式的任务列表:
通过 http://todo.test/api/tasks/1
指定具体的任务 ID,也可以获取到对应的 JSON 格式的单个任务数据:
可见,在 Laravel API 接口中,返回 JSON 格式响应数据是非常方便的,不管是数据库查询结果,还是 Eloquent 模型实例,还是集合数据,以及数组和其它对象实例,都会被自动转化为 JSON 格式数据。
基于 API 资源类构建自定义格式的 JSON 响应
资源类
如官方文档所言,一个 API 资源类表示一个单独的需要被转化为 JSON 数据结构的模型,而且使用 API 资源类的时候,往往是需要对某些模型字段的转化进行自定义的情况下,比如上面的示例中,我们可以对返回某个任务实例的 show
方法使用 API 资源类将模型实例转化为 JSON 格式数据。
使用 API 资源类之前,需要先为对应模型类创建资源类:
php artisan make:resource Task
该命令会在 app/Http/Resources
目录下生成 Task.php
资源类,然后我们可以将资源控制器的 show
方法改写如下:
public function show($id)
{
return new \App\Http\Resources\Task(Task::find($id));
}
这种方式返回的数据和之前差不多,只是多了一个 data 层将数据包裹在里面:
如果想要自定义返回的字段和数据,可以自定义该资源类中的 toArray
方法,当我们将调用资源类转化为 JSON 数据时,调用的正是该方法:
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'text' => $this->text,
'completed' => $this->is_completed ? 'yes' : 'no'
];
}
我们可以在 $this
实例上调用模型类实例的任何字段,原理是资源类底层会通过魔术方法 __get
将其转化为调用实例化资源类时传入的模型实例上的对应字段。这样一来,对应的 API 接口返回数据就变成这样了:
如果想要在资源类中附带关联模型,也很简单,在查询的时候通过渴求式查询带上要关联的模型:
public function show($id)
{
return new \App\Http\Resources\Task(Task::with(['user' => function($query) {
$query->select('id', 'name');
}])->find($id));
}
然后在 Task
资源类的 toArray
方法中带上这个关联模型对应字段即可:
资源集合
如果要同时返回多个模型实例,比如资源控制器中的 index
方法就是这种场景,显然上面这个调用方式不适用,好在资源类中还提供了一个 collection
方法,通过该方法即可包裹模型实例集合以便应用资源类的 toArray
方法对返回数据格式进行自定义:
public function index()
{
$tasks = Task::where('user_id', auth('api')->user()->id)->with('user')->get();
return \App\Http\Resources\Task::collection($tasks);
}
这个时候返回响应的数据格式如下:
每个模型实例中都包含一个 user
字段,而对同一个用户来说,不同的任务对应的用户信息都是一致的,因此,需要对这个返回数据格式进行优化,此时,借助资源类提供的 collection
方法就不能满足我们的需求了,我们需要创建额外的资源集合类来对返回的模型集合数据格式进行定义:
php artisan make:resource TaskCollection
编辑新生成的资源集合类 app/Http/Resources/TaskCollection.php
代码如下:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TaskCollection extends ResourceCollection
{
public $collects = \App\Task::class;
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'tasks' => $this->collection->map(function ($task) {
return $task->only(['id', 'text', 'is_completed']);
}),
'user' => $this->collection->first()->user->only(['id', 'name']),
];
}
}
默认情况下,如果资源类存在的话,资源集合类的 $collects
属性值是对应的资源类,比如这里默认是 App\Http\Resources\Task
,如果你不想复用这个资源类的转化逻辑,可以将其设置为对应模型类进行覆盖,比如我这里就是这么做的,此外,在 toArray
方法中,我还将任务数据和用户数据分隔开返回。这样,再次访问 http://todo.test/api/tasks
,返回数据格式如下:
分页数据
除此之外,返回分页数据也是一种很常见的场景,Laravel 框架默认对资源集合的分页数据格式化提供了友好的支持。还是以 index
方法为例,我们将该方法改写如下已支持对任务进行分页显示(每页显示10个任务):
public function index()
{
$tasks = Task::where('user_id', auth('api')->user()->id)->with('user')->paginate(10);
return new TaskCollection($tasks);
}
然后,我们不需要对资源集合类 TaskCollection
做任何修改,Laravel 框架底层会自动为我们在响应数据中加上分页数据:
是不是很方便?好了,关于资源类和资源集合类学院君就简单介绍到这里,更多使用方法,请参考官方文档。
以上就是在 Laravel 框架中基于资源控制器和 API 资源类快速构建 JSON API 接口的入门教程,相关代码已经推送到 Github 代码仓库,希望看完这篇教程,你可以快速上手 Laravel API 接口编写。
1 Comment
如果想在响应中添加code,msg等常见的字段信息,怎样处理更好呢?