详解 WordPress `WP_REST_Response` 类的源码:如何构建 REST API 的响应对象。

WordPress REST API 响应对象详解:WP_REST_Response 类源码剖析

嘿,各位代码爱好者!今天咱们来聊聊 WordPress REST API 的核心部分:WP_REST_Response 类。这玩意儿,说白了,就是你构建 REST API 响应的容器,就像你用漂亮的盒子把礼物包装起来一样。没它,你的 API 就只能吐出一些冷冰冰的数据,缺少了应有的格式和信息。

咱们今天就深入 WP_REST_Response 的源码,看看它到底是怎么工作的,以及怎么用它来构建高质量的 REST API 响应。

1. 基础概念:REST API 响应的结构

在深入代码之前,咱们先明确一下 REST API 响应的基本结构。一个典型的 REST API 响应包含以下几个关键部分:

  • 状态码 (Status Code): 指示请求是否成功,例如 200 (OK), 404 (Not Found), 500 (Internal Server Error) 等。
  • 头部 (Headers): 包含关于响应的元数据,例如 Content-Type, Cache-Control 等。
  • 正文 (Body): 实际返回的数据,通常是 JSON 格式。

WP_REST_Response 类就是用来封装这些信息的。

2. WP_REST_Response 类的诞生:构造函数

咱们先来看看 WP_REST_Response 类的构造函数:

/**
 * Constructor.
 *
 * @param mixed $data   Optional. Response data. Default null.
 * @param int   $status Optional. The HTTP status code. Default 200.
 * @param array $headers Optional. Array of HTTP headers to set. Default empty array.
 */
public function __construct( $data = null, $status = 200, $headers = array() ) {
    $this->set_data( $data );
    $this->set_status( $status );
    $this->set_headers( $headers );
}

很简单,对吧?构造函数接收三个参数:

  • $data: 响应的数据,可以是任何类型,但通常是数组或对象,最终会被转换为 JSON。
  • $status: HTTP 状态码,默认为 200 (OK)。
  • $headers: HTTP 头部,是一个关联数组,键是头部名称,值是头部的值。

示例:

$response = new WP_REST_Response(
    array(
        'message' => 'Hello, world!',
        'status'  => 'success',
    ),
    200,
    array(
        'Content-Type' => 'application/json',
        'Cache-Control' => 'no-cache',
    )
);

这段代码创建了一个 WP_REST_Response 对象,包含了数据、状态码和头部信息。

3. 设置和获取数据:set_data()get_data()

WP_REST_Response 类提供了 set_data()get_data() 方法来设置和获取响应的数据。

/**
 * Sets the data for the response.
 *
 * @param mixed $data Data for the response.
 */
public function set_data( $data ) {
    $this->data = $data;
}

/**
 * Gets the data for the response.
 *
 * @return mixed Data for the response.
 */
public function get_data() {
    return $this->data;
}

这两个方法非常直接,就是简单的设置和获取 $data 属性。

示例:

$response = new WP_REST_Response();
$response->set_data( array( 'message' => 'Updated data!' ) );
$data = $response->get_data();
echo $data['message']; // 输出: Updated data!

4. 设置和获取状态码:set_status()get_status()

类似地,set_status()get_status() 方法用于设置和获取 HTTP 状态码。

/**
 * Sets the HTTP status code.
 *
 * @param int $status HTTP status code.
 */
public function set_status( $status ) {
    $this->status = (int) $status;
}

/**
 * Gets the HTTP status code.
 *
 * @return int HTTP status code.
 */
public function get_status() {
    return $this->status;
}

示例:

$response = new WP_REST_Response();
$response->set_status( 404 );
$status = $response->get_status();
echo $status; // 输出: 404

5. 设置和获取头部:set_headers(), get_headers(), header()

HTTP 头部是响应的重要组成部分,WP_REST_Response 类提供了几个方法来处理它们:

  • set_headers( array $headers, bool $replace = true ): 设置头部。
  • get_headers(): 获取所有头部。
  • header( string $key, string $value, bool $replace = true ): 设置单个头部。
/**
 * Sets HTTP response headers.
 *
 * @param array $headers Array of HTTP headers to set.
 * @param bool  $replace Optional. Whether to replace the header, or add it. Default true.
 */
public function set_headers( $headers, $replace = true ) {
    foreach ( (array) $headers as $key => $value ) {
        $this->header( $key, $value, $replace );
    }
}

/**
 * Retrieves all HTTP response headers.
 *
 * @return array HTTP response headers.
 */
public function get_headers() {
    return $this->headers;
}

/**
 * Sets a single HTTP header.
 *
 * @param string $key     HTTP header key.
 * @param string $value   HTTP header value.
 * @param bool   $replace Optional. Whether to replace the header, or add it. Default true.
 */
public function header( $key, $value, $replace = true ) {
    $key   = $this->normalize_header_key( $key );
    $value = (string) $value;

    if ( $replace || ! isset( $this->headers[ $key ] ) ) {
        $this->headers[ $key ] = $value;
    } else {
        $this->headers[ $key ] .= ', ' . $value;
    }
}

