扩展 WordPress REST API 响应:字段、元数据与链接

使用范围:这些片段需要在 WordPress 已加载的插件等合适上下文中使用,分别演示不同注册方式,并不是应全部拼接执行的单一插件。不要重复定义同名函数,或把演示同一字段不同结构的代码同时启用。公开 REST 字段前,应确认数据适合公开,并按业务要求处理读取上下文、写入权限和校验。

WordPress REST API 的默认端点所返回的数据,旨在满足大多数站点和使用场景。不过,你经常仍会需要在各类对象的响应中访问或公开额外数据。

与 WordPress 的其他部分一样,REST API 也具有很强的可扩展性,以满足这些需求。本指南介绍如何使用 register_rest_field 和 register_meta 函数,向默认端点的响应添加额外数据。你可以借助它们,为 REST API 支持的任意对象类型添加字段。这些自定义字段可以同时支持读取和更新操作。

修改响应的重要注意事项

修改或删除核心 REST API 端点响应中的数据,可能破坏插件或 WordPress 核心的行为,应尽可能避免。

API 响应公开了很多字段,其中有些可能不是你所需要的,或者不符合站点的工作方式。虽然你可能想修改或移除这些字段,但这样做会使依赖标准响应的 API 客户端出现问题,包括移动客户端、帮助管理站点的第三方工具,甚至 wp-admin 本身。

你可能只需要少量数据,但要记住:API 是向所有客户端提供接口,而不只是服务于你正在开发的功能。更改现有响应会带来风险。

新增字段不会造成同样的兼容性破坏。因此,如果需要修改数据,更好的方式是新增一个字段,在其中提供修改后的数据。始终不建议移除字段;如果只需要返回较小的数据子集,应使用 _fields 参数,或者利用上下文机制。

如果必须从现有上下文中移除字段,应确保这一行为需要调用方主动选择启用,例如使用自定义查询参数来触发字段移除。

API 无法阻止你修改响应,但其代码结构明确不鼓励这样做。字段注册在内部通过过滤器实现;如果确实没有其他选择,可以利用这些过滤器。

register_rest_field 与 register_meta 的区别

向 WordPress REST API 响应添加数据有两种方法:register_rest_field 和 register_meta。

register_rest_field 可以向任意 REST API 响应添加自定义字段,并通过 API 读写这些字段的数据。注册新的 REST 字段时,需要自行提供读取或设置字段值的回调函数,还要手动指定该字段的模式定义。

register_meta 用于将现有自定义元数据值加入允许通过 REST API 访问的名单。把元数据字段的 show_in_rest 参数设为 true 后,该字段值就会出现在端点响应的 .meta 键下,WordPress 会负责设置读写该元数据键的回调。这比 register_rest_field 简单得多,但有一点需要注意:


在 WordPress 4.9.8 之前,将元数据字段设置为 show_in_rest 并使用 register_meta 注册时,会对指定对象类型中的所有对象生效。如果一种自定义文章类型显示某个元数据字段,其他自定义文章类型也会显示它。从 WordPress 4.9.8 开始,可以为 register_meta 指定 object_subtype 参数,将元数据键限制在特定文章类型中使用。

在 WordPress 5.3 之前,register_meta 只支持标量值(string、integer、number 和 boolean)。WordPress 5.3 新增了对 object 和 array 类型的支持。

向 API 响应添加自定义字段

使用 register_rest_field

register_rest_field 函数是向 REST API 响应对象添加字段最灵活的方式。它接受三个参数:

  1. $object_type:需要注册字段的对象名称,可以是字符串,也可以是对象名称数组。可以是“post”“terms”“meta”“user”或“comment”等核心类型,也可以是自定义文章类型的字符串名称。
  2. $attribute:字段名称,用作响应对象中的键。
  3. $args:一个数组,其中的键用于定义读取字段值的回调(get_callback)、更新字段值的回调(update_callback),以及字段的模式(schema)。

$args 数组中的每个键都是可选的;不提供某个键,就不会添加相应能力。例如,可以只指定读取值的回调,省略更新回调,从而将字段设为只读。

