API 响应

现代 PHP 开发中,构建 API 已成为常见需求,无论是为 JavaScript 驱动的单页应用提供数据,还是作为独立产品。CodeIgniter 提供了若干 Trait,可配合任何控制器使用,以简化常用响应类型的处理,且无需记忆各响应类型对应的 HTTP 状态码。

响应示例

以下示例展示了控制器中的常见用法模式。

<?php

namespace App\Controllers;

use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Controller;

class Users extends Controller
{
    use ResponseTrait;

    public function createUser()
    {
        $model = new UserModel();
        $user  = $model->save($this->request->getPost());

        // Respond with 201 status code
        return $this->respondCreated();
    }
}

此示例返回 HTTP 状态码 201 及通用状态消息 "Created"。该 Trait 为最常见的用例提供了方法:

<?php

// Generic response method
$this->respond($data, 200);

// Generic failure response
$this->fail($errors, 400);

// Item created response
$this->respondCreated($data);

// Item successfully deleted
$this->respondDeleted($data);

// Command executed by no response required
$this->respondNoContent($message);

// Client isn't authorized
$this->failUnauthorized($description);

// Forbidden action
$this->failForbidden($description);

// Resource Not Found
$this->failNotFound($description);

// Data was not validated
$this->failValidationErrors($errors);

// Resource already exists
$this->failResourceExists($description);

// Resource previously deleted
$this->failResourceGone($description);

// Client made too many requests
$this->failTooManyRequests($description);

处理响应类型

调用这些方法传入数据时,系统将根据以下规则确定数据类型以格式化结果:

  • 根据控制器的 $this->format 值确定格式。 若该值为 null,将尝试与客户端请求进行内容协商, 默认使用 app/Config/Format.php$supportedResponseFormats 属性的第一个元素(默认为 JSON)。

  • 数据将按格式进行格式化。若非 JSON 格式且数据为字符串,将视为 HTML 返回给客户端。

备注

在 v4.5.0 之前,由于一个 Bug,若数据为字符串,即使格式为 JSON 也会被视为 HTML。

如需定义所使用的格式化程序,请编辑 app/Config/Format.php$supportedResponseFormats 包含应用可自动格式化的 MIME 类型。系统默认支持 XML 和 JSON 响应格式:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Format extends BaseConfig
{
    public $supportedResponseFormats = [
        'application/json',
        'application/xml',
    ];

    // ...
}

备注

v4.7.0 起,通过编辑 app/Config/Format.php 文件可修改默认 JSON 编码深度。$jsonEncodeDepth 值用于定义最大深度,默认值为 512

进行 内容协商 时,通过该数组确定返回的响应类型。若客户端请求与支持的格式不匹配,则返回数组中的第一个格式。

接下来,需要定义用于格式化数据数组的类。必须是完全限定类名,且该类必须实现 CodeIgniter\Format\FormatterInterface。框架内置支持 JSON 和 XML 的格式化程序:

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Format extends BaseConfig
{
    public $formatters = [
        'application/json' => \CodeIgniter\Format\JSONFormatter::class,
        'application/xml'  => \CodeIgniter\Format\XMLFormatter::class,
    ];

    // ...
}

因此,若请求在 Accept 标头中要求 JSON 格式的数据,传递给任何 respond*fail* 方法的数据数组将由 CodeIgniter\Format\JSONFormatter 类格式化,生成的 JSON 数据将返回给客户端。

类参考

setResponseFormat($format)
参数:
  • $format (string) -- 要返回的响应类型,jsonxml

用于定义响应中数组格式化的方式。若将 $format 设为 null,则通过内容协商自动确定格式。

<?php

return $this->setResponseFormat('json')->respond(['error' => false]);
respond($data[, $statusCode = 200[, $message = '']])
参数:
  • $data (mixed) -- 要返回给客户端的数据,字符串或数组。

  • $statusCode (int) -- 要返回的 HTTP 状态码,默认为 200

  • $message (string) -- 要返回的自定义“原因”消息。

Trait 中的其他方法均通过此方法向客户端返回响应。

$data 参数可以是字符串或数组。默认情况下,字符串作为 HTML 返回,数组则经 json_encode 处理后作为 JSON 返回;除非通过 内容协商 确定了其他返回格式。

