编写 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 骨架:

Laravel REST API

然后,对于 API 接口而言,创建资源表单页面和编辑资源表单页面无需额外的路由,所以我们需要在资源控制器 app/Http/Controllers/TaskController.php 中将对应的 create 方法和 edit 方法删除,同时在注册资源路由的时候将这两个路由排除:

Route::resource('tasks', 'TaskController', ['except' => ['create', 'edit']]);

这样,最终的 API 路由信息如下:

Laravel REST 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 页面添加几个新任务:

Laravel Todo App

然后通过 http://todo.test/api/tasks 即可获取 JSON 格式的任务列表:

Laravel JSON API

通过 http://todo.test/api/tasks/1 指定具体的任务 ID,也可以获取到对应的 JSON 格式的单个任务数据:

Laravel JSON API

可见,在 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 层将数据包裹在里面:

Laravel JSON API

如果想要自定义返回的字段和数据,可以自定义该资源类中的 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 接口返回数据就变成这样了:

Laravel JSON API

如果想要在资源类中附带关联模型,也很简单,在查询的时候通过渴求式查询带上要关联的模型:

public function show($id)
{
    return new \App\Http\Resources\Task(Task::with(['user' => function($query) {
        $query->select('id', 'name');
    }])->find($id));
}

然后在 Task 资源类的 toArray 方法中带上这个关联模型对应字段即可:

Laravel JSON API

资源集合

如果要同时返回多个模型实例,比如资源控制器中的 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);
}

这个时候返回响应的数据格式如下:

Laravel JSON API

每个模型实例中都包含一个 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 JSON API

分页数据

除此之外,返回分页数据也是一种很常见的场景,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 JSON API

是不是很方便?好了,关于资源类和资源集合类学院君就简单介绍到这里,更多使用方法,请参考官方文档

以上就是在 Laravel 框架中基于资源控制器和 API 资源类快速构建 JSON API 接口的入门教程,相关代码已经推送到 Github 代码仓库,希望看完这篇教程,你可以快速上手 Laravel API 接口编写。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 编写 JSON API —— RESTful 风格 API 设计原则与最佳实践

>> 下一篇: Laravel API 系列教程(一): 基于 Laravel 5.5 构建 & 测试 RESTful API