字段应在 rest_api_init 动作中注册。使用此动作而不是 init,可以避免在不使用 REST API 的 WordPress 请求中执行字段注册。

示例

读写评论响应中的额外字段

<?php
add_action( 'rest_api_init', function () {
    register_rest_field( 'comment', 'karma', array(
        'get_callback' => function( $comment_arr ) {
            $comment_obj = get_comment( $comment_arr['id'] );
            return (int) $comment_obj->comment_karma;
        },
        'update_callback' => function( $karma, $comment_obj ) {
            $ret = wp_update_comment( array(
                'comment_ID'    => $comment_obj->comment_ID,
                'comment_karma' => $karma
            ) );
            if ( false === $ret ) {
                return new WP_Error(
                  'rest_comment_karma_failed',
                  __( 'Failed to update comment karma.' ),
                  array( 'status' => 500 )
                );
            }
            return true;
        },
        'schema' => array(
            'description' => __( 'Comment karma.' ),
            'type'        => 'integer'
        ),
    ) );
} );

对象说明:原文紧接示例的解释把对象写成 posts,但代码实际注册的是 comment,本段应按评论响应理解。wp_update_comment() 返回 0 也可能只是数据未改变;原例使用 false === $ret 判断失败,不应随意改为把所有假值都视作失败。详见 函数返回值说明。

此示例向评论响应添加一个名为 karma 的字段。之所以能够实现,是因为 comment_karma 字段已经存在,但核心没有使用它。请注意,真正实现评论 karma 功能时,需要使用单独的端点。

这是一个基础示例。请根据具体字段,认真考虑所需的权限检查和错误处理。

register_rest_field 的工作方式

REST API 基础设施在内部使用全局变量 $wp_rest_additional_fields,保存要添加到各类对象中的响应字段。REST API 提供了 register_rest_field 辅助函数,用于向这个全局变量添加定义。为了保持向前兼容,应避免直接修改该全局变量。

对于每一种对象类型——文章、用户、分类项、评论等——$wp_rest_additional_fields 都包含一个字段定义数组,其中保存了读取或更新字段值的回调。

在 REST API 中使用已注册的元数据

register_meta 函数简化了为特定对象类型定义元数据字段的过程。注册新元数据键时,设置 'show_in_rest' => true,即可通过 REST API 访问该键。

在文章响应中读写文章元数据字段

<?php
// The object type. For custom post types, this is 'post';
// for custom comment types, this is 'comment'. For user meta,
// this is 'user'.
$object_type = 'post';
$meta_args = array( // Validate and sanitize the meta value.
    // Note: currently (4.7) one of 'string', 'boolean', 'integer',
    // 'number' must be used as 'type'. The default is 'string'.
    'type'         => 'string',
    // Shown in the schema for the meta key.
    'description'  => 'A meta key associated with a string meta value.',
    // Return a single value of the type.
    'single'       => true,
    // Show in the WP REST API response. Default: false.
    'show_in_rest' => true,
);
register_meta( $object_type, 'my_meta_key', $meta_args );

此示例展示如何允许读写文章元数据字段。可以通过向 wp-json/wp/v2/posts/<post-id> 发送 POST 请求更新该字段,也可以向 wp-json/wp/v2/posts/ 发送 POST 请求,在创建文章的同时创建该字段。

请注意,为自定义文章类型注册元数据字段时,该文章类型必须支持 custom-fields,否则这些元数据字段不会出现在 REST API 中。

特定文章类型的元数据

WordPress 4.9.8 新增了为特定文章类型或分类法注册元数据的能力,使用的是 register_post_meta 和 register_term_meta 函数。它们遵循与 register_meta 相同的规则,但第一个参数接受文章类型或分类法,而非对象类型。以下代码会注册前述 my_meta_key 示例,不过仅适用于 page 文章类型。

$meta_args = array(
    'type'         => 'string',
    'description'  => 'A meta key associated with a string meta value.',
    'single'       => true,
    'show_in_rest' => true,
);
register_post_meta( 'page', 'my_meta_key', $meta_args );

对象类型元数据

WordPress 5.3 支持在注册元数据时使用 object 类型。这里的 object 指 JSON 对象,相当于 PHP 中的关联数组。