若传入 $message 字符串,则会替换响应状态中标准的 IANA 原因短语。但并非所有客户端都支持自定义代码,部分客户端仍会使用与状态码对应的 IANA 标准说明。

备注

由于此方法会设置当前 Response 实例的状态码和正文,因此应始终作为脚本执行过程中的最后一个方法调用。

fail($messages[, int $status = 400[, string $code = null[, string $message = '']]])
参数:
  • $messages (mixed) -- 包含遇到错误消息的字符串或字符串数组。

  • $status (int) -- 要返回的 HTTP 状态码,默认为 400。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

以客户端首选格式返回的 multi-part 响应。

表示失败响应的通用方法,所有其他 "fail" 方法均使用此方法。

$messages 元素可为字符串或字符串数组。

$status 参数为应返回的 HTTP 状态码。

由于许多 API 更适合使用自定义错误码,可在第三个参数中传入自定义错误码。若未提供值,将与 $status 相同。

若传入 $message 字符串,将替代响应状态的标准 IANA 原因码。但并非所有客户端都支持自定义原因码,部分客户端仍会使用与状态码匹配的 IANA 标准。

响应是一个包含三个元素的数组:statuscodemessages

  • status 元素包含错误状态码。

  • code 元素包含 API 专用的自定义错误代码。

  • messages 元素包含错误消息数组。

根据错误消息的数量,响应示例如下:

<?php

// Example response with a single error message
$response = [
    'status'   => 400,
    'code'     => '321',
    'messages' => [
        'error' => 'An error occurred',
    ],
];

// Example response with multiple error messages per field
$response = [
    'status'   => 400,
    'code'     => '321a',
    'messages' => [
        'foo' => 'Error message 1',
        'bar' => 'Error message 2',
    ],
];
respondCreated($data = null[, string $message = ''])
参数:
  • $data (mixed) -- 要返回给客户端的数据,字符串或数组。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

创建新资源时设置相应的状态码,通常为 201:

<?php

$user = $userModel->insert($data);

return $this->respondCreated($user);
respondDeleted($data = null[, string $message = ''])
参数:
  • $data (mixed) -- 要返回给客户端的数据,字符串或数组。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

API 调用成功删除资源时设置相应的状态码,通常为 200。

<?php

$user = $userModel->delete($id);

return $this->respondDeleted(['id' => $id]);
respondNoContent(string $message = 'No Content')
参数:
  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

服务器成功执行命令但无内容返回时,设置相应的状态码,通常为 204:

<?php

sleep(1);

return $this->respondNoContent();
failUnauthorized(string $description = 'Unauthorized'[, string $code = null[, string $message = '']])
参数:
  • $description (string) -- 向用户显示的错误消息。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

未经授权或授权错误时设置相应的状态码,状态码为 401。

<?php

return $this->failUnauthorized('Invalid Auth token');
failForbidden(string $description = 'Forbidden'[, string $code=null[, string $message = '']])
参数:
  • $description (string) -- 向用户显示的错误消息。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

failUnauthorized() 不同,当请求的 API 端点完全禁止访问时,应使用此方法。Unauthorized 即建议客户端更换凭据重试。Forbidden 则表示重试无效,不应再次尝试。状态码为 403。

<?php

return $this->failForbidden('Invalid API endpoint.');
failNotFound(string $description = 'Not Found'[, string $code=null[, string $message = '']])
参数:
  • $description (string) -- 向用户显示的错误消息。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

找不到请求的资源时设置相应的状态码,状态码为 404。

<?php

return $this->failNotFound('User 13 cannot be found.');
failValidationErrors($errors[, string $code=null[, string $message = '']])
参数:
  • $errors (mixed) -- 向用户显示的错误消息或消息数组。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

客户端发送的数据未通过验证规则时设置相应的状态码,通常为 400。

<?php

return $this->failValidationErrors($validation->getErrors());
failResourceExists(string $description = 'Conflict'[, string $code=null[, string $message = '']])
参数:
  • $description (string) -- 向用户显示的错误消息。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

客户端尝试创建的资源已存在时,设置相应的状态码,通常为 409。

