WordPress函数register_rest_field在自定义REST字段注册中的运行原理

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。 如果不需要更新功能,可以省略此参数,或者设置为 nullfalse

    • 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;
}

这段代码做了以下事情:

  1. 定义了一个名为 register_read_time_field 的函数,该函数负责注册自定义字段。
  2. 使用 register_rest_field 函数注册一个名为 read_time 的字段,该字段与 post 对象类型关联。
  3. get_callback 设置为 get_post_read_time 函数,该函数用于计算文章的阅读时长。
  4. update_callback 设置为 null,表示该字段是只读的,不能通过 REST API 进行更新。
  5. schema 定义了字段的数据类型为 integer,描述为 "Estimated reading time in minutes.",并将其设置为只读。
  6. 使用 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; // 表示更新成功
}

这段代码做了以下事情:

  1. 定义了一个名为 register_social_media_fields 的函数,该函数负责注册自定义字段。
  2. 使用 register_rest_field 函数注册一个名为 social_media 的字段,该字段与 user 对象类型关联。
  3. get_callback 设置为 get_user_social_media 函数,该函数从用户元数据中获取 Facebook 和 Twitter 链接。
  4. update_callback 设置为 update_user_social_media 函数,该函数用于更新用户元数据中的 Facebook 和 Twitter 链接。
  5. schema 定义了字段的数据类型为 object,描述为 "User social media links.",并定义了 facebooktwitter 属性,它们都是字符串类型。
  6. 使用 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 字段的 PUTPATCH 请求来更新用户的社交媒体链接。

案例 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.',
    ),
  • 只读/只写: 你可以使用 readonlywriteonly 属性来控制字段是否可以读取或写入。

    '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 提供的验证功能,例如 enumpattern,来确保字段的值符合预期的格式和范围。 这可以帮助防止无效数据进入数据库。

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 表示失败。如果不需要更新功能,可以设置为 nullfalse
$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 构建强大的应用程序。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注