注册 object 元数据时,仅将 type 设为 object 还不够;还需要告诉 WordPress 允许哪些属性。方法是在注册元数据时编写 JSON Schema。

例如,下列代码注册名为 release 的文章元数据字段,并接受所示 JSON 数据。

{
  "meta": {
    "release": {
      "version": "5.2",
      "artist": "Jaco"
    }
  }
}
register_post_meta(
    'post',
    'release',
    array(
        'single'       => true,
        'type'         => 'object',
        'show_in_rest' => array(
            'schema' => array(
                'type'       => 'object',
                'properties' => array(
                    'version' => array(
                        'type' => 'string',
                    ),
                    'artist'  => array(
                        'type' => 'string',
                    ),
                ),
            ),
        ),
    )
);

注意,show_in_rest 此时是数组,而不是 true;同时在 schema 键中指定了 JSON Schema。随后在 properties 数组中列出各个属性。每个属性至少必须指定 type,也可以使用 rest_validate_value_from_schema 所支持的其他 JSON Schema 关键字。

额外属性

默认情况下,属性列表是严格的允许名单。如果请求中包含未列出的属性,REST API 会返回错误:your_property is not a valid property of Object.。如果事先不知道属性名称,可以使用 additionalProperties 关键字。additionalProperties 接受一个 JSON Schema,用于验证未知属性。例如,要要求所有额外属性都为数值,可以使用以下代码。

{
  "meta": {
    "release": {
      "version": "5.2",
      "artist": "Jaco",
      "unknown_field": 5.3
    }
  }
}
register_post_meta(
    'post',
    'version',
    array(
        'single'       => true,
        'type'         => 'object',
        'show_in_rest' => array(
            'schema' => array(
                'type'                 => 'object',
                'properties'           => array(
                    'version' => array(
                        'type' => 'string',
                    ),
                    'artist'  => array(
                        'type' => 'string',
                    ),
                ),
                'additionalProperties' => array(
                    'type' => 'number',
                ),
            ),
        ),
    )
);

校注:原文这里注册的是 version,前面的 JSON 却提交 release。两者需统一;文末“修正示例”给出使用 release 的版本,其他属性模式保持原样。

additionalProperties 可以设置为 true,以允许任意格式的未知属性,但不建议这样做。

数组类型元数据

WordPress 5.3 也支持使用 array 类型。这里的 array 指 JSON 数组,相当于 PHP 中的数字索引数组。

注册 array 元数据时,仅将 type 设为 array 还不够;还需要告诉 WordPress 数组元素的预期格式。方法是在注册元数据时编写相应的 JSON Schema 定义。

如果不提供该值,register_meta 会返回 false,并发出以下警告:When registering an "array" meta type to show in the REST API, you must specify the schema for each array item in "show_in_rest.schema.items".

下列代码注册名为 projects 的文章元数据字段,其中包含项目名称列表。它接受所示 JSON 数据。

{
  "meta": {
    "projects": [
      "WordPress",
      "BuddyPress"
    ]
  }
}
register_post_meta(
    'post',
    'projects',
    array(
        'single'       => true,
        'type'         => 'array',
        'show_in_rest' => array(
            'schema' => array(
                'type'  => 'array',
                'items' => array(
                    'type' => 'string',
                ),
            ),
        ),
    )
);

注意,这里的 show_in_rest 同样改为数组,而不是 true,并为 schema 键指定 JSON Schema。
items 关键字定义了用于验证每个数组成员的 JSON Schema。成员可以是 string 这样的标量类型,也可以是 object 这样的复杂类型。

例如,要接受下面的 JSON 数据,应使用对应的元数据注册代码。

{
  "meta": {
    "projects": [
      {
        "name": "WordPress",
        "website": "https://wordpress.org"
      },
      {
        "name": "BuddyPress",
        "website": "https://buddypress.org"
      }
    ]
  }
}
register_post_meta(
    'post',
    'projects',
    array(
        'single'       => true,
        'type'         => 'array',
        'show_in_rest' => array(
            'schema' => array(
                'items' => array(
                    'type'       => 'object',
                    'properties' => array(
                        'name'    => array(
                            'type' => 'string',
                        ),
                        'website' => array(
                            'type'   => 'string',
                            'format' => 'uri',
                        ),
                    ),
                ),
            ),
        ),
    )
);
array 类型要求数组键为从 0 开始的连续整数。数组会通过 array_values 重新建立索引。