<?php

return $this->failResourceExists('A user already exists with that email.');
failResourceGone(string $description = 'Gone'[, string $code=null[, string $message = '']])
参数:
  • $description (string) -- 向用户显示的错误消息。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

请求的资源此前已被删除且不再可用时,设置相应的状态码。状态码通常为 410。

<?php

return $this->failResourceGone('That user has been previously deleted.');
failTooManyRequests(string $description = 'Too Many Requests'[, string $code=null[, string $message = '']])
参数:
  • $description (string) -- 向用户显示的错误消息。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

客户端调用 API 端点次数过多时设置相应的状态码。通常源于流量控制或频率限制。状态码通常为 400。

<?php

return $this->failTooManyRequests('You must wait 15 seconds before making another request.');
failServerError(string $description = 'Internal Server Error'[, string $code = null[, string $message = '']])
参数:
  • $description (string) -- 向用户显示的错误消息。

  • $code (string) -- 自定义 API 专用错误码。

  • $message (string) -- 要返回的自定义“原因”消息。

返回:

Response 对象 send() 方法的返回值。

服务器发生错误时设置相应的状态码。

<?php

return $this->failServerError('Server error.');

分页响应

从 API 端点返回分页结果时,可使用 paginate() 方法同时返回结果与分页信息。这有助于保持 API 响应的一致性,同时提供客户端进行分页所需的全部信息。

使用示例

<?php

use App\Controllers\BaseController;
use App\Models\UserModel;
use CodeIgniter\API\ResponseTrait;

class UserController extends BaseController
{
    use ResponseTrait;

    public function index()
    {
        $model = model(UserModel::class)
            ->where('active', 1);

        return $this->paginate($model, 20);
    }
}

典型响应示例如下:

{
    "data": [
        {
            "id": 1,
            "username": "admin",
            "email": "admin@example.com"
        },
        {
            "id": 2,
            "username": "user",
            "email": "user@example.com"
        }
    ],
    "meta": {
        "page": 1,
        "perPage": 20,
        "total": 2,
        "totalPages": 1
    },
    "links": {
        "self": "http://example.com/users?page=1",
        "first": "http://example.com/users?page=1",
        "last": "http://example.com/users?page=1",
        "next": null,
        "previous": null
    }
}

paginate() 方法始终将结果封装在 data 元素中,并包含 metalinks 元素以辅助客户端翻页。若无结果,data 元素将为空数组,metalinks 元素依然存在,但其值将反映无结果状态。

除了模型,也可传入 Builder 实例,只需确保 Builder 已正确配置表名以及必要的 Join 或 Where 子句。

<?php

use App\Controllers\BaseController;
use CodeIgniter\API\ResponseTrait;

class UserController extends BaseController
{
    use ResponseTrait;

    public function index()
    {
        $builder = db_connect()
            ->table('users')
            ->where('active', 1);

        return $this->paginate(resource: $builder, perPage: 20);
    }
}

类参考

paginate(Model|BaseBuilder $resource, int $perPage = 20, ?string $transformWith = null)
参数:
  • $resource (Model|BaseBuilder) -- 要分页的资源,可为模型或 Builder 实例。

  • $perPage (int) -- 每页返回的条目数。

  • $transformWith (string|null) -- 可选的转换器类名,用于转换结果。

根据给定资源生成分页响应。资源可为模型或 Builder 实例。该方法会自动从请求的查询参数中确定当前页码。响应将包含分页数据、分页状态元数据以及页面导航链接。

如果提供包含转换器类名的 $transformWith 参数,分页结果中的每个条目在返回前都将使用该转换器进行转换。这有助于精确控制 API 响应的结构与内容。有关创建和使用转换器的更多信息,请参阅 API 转换器

带有转换器的示例:

<?php

use App\Controllers\BaseController;
use App\Models\UserModel;
use App\Transformers\UserTransformer;
use CodeIgniter\API\ResponseTrait;

class UserController extends BaseController
{
    use ResponseTrait;

    public function index()
    {
        $model = model(UserModel::class);

        return $this->paginate(resource: $model, perPage: 20, transformWith: UserTransformer::class);
    }
}