/**
 * Normalizes a header key to be properly case-insensitive.
 *
 * @param string $key Header key to normalize.
 * @return string Normalized header key.
 */
protected function normalize_header_key( $key ) {
    $key = strtolower( $key );
    $key = str_replace( '_', '-', $key );
    return $key;
}

注意: normalize_header_key() 方法会将头部名称转换为小写,并将下划线替换为短横线,以确保头部名称的一致性。

示例:

$response = new WP_REST_Response();
$response->header( 'Content-Type', 'application/json' );
$response->header( 'X-Custom-Header', 'My Value' );

$headers = $response->get_headers();
print_r( $headers );
// 输出:
// Array
// (
//     [content-type] => application/json
//     [x-custom-header] => My Value
// )

6. 添加链接关系:add_link()remove_link()

REST API 提倡使用超媒体作为应用状态引擎 (HATEOAS)。这意味着响应应该包含指向相关资源的链接。WP_REST_Response 类提供了 add_link()remove_link() 方法来添加和删除链接关系。

/**
 * Adds a link relation.
 *
 * @param string $rel  Relation type.
 * @param string $href URL.
 * @param array  $attributes Optional. Attributes for the link. Default empty array.
 */
public function add_link( $rel, $href, $attributes = array() ) {
    if ( empty( $this->links[ $rel ] ) ) {
        $this->links[ $rel ] = array();
    }

    $this->links[ $rel ][] = array(
        'href'       => $href,
        'attributes' => $attributes,
    );
}

/**
 * Removes a link relation.
 *
 * @param string $rel  Relation type.
 * @param string $href URL to remove.
 */
public function remove_link( $rel, $href ) {
    if ( empty( $this->links[ $rel ] ) ) {
        return;
    }

    foreach ( $this->links[ $rel ] as $key => $link ) {
        if ( $link['href'] === $href ) {
            unset( $this->links[ $rel ][ $key ] );
        }
    }

    if ( empty( $this->links[ $rel ] ) ) {
        unset( $this->links[ $rel ] );
    }
}

示例:

$response = new WP_REST_Response();
$response->add_link( 'self', 'https://example.com/posts/1' );
$response->add_link( 'author', 'https://example.com/users/1', array( 'title' => 'John Doe' ) );

$links = $response->get_links();
print_r( $links );
// 输出:
// Array
// (
//     [self] => Array
//         (
//             [0] => Array
//                 (
//                     [href] => https://example.com/posts/1
//                     [attributes] => Array
//                         (
//                         )
//
//                 )
//
//         )
//
//     [author] => Array
//         (
//             [0] => Array
//                 (
//                     [href] => https://example.com/users/1
//                     [attributes] => Array
//                         (
//                             [title] => John Doe
//                         )
//
//                 )
//
//         )
//
// )

7. 获取链接关系:get_links()

get_links() 方法用于获取所有已添加的链接关系。

/**
 * Retrieves all link relations.
 *
 * @return array Link relations.
 */
public function get_links() {
    return $this->links;
}

8. 准备响应:prepare_response_for_collection()

这个方法用于将单个响应对象准备成集合响应的一部分。它主要用于分页或其他需要返回多个资源的场景。

/**
 * Prepares the object for serialization into a collection.
 *
 * @return array Prepared data, which is likely to be an array
 */
public function prepare_response_for_collection() {
    return $this->get_data();
}

通常,这个方法会返回 get_data() 的结果,但你可以根据需要进行自定义。

9. 转换为响应对象:to_array()get_data_for_response()

这两个方法用于将 WP_REST_Response 对象转换为数组,以便进行序列化。to_array() 是一个公共方法,而 get_data_for_response() 是一个受保护的方法,用于获取用于响应的数据。

/**
 * Returns the object as an array, suitable for returning in a response.
 *
 * @return array Data for response.
 */
public function to_array() {
    $data = $this->get_data_for_response();

    // If the data is already an array, just return it.
    if ( is_array( $data ) ) {
        return $data;
    }

    // Otherwise, wrap it in an array.
    return array( 'data' => $data );
}

/**
 * Get the data to be used in the response.
 *
 * @return mixed The data to be used in the response.
 */
protected function get_data_for_response() {
    return $this->get_data();
}

to_array() 方法会检查数据是否已经是数组,如果是,则直接返回;否则,将其包装在一个包含 data 键的数组中。

10. 发送响应:与 WordPress 钩子的交互

WP_REST_Response 对象本身并不负责发送响应。它只是一个数据容器。WordPress REST API 使用钩子来处理响应的发送。例如,rest_pre_serve_request 钩子允许你修改响应对象,而 rest_send_cors_headers 钩子允许你设置 CORS 头部。

11. 实战演练:构建一个简单的 REST API 响应

现在,咱们来用 WP_REST_Response 类构建一个简单的 REST API 响应。假设我们要创建一个 API 端点,用于获取文章的信息。