非单值元数据

非单值元数据字段为每个对象保存一组值,而不是单个值。每个值都会存储在元数据表的独立记录中。

array 和 object 类型也可以用于非单值元数据字段。例如,如果前面的 release 元数据键将 single 设为 false,就可以接受以下 JSON 数据。

{
  "meta": {
    "release": [
      {
        "version": "5.2",
        "artist": "Jaco"
      },
      {
        "version": "5.1",
        "artist": "Betty"
      }
    ]
  }
}

这会产生两条元数据数据库记录。第一条包含 { "version": "5.2", "artist": "Jaco" },第二条包含 { "version": "5.1", "artist": "Betty" }。

同样,如果 projects 示例将 single 设为 false,就可以接受以下数据。

{
  "meta": {
    "projects": [
      [
        "WordPress",
        "BuddyPress"
      ],
      [
        "bbPress"
      ]
    ]
  }
}

这会产生两条元数据数据库记录。第一条包含 [ "WordPress", "BuddyPress" ],第二条包含 [ "bbPress" ]。

无效的已存储值

如果元数据字段的已有值未通过注册类型和模式的验证,响应中该字段的值会返回 null。如果更新请求把这个 null 传回 API,将收到 rest_invalid_stored_value 错误:The %s property has an invalid stored value, and cannot be updated to null.。解决方法是用有效值更新该元数据键,或者在请求中省略该属性。

元数据默认值

WordPress 5.5 正式支持在尚未定义元数据值时指定默认值。例如,使用下面的代码后,如果 price 元数据字段尚未有值,REST API 响应会使用 0.00 作为默认值。

register_post_meta(
     'product',
     'price',
     array(
         'single'  => true,
         'type'    => 'string',
         'default' => '0.00',
     )
 );

校注:此原例展示元数据默认值,但没有启用 show_in_rest,单独照抄不会因此公开 REST 字段。文末补充该选项;product 类型本身仍须启用 REST API 并支持 custom-fields。默认值是字符串 0.00,不是新的数据库记录。

WordPress 会生成与当前查询资源相关的链接列表,方便访问关联资源。

{
 "_links": {
    "self": [
      {
        "href": "https://make.wordpress.org/core/wp-json/wp/v2/posts/28312"
      }
    ],
    "collection": [
      {
        "href": "https://make.wordpress.org/core/wp-json/wp/v2/posts"
      }
    ],
    "author": [
      {
        "embeddable": true,
        "href": "https://make.wordpress.org/core/wp-json/wp/v2/users/8670591"
      }
    ],
    "replies": [
      {
        "embeddable": true,
        "href": "https://make.wordpress.org/core/wp-json/wp/v2/comments?post=28312"
      }
    ],
    "wp:term": [
      {
        "taxonomy": "category",
        "embeddable": true,
        "href": "https://make.wordpress.org/core/wp-json/wp/v2/categories?post=28312"
      },
      {
        "taxonomy": "post_tag",
        "embeddable": true,
        "href": "https://make.wordpress.org/core/wp-json/wp/v2/tags?post=28312"
      }
    ]
  }
}

Make.WordPress.org 文章中的链接示例

这些链接虽然出现在 JSON 响应对象的 _links 属性下,但并不存储在 WP_REST_Response::$data 中,也不能通过 WP_REST_Response::get_data() 读取。服务器会在输出响应数据之前,把链接数据附加到响应中。

可以使用 WP_REST_Response::add_link() 方法向响应添加自定义链接。该方法接受三个参数:链接关系、URL,以及可选的链接属性列表。例如,添加 author 和 wp:term 链接:

<?php
$response->add_link( 'author', rest_url( "/wp/v2/users/{$post->post_author}" ) );

$response->add_link( 'https://api.w.org/term', add_query_arg( 'post', $post->ID, rest_url( "/wp/v2/{$tax_base}" ) ) );

