API 资源
构建 API 时,通常需要在将数据模型发送给客户端前,将其转换为统一格式。通过 Transformer 实现的 API 资源提供了一种将实体、数组或对象转换为结构化 API 响应的简洁方式。这有助于将内部数据结构与 API 暴露的内容分离,使 API 的维护和演进更加容易。
快速示例
以下示例展示了应用中 Transformer 的常见使用模式。
<?php
namespace App\Transformers;
use CodeIgniter\API\BaseTransformer;
class UserTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'id' => $resource['id'],
'name' => $resource['name'],
'email' => $resource['email'],
'created_at' => $resource['created_at'],
];
}
}
// In your controller
$user = model('UserModel')->find(1);
$transformer = new UserTransformer();
return $this->respond($transformer->transform($user));
在此示例中,UserTransformer 定义了 API 响应中应包含的 User 实体字段。transform() 方法转换单个资源,而 transformMany() 则处理资源集合。
创建 Transformer
若要创建 Transformer,请继承 BaseTransformer 类并实现 toArray() 方法来定义 API 资源结构。toArray() 方法接收被转换的资源作为参数,以便访问并转换其数据。
基础 Transformer
<?php
namespace App\Transformers;
use CodeIgniter\API\BaseTransformer;
class UserTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'id' => $resource['id'],
'username' => $resource['name'], // Renaming the field
'email' => $resource['email'],
'member_since' => date('Y-m-d', strtotime($resource['created_at'])), // Formatting
];
}
}
toArray() 方法接收资源(实体、数组或对象)作为参数,并定义 API 响应的结构。可以根据需要包含资源中的任何字段,也可以重命名或转换其值。
生成 Transformer 文件
CodeIgniter 提供了一个 CLI 命令,可快速生成 Transformer 骨架文件:
php spark make:transformer User
这将在 app/Transformers/User.php 创建一个新的 Transformer 文件,并已生成基础结构。
命令选项
make:transformer 命令支持以下选项:
- --suffix
在类名后添加 “Transformer”:
php spark make:transformer User --suffix将创建 app/Transformers/UserTransformer.php
- --namespace
指定自定义根命名空间:
php spark make:transformer User --namespace="MyCompany\\API"- --force
强制覆盖现有文件:
php spark make:transformer User --force
子目录
通过在名称中包含路径,可以将 Transformer 组织到子目录中:
php spark make:transformer api/v1/User
这将创建 app/Transformers/Api/V1/User.php,并带有相应的命名空间 App\Transformers\Api\V1。
在控制器中使用 Transformer
创建 Transformer 后,即可在控制器中转换数据,然后将其返回给客户端。
<?php
namespace App\Controllers;
use App\Transformers\UserTransformer;
use CodeIgniter\API\ResponseTrait;
class Users extends BaseController
{
use ResponseTrait;
public function show($id)
{
$user = model('UserModel')->find($id);
if (! $user) {
return $this->failNotFound('User not found');
}
$transformer = new UserTransformer();
return $this->respond($transformer->transform($user));
}
public function index()
{
$users = model('UserModel')->findAll();
$transformer = new UserTransformer();
return $this->respond($transformer->transformMany($users));
}
}
字段筛选
Transformer 通过当前 URL 的 fields 查询参数自动支持字段筛选。这允许 API 客户端仅请求所需的特定字段,从而节省带宽并提高性能。
<?php
namespace App\Transformers;
use CodeIgniter\API\BaseTransformer;
class UserTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'id' => $resource['id'],
'name' => $resource['name'],
'email' => $resource['email'],
'created_at' => $resource['created_at'],
'updated_at' => $resource['updated_at'],
];
}
}
// Request: GET /users/1?fields=id,name
// Response: {"id": 1, "name": "John Doe"}
若请求 /users/1?fields=id,name,将仅返回:
{
"id": 1,
"name": "John Doe"
}
限制可用字段
默认情况下,客户端可以请求 toArray() 方法中定义的任何字段。可通过重写 getAllowedFields() 方法来限制允许的字段:
<?php
namespace App\Transformers;
use CodeIgniter\API\BaseTransformer;
class UserTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'id' => $resource['id'],
'name' => $resource['name'],
'email' => $resource['email'],
'created_at' => $resource['created_at'],
'updated_at' => $resource['updated_at'],
];
}
protected function getAllowedFields(): ?array
{
// Only these fields can be requested
return ['id', 'name', 'created_at'];
}
}
此时,即使客户端请求 /users/1?fields=email,也会抛出 ApiException,因为 email 不在允许的字段列表中。
包含关联资源
Transformer 支持通过 include 查询参数加载关联资源。这遵循了常见的 API 模式,即客户端可以指定需要包含哪些关联关系。虽然关联关系是最常见的用例,但也可以通过定义自定义 include 方法来包含任何额外数据。
定义 include 方法
若要支持包含关联资源,请创建以 include 为前缀、后跟资源名称的方法。在这些方法中,可以通过 $this->resource 访问当前正在转换的资源:
<?php
namespace App\Transformers;
use CodeIgniter\API\BaseTransformer;
class UserTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'id' => $resource['id'],
'name' => $resource['name'],
'email' => $resource['email'],
];
}
protected function includePosts(): array
{
// Use $this->resource to access the current resource being transformed
$posts = model('PostModel')->where('user_id', $this->resource['id'])->findAll();
return (new PostTransformer())->transformMany($posts);
}
protected function includeComments(): array
{
$comments = model('CommentModel')->where('user_id', $this->resource['id'])->findAll();
return (new CommentTransformer())->transformMany($comments);
}
}
请注意 include 方法如何使用 $this->resource['id'] 访问被转换用户的 ID。调用 transform() 时,Transformer 会自动设置 $this->resource 属性。
客户端现在可以请求:/users/1?include=posts,comments
响应将包含:
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"posts": [
{
"id": 1,
"title": "First Post"
}
],
"comments": [
{
"id": 1,
"content": "Great article!"
}
]
}
限制可用 include
与字段筛选类似,可以通过重写 getAllowedIncludes() 方法来限制可包含的关联关系:
<?php
namespace App\Transformers;
use App\Transformers\CommentTransformer;
use App\Transformers\PostTransformer;
use CodeIgniter\API\BaseTransformer;
class UserTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'id' => $resource['id'],
'name' => $resource['name'],
'email' => $resource['email'],
];
}
protected function getAllowedIncludes(): ?array
{
// Only these relationships can be included
return ['posts', 'comments'];
}
protected function includePosts(): array
{
$posts = model('PostModel')->where('user_id', $this->resource['id'])->findAll();
return (new PostTransformer())->transformMany($posts);
}
protected function includeComments(): array
{
$comments = model('CommentModel')->where('user_id', $this->resource['id'])->findAll();
return (new CommentTransformer())->transformMany($comments);
}
protected function includeOrders(): array
{
// This method exists but won't be callable from the API
// because 'orders' is not in getAllowedIncludes()
$orders = model('OrderModel')->where('user_id', $this->resource['id'])->findAll();
return (new OrderTransformer())->transformMany($orders);
}
}
如果想禁用所有 include,请返回一个空数组:
<?php
namespace App\Transformers;
use CodeIgniter\API\BaseTransformer;
class UserTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'id' => $resource['id'],
'name' => $resource['name'],
'email' => $resource['email'],
];
}
protected function getAllowedIncludes(): ?array
{
// Return empty array to disable all includes
return [];
}
}
include 验证
Transformer 会自动验证所有请求的 include 是否在 Transformer 类中定义了对应的 include*() 方法。如果客户端请求了不存在的 include,将抛出 ApiException。
例如,如果客户端请求:
GET /api/users?include=invalid
而 Transformer 中没有 includeInvalid() 方法,则会抛出异常,提示:“Missing include method for: invalid”。
这有助于捕获拼写错误并防止非预期行为。
转换集合
使用 transformMany() 方法可轻松转换资源数组:
<?php
namespace App\Controllers;
use App\Transformers\UserTransformer;
use CodeIgniter\API\ResponseTrait;
class Users extends BaseController
{
use ResponseTrait;
public function index()
{
$users = model('UserModel')->findAll();
$transformer = new UserTransformer();
$data = $transformer->transformMany($users);
return $this->respond($data);
}
}
transformMany() 方法会对集合中的每一项应用相同的转换逻辑,包括请求中指定的任何字段筛选或 include。
处理不同的数据类型
Transformer 不仅能处理实体,还能处理各种数据类型。
转换实体
将 Entity 实例传递给 transform() 时,它会自动调用实体的 toArray() 方法获取数据:
<?php
use App\Entities\User;
use App\Transformers\UserTransformer;
$user = new User([
'id' => 1,
'name' => 'John Doe',
'email' => 'john@example.com',
]);
$transformer = new UserTransformer();
$result = $transformer->transform($user);
转换数组
也可以转换普通数组:
<?php
use App\Transformers\UserTransformer;
$userData = [
'id' => 1,
'name' => 'John Doe',
'email' => 'john@example.com',
];
$transformer = new UserTransformer();
$result = $transformer->transform($userData);
转换对象
任何对象都可以转换为数组并进行转换:
<?php
use App\Transformers\UserTransformer;
$user = new \stdClass();
$user->id = 1;
$user->name = 'John Doe';
$user->email = 'john@example.com';
$transformer = new UserTransformer();
$result = $transformer->transform($user);
仅使用 toArray()
如果未向 transform() 传递资源,它将使用来自 toArray() 方法的数据:
<?php
namespace App\Transformers;
use CodeIgniter\API\BaseTransformer;
class StaticDataTransformer extends BaseTransformer
{
public function toArray(mixed $resource): array
{
return [
'version' => '1.0',
'status' => 'active',
'message' => 'API is running',
];
}
}
// Usage
$transformer = new StaticDataTransformer();
$result = $transformer->transform(null); // No resource passed
类参考
- class CodeIgniter\API\BaseTransformer
- __construct(?IncomingRequest $request = null)
- 参数:
$request (
IncomingRequest|null) -- 可选的请求实例。若未提供,将使用全局请求。
初始化 Transformer,并从请求中提取
fields和include查询参数。
- toArray(mixed $resource)
- 参数:
$resource (
mixed) -- 正在转换的资源(实体、数组、对象或 null)
- 返回:
资源的数组表示
- 返回类型:
array
此抽象方法必须由子类实现,用以定义 API 资源的结构。resource 参数包含正在转换的数据。返回一个包含要在 API 响应中显示的字段的数组,并从
$resource参数中获取数据。<?php namespace App\Transformers; use CodeIgniter\API\BaseTransformer; class ProductTransformer extends BaseTransformer { public function toArray(mixed $resource): array { return [ 'id' => $resource['id'], 'name' => $resource['name'], 'price' => $resource['price'], 'in_stock' => $resource['stock_quantity'] > 0, 'description' => $resource['description'], ]; } }
- transform($resource = null)
- 参数:
$resource (
mixed) -- 要转换的资源(实体、数组、对象或 null)
- 返回:
转换后的数组
- 返回类型:
array
通过调用包含资源数据的
toArray(),将给定资源转换为数组。如果$resource为null,则向toArray()传递null。如果是实体,则先提取其数组表示;否则将其强制转换为数组。资源还会存储在
$this->resource中,以便 include 方法访问。该方法会自动根据查询参数应用字段筛选和 include。
<?php use App\Transformers\UserTransformer; $user = model('UserModel')->find(1); $transformer = new UserTransformer(); // Transform an entity $result = $transformer->transform($user); // Transform an array $userData = ['id' => 1, 'name' => 'John Doe']; $result = $transformer->transform($userData); // Use toArray() data $result = $transformer->transform();
- transformMany(array $resources)
- 参数:
$resources (
array) -- 要转换的资源数组
- 返回:
转换后的资源数组
- 返回类型:
array
通过对每一项调用
transform()来转换资源集合。字段筛选和 include 会一致地应用到所有项。<?php use App\Transformers\UserTransformer; $users = model('UserModel')->findAll(); $transformer = new UserTransformer(); $results = $transformer->transformMany($users); // $results is an array of transformed user arrays foreach ($results as $user) { // Each $user is the result of calling transform() on an individual user }
- getAllowedFields()
- 返回:
允许的字段名数组,或返回
null以允许所有字段- 返回类型:
array|null
重写此方法以限制可通过
fields查询参数请求的字段。返回null(默认值)以允许toArray()中的所有字段。返回字段名数组以创建允许字段的白名单。<?php namespace App\Transformers; use CodeIgniter\API\BaseTransformer; class UserTransformer extends BaseTransformer { public function toArray(mixed $resource): array { return [ 'id' => $resource['id'], 'name' => $resource['name'], 'email' => $resource['email'], 'created_at' => $resource['created_at'], ]; } protected function getAllowedFields(): ?array { // Clients can only request id, name, and created_at // Attempting to request 'email' will throw an ApiException return ['id', 'name', 'created_at']; } }
- getAllowedIncludes()
- 返回:
允许的 include 名称数组,或返回
null以允许所有 include- 返回类型:
array|null
重写此方法以限制可通过
include查询参数包含的关联资源。返回null(默认值)以允许所有具有对应方法的 include。返回 include 名称数组以创建白名单。返回空数组则禁用所有 include。<?php namespace App\Transformers; use CodeIgniter\API\BaseTransformer; class UserTransformer extends BaseTransformer { public function toArray(mixed $resource): array { return [ 'id' => $resource['id'], 'name' => $resource['name'], 'email' => $resource['email'], ]; } protected function getAllowedIncludes(): ?array { // Only 'posts' can be included via ?include=posts // Attempting to include 'orders' will throw an ApiException return ['posts']; } protected function includePosts(): array { $posts = model('PostModel')->where('user_id', $this->resource['id'])->findAll(); return (new PostTransformer())->transformMany($posts); } protected function includeOrders(): array { // This method exists but cannot be called via the API $orders = model('OrderModel')->where('user_id', $this->resource['id'])->findAll(); return (new OrderTransformer())->transformMany($orders); } }
异常参考
- class CodeIgniter\API\ApiException
- static forInvalidFields(string $field)
- 参数:
$field (
string) -- 无效的字段名
- 返回:
ApiException 实例
- 返回类型:
当客户端通过
fields查询参数请求了不在允许字段列表中的字段时抛出。
- static forInvalidIncludes(string $include)
- 参数:
$include (
string) -- 无效的 include 名称
- 返回:
ApiException 实例
- 返回类型:
当客户端通过
include查询参数请求了不在允许 include 列表中的内容时抛出。
- static forMissingInclude(string $include)
- 参数:
$include (
string) -- 缺失的 include 方法名
- 返回:
ApiException 实例
- 返回类型:
当客户端通过
include查询参数请求了某个 include,但 Transformer 类中不存在对应的include*()方法时抛出。此验证确保所有请求的 include 都有定义的处理方法。