add_action( 'rest_api_init', function () {
    register_rest_route( 'my-plugin/v1', '/posts/(?P<id>d+)', array(
        'methods'  => 'GET',
        'callback' => 'my_plugin_get_post',
    ) );
} );

function my_plugin_get_post( WP_REST_Request $request ) {
    $post_id = $request['id'];
    $post = get_post( $post_id );

    if ( empty( $post ) ) {
        return new WP_Error( 'rest_post_invalid_id', 'Invalid post ID.', array( 'status' => 404 ) );
    }

    $data = array(
        'id'    => $post->ID,
        'title' => get_the_title( $post ),
        'content' => apply_filters( 'the_content', $post->post_content ),
        'link'  => get_permalink( $post ),
    );

    $response = new WP_REST_Response( $data, 200 );
    $response->header( 'Content-Type', 'application/json' );
    $response->add_link( 'self', rest_url( 'my-plugin/v1/posts/' . $post->ID ) );

    return $response;
}

这段代码做了以下几件事:

  1. 注册 REST API 路由: 使用 register_rest_route() 函数注册一个路由,用于获取文章的信息。
  2. 定义回调函数: my_plugin_get_post() 函数是路由的回调函数,它接收一个 WP_REST_Request 对象作为参数。
  3. 获取文章信息: 从请求中获取文章 ID,并使用 get_post() 函数获取文章对象。
  4. 构建响应数据: 创建一个包含文章信息的数组。
  5. 创建 WP_REST_Response 对象: 使用数组、状态码和头部信息创建一个 WP_REST_Response 对象。
  6. 添加链接: 添加一个指向文章自身的链接。
  7. 返回响应对象: 返回 WP_REST_Response 对象。

12. WP_Error 类:处理错误

在上面的例子中,我们使用了 WP_Error 类来处理无效的文章 ID。WP_Error 类是 WordPress 中用于处理错误的类。它包含错误代码、错误信息和错误数据。

if ( empty( $post ) ) {
    return new WP_Error( 'rest_post_invalid_id', 'Invalid post ID.', array( 'status' => 404 ) );
}

当返回 WP_Error 对象时,WordPress REST API 会自动将其转换为一个包含错误信息的 JSON 响应,并将 HTTP 状态码设置为错误代码中指定的 status

13. WP_REST_Controller 类:组织你的 API

虽然你可以直接使用 WP_REST_Response 类来构建 API 响应,但使用 WP_REST_Controller 类可以更好地组织你的 API 代码。WP_REST_Controller 类是一个基类,你可以继承它来创建自己的 API 控制器。

class My_Plugin_REST_Controller extends WP_REST_Controller {

    /**
     * Register the routes for the objects of the controller.
     */
    public function register_routes() {
        $namespace = 'my-plugin/v1';
        $base = 'posts';
        register_rest_route( $namespace, '/' . $base . '/(?P<id>d+)', array(
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'get_item' ),
                'permission_callback' => array( $this, 'get_item_permissions_check' ),
                'args'                => array(
                    'id' => array(
                        'description' => __( 'Unique identifier for the object.' ),
                        'type'        => 'integer',
                        'required'    => true,
                    ),
                ),
            ),
        ) );
    }

    /**
     * Get one item from the collection.
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_REST_Response|WP_Error Response on success.
     */
    public function get_item( $request ) {
        $post_id = $request['id'];
        $post = get_post( $post_id );

        if ( empty( $post ) ) {
            return new WP_Error( 'rest_post_invalid_id', 'Invalid post ID.', array( 'status' => 404 ) );
        }

        $data = array(
            'id'    => $post->ID,
            'title' => get_the_title( $post ),
            'content' => apply_filters( 'the_content', $post->post_content ),
            'link'  => get_permalink( $post ),
        );

        $response = new WP_REST_Response( $data, 200 );
        $response->header( 'Content-Type', 'application/json' );
        $response->add_link( 'self', rest_url( 'my-plugin/v1/posts/' . $post->ID ) );

        return $response;
    }

    /**
     * Check if a given request has access to get one item.
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_Error|bool
     */
    public function get_item_permissions_check( $request ) {
        // You can add permission checks here.
        return true;
    }
}

add_action( 'rest_api_init', function () {
    $controller = new My_Plugin_REST_Controller();
    $controller->register_routes();
} );

这个例子展示了如何使用 WP_REST_Controller 类来组织 API 代码,包括注册路由、定义回调函数和进行权限检查。

总结

WP_REST_Response 类是 WordPress REST API 的核心组成部分,它提供了一种简单而强大的方式来构建 API 响应。通过理解 WP_REST_Response 类的源码和使用方法,你可以创建高质量、易于使用的 REST API。

希望今天的讲解能帮助你更好地理解 WP_REST_Response 类,并在你的 WordPress REST API 开发中发挥作用。记住,代码就像魔法,只要你掌握了咒语,就能创造出令人惊叹的东西! 祝你编程愉快!

发表回复

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