链接关系必须是 IANA 已注册的链接关系,或者你控制的 URI。

author 是已注册的链接关系,含义为“当前上下文的作者”;这里用它指向文章作者对应的 WordPress 用户。没有现成的链接关系描述文章关联的分类项,因此 WordPress 使用 https://api.w.org/term,在生成响应时通过 CURIE 转换为 wp:term。

add_link() 的第三个参数是自定义属性列表。embeddable 属性用于允许把链接资源放入响应的 _embedded 部分,前提是请求使用了 _embed 查询参数。如果同一种关系添加了多个链接,嵌入响应的顺序与链接添加顺序一致。

<?php
$response->add_link( 'author', rest_url( "/wp/v2/users/{$post->post_author}" ), array(
    'embeddable' => true,
) );
$response->add_link( 'author', rest_url( "/wp/v2/users/{$additional_author}" ), array(
    'embeddable' => true,
) );

为多作者文章建立链接的示例。

{
  "_links": {
    "author": [
      {
        "embeddable": true,
        "href": "https://yourwebsite.com/wp-json/wp/v2/users/1"
      },
      {
        "embeddable": true,
        "href": "https://yourwebsite.com/wp-json/wp/v2/users/2"
      }
    ]
  },
  "_embedded": {
    "author": [
      {
        "id": 1,
        "name": "Primary Author"
      },
      {
        "id": 2,
        "name": "Secondary Author"
      }
    ]
  }
}

链接的添加顺序会被保留。

注册 CURIE

WordPress 4.5 引入了对紧凑 URI(CURIE)的支持。这样就可以使用简短得多的标识符引用链接,而不必使用可能很长的完整 URL。

通过 rest_response_link_curies 过滤器注册 CURIE,例如:

<?php
function my_plugin_prefix_register_curie( $curies ) {

    $curies[] = array(
        'name'      => 'my_plugin',
        'href'      => 'https://api.mypluginurl.com/{rel}',
        'templated' => true,
    );

    return $curies;
}

校注:这里仅定义回调,尚未将它挂到 rest_response_link_curies。文末给出带钩子注册的版本;两份同名函数只保留一份。

这会在 API 响应中把链接从 https://api.mypluginurl.com/my_link 转换为 my_plugin:my_link。使用 WP_REST_Response::add_link() 时仍须使用完整 URL。

修正示例:统一字段名并补全注册条件

以下三个片段是针对前述问题的单独修正,不属于原文代码。它们保留原接口和数据结构,只修改已指出的差异;PHP 与真实 WordPress 请求的联调仍需在隔离测试站完成。

额外属性:统一使用 release 字段

register_post_meta(
    'post',
    'release',
    array(
        'single'       => true,
        'type'         => 'object',
        'show_in_rest' => array(
            'schema' => array(
                'type'                 => 'object',
                'properties'           => array(
                    'version' => array(
                        'type' => 'string',
                    ),
                    'artist'  => array(
                        'type' => 'string',
                    ),
                ),
                'additionalProperties' => array(
                    'type' => 'number',
                ),
            ),
        ),
    )
);

默认值:同时启用 REST 暴露

register_post_meta(
     'product',
     'price',
     array(
         'single'  => true,
         'type'    => 'string',
         'default' => '0.00',
         'show_in_rest' => true,
     )
 );

CURIE:挂载已定义的回调

<?php
function my_plugin_prefix_register_curie( $curies ) {

    $curies[] = array(
        'name'      => 'my_plugin',
        'href'      => 'https://api.mypluginurl.com/{rel}',
        'templated' => true,
    );

    return $curies;
}

add_filter( 'rest_response_link_curies', 'my_plugin_prefix_register_curie' );

相关接口:元数据注册与公开范围、CURIE 注册钩子。后者还提醒:有些客户端不能正确展开 CURIE,新增缩写可能影响兼容性,应检查消费方。


原文:WordPress Developer Resources:Modifying Responses。作者:WordPress REST API 文档贡献者。原文及示例权利归 WordPress 项目与相应贡献者所有。本版保留完整原文信息与源代码,另列说明和修正示例。

WordPress® 商标为 WordPress Foundation 的知识产权。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容