WordPress 自定义 REST 字段注册:register_rest_field
函数详解
大家好,今天我们深入探讨 WordPress REST API 的一个关键函数:register_rest_field
。 这个函数是扩展 WordPress REST API 功能的基石,允许我们向标准 WordPress 对象(如文章、页面、用户等)添加自定义字段,从而更好地满足我们应用程序的需求。
1. register_rest_field
函数的基本结构与参数
register_rest_field
函数用于注册一个自定义的 REST API 字段。 它的基本结构如下:
register_rest_field(
string $object_type,
string $attribute,
array $args
);
让我们逐一分析这些参数:
-
$object_type
(string): 指定你想要添加自定义字段的对象类型。 它可以是 WordPress 内置的对象类型,例如'post'
,'page'
,'user'
,'comment'
,'term'
, 也可以是自定义的 post type 或 taxonomy。 -
$attribute
(string): 定义自定义字段的名称。这个名称将成为 REST API 响应中的键。 建议使用有意义且易于理解的名称。 -
$args
(array): 一个包含用于定义字段行为的参数数组。这个数组至关重要,它控制了字段的获取、更新和显示方式。$args
数组的常见键如下:-
get_callback
(callable): 一个回调函数,用于获取字段的值。 这个函数应该接受三个参数:$object
(array|object): 当前正在处理的对象。 例如,如果是文章,则$object
将是文章对象。$field_name
(string): 字段的名称 ($attribute
)。$request
(WP_REST_Request): REST API 请求对象。
get_callback
函数必须返回字段的值。 如果字段不存在或无法获取,可以返回null
。 -
update_callback
(callable): 一个回调函数,用于更新字段的值。 这个函数应该接受三个参数:$value
(mixed): 要设置的字段的新值。$object
(array|object): 当前正在处理的对象。$field_name
(string): 字段的名称 ($attribute
)。
update_callback
函数应该返回true
如果更新成功,否则返回false
。 如果不需要更新功能,可以省略此参数,或者设置为null
或false
。 -
schema
(array): 一个描述字段数据类型的 schema 数组。 这对于向 REST API 客户端提供有关字段的元数据非常有用。 schema 遵循 JSON Schema 规范。 例如,你可以指定字段的类型 ('string'
,'integer'
,'boolean'
,'array'
,'object'
)、描述、是否只读等等。 如果省略此参数,WordPress 将尝试根据get_callback
函数的返回值自动推断 schema,但这可能并不总是准确。 -
show_in_rest
(boolean|array): 用于控制是否以及如何将该自定义字段显示在 REST API 响应中。 默认为true
,表示字段将始终显示。如果设置为false
,则该字段不会显示。如果设置为一个数组,则可以指定哪些 endpoint 中显示此字段。 例如,['edit' => true, 'view' => false]
表示只在编辑 endpoint 中显示。 在 WordPress 5.0 之后引入。
-
2. 注册自定义字段的实践案例
让我们通过一些实际的例子来了解如何使用 register_rest_field
。
案例 1: 为文章添加自定义 "阅读时长" 字段
假设我们想为每篇文章添加一个 "阅读时长" 字段,以分钟为单位。 我们可以在主题的 functions.php
文件或自定义插件中添加以下代码:
function register_read_time_field() {
register_rest_field(
'post',
'read_time',
array(
'get_callback' => 'get_post_read_time',
'update_callback' => null, // 禁用更新
'schema' => array(
'type' => 'integer',
'description' => 'Estimated reading time in minutes.',
'readonly' => true,
),
)
);
}
add_action( 'rest_api_init', 'register_read_time_field' );
function get_post_read_time( $object, $field_name, $request ) {
$content = $object['content']['rendered']; // 获取文章内容
$word_count = str_word_count( strip_tags( $content ) ); // 计算单词数
$reading_time = ceil( $word_count / 200 ); // 假设平均阅读速度为每分钟 200 个单词
return $reading_time;
}
这段代码做了以下事情:
- 定义了一个名为
register_read_time_field
的函数,该函数负责注册自定义字段。 - 使用
register_rest_field
函数注册一个名为read_time
的字段,该字段与post
对象类型关联。 get_callback
设置为get_post_read_time
函数,该函数用于计算文章的阅读时长。update_callback
设置为null
,表示该字段是只读的,不能通过 REST API 进行更新。schema
定义了字段的数据类型为integer
,描述为 "Estimated reading time in minutes.",并将其设置为只读。- 使用
add_action
函数将register_read_time_field
函数挂钩到rest_api_init
动作,确保在 REST API 初始化时注册该字段。
现在,当你通过 REST API 请求一篇文章时,你会在响应中看到 read_time
字段:
{
"id": 123,
"title": {
"rendered": "My Awesome Post"
},
"content": {
"rendered": "<p>This is the content of my post.</p>"
},
"read_time": 3, // 阅读时长为 3 分钟
// ... 其他字段
}
案例 2:为用户添加自定义 "社交媒体链接" 字段
假设我们想为每个用户添加一个 "社交媒体链接" 字段,该字段是一个包含 Facebook 和 Twitter 链接的对象。
function register_social_media_fields() {
register_rest_field(
'user',
'social_media',
array(
'get_callback' => 'get_user_social_media',
'update_callback' => 'update_user_social_media',
'schema' => array(
'type' => 'object',
'description' => 'User social media links.',
'properties' => array(
'facebook' => array(
'type' => 'string',
'description' => 'Facebook profile URL.',
),
'twitter' => array(
'type' => 'string',
'description' => 'Twitter profile URL.',
),
),
),
)
);
}
add_action( 'rest_api_init', 'register_social_media_fields' );
function get_user_social_media( $object, $field_name, $request ) {
$user_id = $object['id'];
$facebook = get_user_meta( $user_id, 'facebook_url', true );
$twitter = get_user_meta( $user_id, 'twitter_url', true );
return array(
'facebook' => $facebook,
'twitter' => $twitter,
);
}
function update_user_social_media( $value, $object, $field_name ) {
$user_id = $object->ID;
if ( isset( $value['facebook'] ) ) {
update_user_meta( $user_id, 'facebook_url', sanitize_text_field( $value['facebook'] ) );
}
if ( isset( $value['twitter'] ) ) {
update_user_meta( $user_id, 'twitter_url', sanitize_text_field( $value['twitter'] ) );
}
return true; // 表示更新成功
}
这段代码做了以下事情:
- 定义了一个名为
register_social_media_fields
的函数,该函数负责注册自定义字段。 - 使用
register_rest_field
函数注册一个名为social_media
的字段,该字段与user
对象类型关联。 get_callback
设置为get_user_social_media
函数,该函数从用户元数据中获取 Facebook 和 Twitter 链接。update_callback
设置为update_user_social_media
函数,该函数用于更新用户元数据中的 Facebook 和 Twitter 链接。schema
定义了字段的数据类型为object
,描述为 "User social media links.",并定义了facebook
和twitter
属性,它们都是字符串类型。- 使用
add_action
函数将register_social_media_fields
函数挂钩到rest_api_init
动作。
现在,当你通过 REST API 请求一个用户时,你会在响应中看到 social_media
字段:
{
"id": 456,
"name": "John Doe",
"social_media": {
"facebook": "https://www.facebook.com/johndoe",
"twitter": "https://twitter.com/johndoe"
},
// ... 其他字段
}
你还可以通过发送带有 social_media
字段的 PUT
或 PATCH
请求来更新用户的社交媒体链接。
案例 3: 添加自定义字段到自定义文章类型
register_rest_field
同样适用于自定义文章类型。 假设你有一个自定义文章类型 book
,并且你想添加一个 author
字段。
function register_book_author_field() {
register_rest_field(
'book', // 自定义文章类型
'author',
array(
'get_callback' => 'get_book_author',
'update_callback' => 'update_book_author',
'schema' => array(
'type' => 'string',
'description' => 'The author of the book.',
),
)
);
}
add_action( 'rest_api_init', 'register_book_author_field' );
function get_book_author( $object, $field_name, $request ) {
$author_id = get_post_meta( $object['id'], 'book_author_id', true );
if ( $author_id ) {
return get_the_title( $author_id ); // 获取作者姓名
}
return '';
}
function update_book_author( $value, $object, $field_name ) {
// 假设 $value 是作者ID
if(is_numeric($value)){
update_post_meta( $object->ID, 'book_author_id', intval($value) );
return true;
}
return false;
}
3. schema
的重要性与高级用法
schema
参数不仅用于描述字段的数据类型,还可以用于定义更复杂的验证规则和显示行为。 以下是一些 schema
的高级用法示例:
-
枚举值: 你可以使用
enum
属性来限制字段的可能值。'schema' => array( 'type' => 'string', 'enum' => array( 'draft', 'pending', 'publish', 'trash' ), 'description' => 'Post status.', ),
-
正则表达式: 你可以使用
pattern
属性来验证字段的值是否符合特定的正则表达式。'schema' => array( 'type' => 'string', 'pattern' => '^https?://.+$', // 必须是 URL 'description' => 'A valid URL.', ),
-
只读/只写: 你可以使用
readonly
和writeonly
属性来控制字段是否可以读取或写入。'schema' => array( 'type' => 'integer', 'readonly' => true, // 只能读取,不能写入 'description' => 'The post ID.', ),
-
嵌套对象: 你可以定义包含其他对象或数组的复杂 schema。 这对于表示复杂的数据结构非常有用。
'schema' => array( 'type' => 'object', 'properties' => array( 'address' => array( 'type' => 'object', 'properties' => array( 'street' => array( 'type' => 'string' ), 'city' => array( 'type' => 'string' ), ), ), 'phone' => array( 'type' => 'string' ), ), ),
4. 安全性与数据验证
在使用 register_rest_field
添加自定义字段时,务必注意安全性与数据验证。
-
数据清理: 在
update_callback
函数中,始终对用户输入的数据进行清理和验证,以防止安全漏洞,例如跨站脚本攻击 (XSS) 和 SQL 注入。 使用 WordPress 提供的函数,例如sanitize_text_field()
,absint()
,esc_url_raw()
等。 -
权限控制: 确保只有授权用户才能更新自定义字段。 在
update_callback
函数中,使用 WordPress 提供的权限检查函数,例如current_user_can()
,来验证用户是否具有足够的权限。 -
Schema 验证: 利用
schema
提供的验证功能,例如enum
和pattern
,来确保字段的值符合预期的格式和范围。 这可以帮助防止无效数据进入数据库。
5. 与其他插件的兼容性
在使用 register_rest_field
时,需要注意与其他插件的兼容性。 有些插件可能会修改 REST API 的行为,或者添加自己的自定义字段。
-
命名冲突: 避免使用与其他插件或主题使用的名称相同的字段名称。 建议使用唯一的前缀来命名你的自定义字段。
-
数据冲突: 如果多个插件尝试更新同一个自定义字段,可能会发生数据冲突。 可以使用 WordPress 提供的事务处理功能来确保数据的一致性。
-
过滤器: WordPress 提供了许多过滤器,可以用于修改 REST API 的行为。 你可以使用这些过滤器来与其他插件进行集成,或者修改自定义字段的显示方式。 常见的过滤器包括
rest_prepare_{$post_type}
和rest_{$post_type}_query
。
6. 性能优化
添加过多的自定义字段可能会影响 REST API 的性能。 以下是一些性能优化技巧:
-
只获取需要的字段: 使用
_fields
查询参数来指定你只需要获取的字段。 例如,?_fields=id,title,read_time
将只返回文章的 ID、标题和阅读时长。 -
缓存: 对
get_callback
函数的结果进行缓存,以避免重复计算。 可以使用 WordPress 提供的对象缓存 API 或瞬态 API。 -
索引: 如果你的自定义字段用于查询或排序,请确保在数据库中为这些字段创建索引。
代码示例: 利用show_in_rest
参数
假设你只想在编辑文章的界面中显示某个自定义字段,但在公开的 API 端点中隐藏它,可以使用 show_in_rest
参数:
register_rest_field(
'post',
'internal_notes',
array(
'get_callback' => 'get_internal_notes',
'update_callback' => 'update_internal_notes',
'schema' => array(
'type' => 'string',
'description' => 'Internal notes for editors.',
),
'show_in_rest' => array( 'edit' => true, 'view' => false ),
)
);
function get_internal_notes( $object, $field_name, $request ) {
return get_post_meta( $object['id'], 'internal_notes', true );
}
function update_internal_notes( $value, $object, $field_name ) {
return update_post_meta( $object->ID, 'internal_notes', sanitize_textarea_field( $value ) );
}
在这个例子中,internal_notes
字段只会在编辑文章时出现在 REST API 响应中,对于公共的 view
端点则会被隐藏。
表格总结: register_rest_field
参数详解
参数 | 类型 | 描述 |
---|---|---|
$object_type |
string | 指定要添加自定义字段的对象类型(例如:’post’, ‘page’, ‘user’)。 |
$attribute |
string | 自定义字段的名称,将作为 REST API 响应中的键。 |
$args |
array | 包含用于定义字段行为的参数数组。 |
$args['get_callback'] |
callable | 用于获取字段值的回调函数。 接受 $object , $field_name , $request 参数。必须返回字段的值。 |
$args['update_callback'] |
callable | 用于更新字段值的回调函数。 接受 $value , $object , $field_name 参数。返回 true 表示更新成功,false 表示失败。如果不需要更新功能,可以设置为 null 或 false 。 |
$args['schema'] |
array | 描述字段数据类型的 schema 数组。 遵循 JSON Schema 规范。 用于向 REST API 客户端提供元数据。 |
$args['show_in_rest'] |
boolean/array | 控制是否以及如何将该自定义字段显示在 REST API 响应中。 默认为 true (总是显示)。设置为 false 则不显示。如果是数组,可以指定在哪些 endpoint 中显示该字段 (例如 ['edit' => true, 'view' => false] )。 |
代码示例: 使用自定义控制器的示例
虽然 register_rest_field
足够强大,但是对于更复杂的场景,可能需要创建一个自定义的 REST API 控制器。 这种方式可以提供更多的控制权,例如自定义路由、参数验证和权限控制。
// 1. 创建一个继承自 WP_REST_Controller 的类
class My_Custom_REST_Controller extends WP_REST_Controller {
// 定义命名空间和路由前缀
protected $namespace = 'my-plugin/v1';
protected $rest_base = 'custom-data';
// 注册路由
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' )
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' )
),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' )
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' )
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' )
),
) );
}
// 2. 定义权限检查回调函数
public function get_items_permissions_check( $request ) {
// 检查用户是否具有读取数据的权限
if ( ! current_user_can( 'read' ) ) {
return new WP_Error( 'rest_forbidden', 'You do not have permission to view this data.', array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
public function create_item_permissions_check( $request ) {
// 检查用户是否具有创建数据的权限
if ( ! current_user_can( 'publish_posts' ) ) {
return new WP_Error( 'rest_forbidden', 'You do not have permission to create this data.', array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
public function update_item_permissions_check( $request ) {
return $this->create_item_permissions_check( $request );
}
public function delete_item_permissions_check( $request ) {
return $this->create_item_permissions_check( $request );
}
protected function authorization_status_code() {
if ( is_user_logged_in() ) {
return rest_authorization_required_code();
}
return 401;
}
// 3. 定义数据处理回调函数
public function get_items( $request ) {
// 获取所有自定义数据
$data = array(); // 替换为你的数据获取逻辑
return rest_ensure_response( $data );
}
public function get_item( $request ) {
$id = (int) $request['id'];
// 获取单个自定义数据项
$data = array('id' => $id, 'message' => 'This is item ' . $id); // 替换为你的数据获取逻辑
if(empty($data)){
return new WP_Error( 'rest_not_found', 'Item not found', array( 'status' => 404 ) );
}
return rest_ensure_response( $data );
}
public function create_item( $request ) {
// 创建新的自定义数据项
$parameters = $request->get_params(); // 获取所有参数
if(empty($parameters['title'])){
return new WP_Error( 'rest_bad_request', 'Title is required', array( 'status' => 400 ) );
}
$data = array('id' => rand(10,100), 'title' => sanitize_text_field($parameters['title']));
return rest_ensure_response( $data );
}
public function update_item( $request ) {
$id = (int) $request['id'];
// 更新自定义数据项
$parameters = $request->get_params(); // 获取所有参数
if(empty($parameters['title'])){
return new WP_Error( 'rest_bad_request', 'Title is required', array( 'status' => 400 ) );
}
$data = array('id' => $id, 'title' => sanitize_text_field($parameters['title']));
return rest_ensure_response( $data );
}
public function delete_item( $request ) {
$id = (int) $request['id'];
// 删除自定义数据项
return rest_ensure_response( array( 'deleted' => true ) );
}
}
// 4. 注册控制器
function register_my_custom_rest_controller() {
$controller = new My_Custom_REST_Controller();
$controller->register_routes();
}
add_action( 'rest_api_init', 'register_my_custom_rest_controller' );
这个例子展示了如何创建一个自定义 REST API 控制器,定义路由、权限检查和数据处理逻辑。 使用自定义控制器可以更好地组织和管理你的 REST API 端点。
自定义REST字段注册技术要点
register_rest_field
函数是 WordPress REST API 扩展的核心工具,通过它我们可以方便地向 WordPress 对象添加自定义数据,从而满足各种应用场景的需求。 充分理解其参数、schema 的使用,并注意安全性和性能优化,可以帮助你更好地利用 REST API 构建强大的应用程序。