概述

API 中的响应包含了我们要的所有数据。如果我们在请求中犯了错误,响应的数据也应告知我们发生了错误。WordPress REST API 中的响应应返回我们请求的数据或错误消息。API 中的响应由 WP_REST_Response 类处理,这是 API 的三个基础架构类之一。

WP_REST_Response

WP_REST_Response 扩展了 WordPress 的 WP_HTTP_Response 类,允许我们访问响应头、响应状态码和响应数据。

// The following code will not do anything and just serves as a demonstration.
$response = new WP_REST_Response( 'This is some data' );

// To get the response data we can use this method. It should equal 'This is some data'.
$our_data = $response->get_data();

// To access the HTTP status code we can use this method. The most common status code is probably 200, which means OK!
$our_status = $response->get_status();

// To access the HTTP response headers we can use this method.
$our_headers = $response->get_headers();

上述内容相当直接,展示了如何从响应中获取所需的内容。WP_REST_Response 进一步扩展了功能。您可以使用 $response->get_matched_route() 访问响应的匹配路由,以回溯响应来自哪个端点。$response->get_matched_handler() 将返回为生成我们响应的端点注册的选项。这些对于记录 API 等其他用途可能很有用。响应类还有助于我们处理错误。

错误处理

如果我们的请求中出了严重问题,我们可以使用 WP_Error 对象在我们的端点回调中返回解释问题的内容,如下所示:

// Register our mock batch endpoint.
function prefix_register_broken_route() {
    register_rest_route( 'my-namespace/v1', '/broken', array(
        // Supported methods for this endpoint. WP_REST_Server::READABLE translates to GET.
        'methods' => WP_REST_Server::READABLE,
        // Register the callback for the endpoint.
        'callback' => 'prefix_get_an_error',
    ) );
}

add_action( 'rest_api_init', 'prefix_register_broken_route' );

/**
 * Our registered endpoint callback. Notice how we are passing in $request as an argument.
 * By default, the WP_REST_Server will pass in the matched request object to our callback.
 *
 * @param WP_REST_Request $request The current matched request object.
 */
function prefix_get_an_error( $request ) {
    return new WP_Error( 'oops', esc_html__( 'This endpoint is currently broken, try another endpoint, I promise the API is cool! EEEK!!!!', 'my-textdomain' ), array( 'status' => 400 ) );
}

这是一个有点愚蠢的例子,但它触及了一些关键点。最重要的是要理解的是,WordPress REST API 会自动将 WP_Error 对象转换为包含您数据的 HTTP 响应。当您在 WP_Error 对象中设置状态码时,您的 HTTP 响应状态码将采用该值。这在需要使用不同的错误代码(例如 404 表示未找到内容,或 403 表示禁止访问)时非常有用。我们只需要让我们的端点回调返回请求,WP_REST_Server 类就会为我们处理很多非常重要的事情。

响应类还可以帮助我们完成其他一些有趣的事情,比如链接。

链接

如果我们想获取一篇文章及其第一条评论呢?我们会编写一个单独的端点来处理这种用例吗?如果这样做,我们需要开始添加很多端点来处理各种小型用例,我们的 API 索引很快就会变得臃肿。响应链接为我们提供了一种在 API 可以理解的资源之间形成关系的方法。API 实现了一种称为 HAL 的资源链接标准。让我们看看我们的文章和评论示例,为每个资源设置路由会更好。

假设我们有一篇文章 ID = 1 且评论 ID = 3。该评论分配给文章 1,因此实际上这两个资源可以位于路由 /my-namespace/v1/posts/1 和 /my-namespace/v1/comments/3。我们将为响应添加链接以创建它们之间的关系。让我们首先从评论的角度来看。

// Register our mock endpoints.
function prefix_register_my_routes() {
    register_rest_route( 'my-namespace/v1', '/posts/(?P<id>[\d]+)', array(
        // Supported methods for this endpoint. WP_REST_Server::READABLE translates to GET.
        'methods' => WP_REST_Server::READABLE,
        // Register the callback for the endpoint.
        'callback' => 'prefix_get_rest_post',
    ) );
    register_rest_route( 'my-namespace/v1', '/comments', array(
        // Supported methods for this endpoint. WP_REST_Server::READABLE translates to GET.
        'methods' => WP_REST_Server::READABLE,
        // Register the callback for the endpoint.
        'callback' => 'prefix_get_rest_comments',
        // Register the post argument to limit results to a specific post parent.
        'args' => array(
            'post' => array(
                'description' => esc_html__( 'The post ID that the comment is assigned to.', 'my-textdomain' ),
                'type'        => 'integer',
                'required'    => true,
            ),
        ),
    ) );
    register_rest_route( 'my-namespace/v1', '/comments/(?P<id>[\d]+)', array(
        // Supported methods for this endpoint. WP_REST_Server::READABLE translates to GET.
        'methods' => WP_REST_Server::READABLE,
        // Register the callback for the endpoint.
        'callback' => 'prefix_get_rest_comment',
    ) );
}

add_action( 'rest_api_init', 'prefix_register_my_routes' );

// Grab a post.
function prefix_get_rest_post( $request ) {
    $id = (int) $request['id'];
    $post = get_post( $id );

    $response = rest_ensure_response( array( $post ) );

    $response->add_links( prefix_prepare_post_links( $post ) );

    return $response;
}

// Prepare post links.
function prefix_prepare_post_links( $post ) {
    $links = array();

    $replies_url = rest_url( 'my-namespace/v1/comments' );
    $replies_url = add_query_arg( 'post', $post->ID, $replies_url );
    $links['replies'] = array(
		'href'         => $replies_url,
		'embeddable'   => true,
    );

    return $links;
}

// Grab a comments.
function prefix_get_rest_comments( $request ) {
    if ( ! isset( $request['post'] ) ) {
        return new WP_Error( 'rest_bad_request', esc_html__( 'You must specify the post parameter for this request.', 'my-text-domain' ), array( 'status' => 400 ) );
    }

    $data = array();

    $comments = get_comments( array( 'post__in' => $request['post'] ) );

    if ( empty( $comments ) ) {
        return array();
    }

    foreach( $comments as $comment ) {
        $response = rest_ensure_response( $comment );
        $response->add_links( prefix_prepare_comment_links( $comment ) );
        $data[] = prefix_prepare_for_collection( $response );
    }

    $response = rest_ensure_response( $data );
    return $response;
}

// Grab a comment.
function prefix_get_rest_comment( $request ) {
    $id = (int) $request['id'];
    $post = get_comment( $id );

    $response = rest_ensure_response( $comment );

    $response->add_links( prefix_prepare_comment_links( $comment ) );

    return $response;
}

// Prepare comment links.
function prefix_prepare_comment_links( $comment ) {
    $links = array();
    if ( 0 !== (int) $comment->comment_post_ID ) {
        $post = get_post( $comment->comment_post_ID );
        if ( ! empty( $post->ID ) ) {
        $links['up'] = array(
                'href'       => rest_url( 'my-namespace/v1/posts/' . $comment->comment_post_ID ),
                'embeddable' => true,
                'post_type'  => $post->post_type,
            );
        }
    }
    return $links;
}

/**
 * Prepare a response for inserting into a collection of responses.
 *
 * This is lifted from WP_REST_Controller class in the WP REST API v2 plugin.
 *
 * @param WP_REST_Response $response Response object.
 * @return array Response data, ready for insertion into collection data.
 */
function prefix_prepare_for_collection( $response ) {
	if ( ! ( $response instanceof WP_REST_Response ) ) {
		return $response;
	}

	$data = (array) $response->get_data();
	$server = rest_get_server();

	if ( method_exists( $server, 'get_compact_response_links' ) ) {
		$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
	} else {
		$links = call_user_func( array( $server, 'get_response_links' ), $response );
	}

	if ( ! empty( $links ) ) {
		$data['_links'] = $links;
	}

	return $data;
}

正如上面的示例所示,我们使用链接来创建资源之间的关系。如果文章有评论,我们的端点回调将添加一个指向评论路由的链接,并指定 `post` 参数以匹配我们当前的文章 ID。因此,如果您遵循该路由,您现在将获得具有该分配文章 ID 的评论。如果您搜索评论,则每条评论都将有一个指向上方的链接指向文章。在 HAL 规范中,使用链接时 `up` 具有特殊含义。如果我们跟随评论的上方链接,则将返回作为评论父级的文章。链接功能非常强大,但还有更好的方法。

WordPress REST API 还支持所谓的嵌入(embedding)。如果您注意到我们添加的两个链接中,我们都指定了 embeddable => true,这使我们能够将链接数据嵌入到响应中。因此,如果我们想获取评论 3 及其分配的文章,我们可以发出此请求 https://ourawesomesite.com/wp-json/my-namespace/v1/comments/3?_embed。_embed 参数告诉 API 我们希望在请求中也添加所有可嵌入资源链接。使用嵌入是一种性能提升,因为多个资源都在一个 HTTP 请求中处理。

巧妙使用嵌入和链接使 WordPress REST API 在与 WordPress 交互时变得极其灵活且功能强大。