控制器类

概述

要注册新的 REST 路由,您必须指定一些回调函数来控制端点行为,例如如何完成请求、如何应用权限检查以及如何生成资源架构。虽然可以在没有任何包装命名空间或类的普通 PHP 文件中声明所有这些方法,但以这种方式声明的所有函数共存于同一全局范围内。如果您决定为您的端点逻辑使用一个通用的函数名称,并且get_items()另一个插件(或您自己的插件中的另一个端点)也注册了一个具有相同名称的函数,PHP 将失败并出现致命错误,因为该函数被声明了两次get_items()

您可以通过使用唯一前缀命名回调函数来避免此问题,例如myplugin_myendpoint_避免任何潜在的冲突:

function myplugin_myendpoint_register_routes() { /* ... */ }
function myplugin_myendpoint_get_item() { /* ... */ }
function myplugin_myendpoint_get_item_schema() { /* ... */ }
// etcetera

add_action( 'rest_api_init', 'myplugin_myendpoint_register_routes' );

您可能已经熟悉这种方法,因为它通常在主题functions.php文件中使用。然而,这些前缀不必要地冗长,并且有几个更好的选择可以以更易于维护的方式对端点的逻辑进行分组和封装。

WordPress 目前需要 PHP 5.6 或更高版本。PHP 5.6 支持命名空间,它提供了一种封装端点功能的简单方法。namespace通过在端点的 PHP 文件顶部声明 a ,该命名空间内的所有方法都将在该命名空间内声明,并且不再与全局函数冲突。然后,您可以为端点回调使用更短、更易读的名称。

namespace MyPlugin\API\MyEndpoint;

function register_routes() { /* ... */ }
function get_item() { /* ... */ }
function get_item_schema() { /* ... */ }
// and so on

add_action( 'rest_api_init', __NAMESPACE__ . '\\register_routes' );

虽然这些较短的函数名称更易于使用,但与声明全局函数相比,它们没有提供任何其他好处。出于这个原因,WordPress 中的核心 REST API 端点都是使用_控制器类_实现的。

本页的其余部分详细介绍了如何编写您自己的控制器类并解释了这样做的优点。

控制器

控制器接收输入(一个WP_REST_Request对象,在 WordPress REST API 的情况下)并生成响应输出作为WP_REST_Response对象。让我们看一个示例控制器类:

class My_REST_Posts_Controller {

    // Here initialize our namespace and resource name.
    public function __construct() {
        $this->namespace     = '/my-namespace/v1';
        $this->resource_name = 'posts';
    }

    // Register our routes.
    public function register_routes() {
        register_rest_route( $this->namespace, '/' . $this->resource_name, array(
            // Here we register the readable endpoint for collections.
            array(
                'methods'   => 'GET',
                'callback'  => array( $this, 'get_items' ),
                'permission_callback' => array( $this, 'get_items_permissions_check' ),
            ),
            // Register our schema callback.
            'schema' => array( $this, 'get_item_schema' ),
        ) );
        register_rest_route( $this->namespace, '/' . $this->resource_name . '/(?P<id>[\d]+)', array(
            // Notice how we are registering multiple endpoints the 'schema' equates to an OPTIONS request.
            array(
                'methods'   => 'GET',
                'callback'  => array( $this, 'get_item' ),
                'permission_callback' => array( $this, 'get_item_permissions_check' ),
            ),
            // Register our schema callback.
            'schema' => array( $this, 'get_item_schema' ),
        ) );
    }

    /**
     * Check permissions for the posts.
     *
     * @param WP_REST_Request $request Current request.
     */
    public function get_items_permissions_check( $request ) {
        if ( ! current_user_can( 'read' ) ) {
            return new WP_Error( 'rest_forbidden', esc_html__( 'You cannot view the post resource.' ), array( 'status' => $this->authorization_status_code() ) );
        }
        return true;
    }

    /**
     * Grabs the five most recent posts and outputs them as a rest response.
     *
     * @param WP_REST_Request $request Current request.
     */
    public function get_items( $request ) {
        $args = array(
            'post_per_page' => 5,
        );
        $posts = get_posts( $args );

        $data = array();

        if ( empty( $posts ) ) {
            return rest_ensure_response( $data );
        }

        foreach ( $posts as $post ) {
            $response = $this->prepare_item_for_response( $post, $request );
            $data[] = $this->prepare_response_for_collection( $response );
        }

        // Return all of our comment response data.
        return rest_ensure_response( $data );
    }

    /**
     * Check permissions for the posts.
     *
     * @param WP_REST_Request $request Current request.
     */
    public function get_item_permissions_check( $request ) {
        if ( ! current_user_can( 'read' ) ) {
            return new WP_Error( 'rest_forbidden', esc_html__( 'You cannot view the post resource.' ), array( 'status' => $this->authorization_status_code() ) );
        }
        return true;
    }

    /**
     * Gets post data of requested post id and outputs it as a rest response.
     *
     * @param WP_REST_Request $request Current request.
     */
    public function get_item( $request ) {
        $id = (int) $request['id'];
        $post = get_post( $id );

        if ( empty( $post ) ) {
            return rest_ensure_response( array() );
        }

        $response = $this->prepare_item_for_response( $post, $request );

        // Return all of our post response data.
        return $response;
    }

    /**
     * Matches the post data to the schema we want.
     *
     * @param WP_Post $post The comment object whose response is being prepared.
     */
    public function prepare_item_for_response( $post, $request ) {
        $post_data = array();

        $schema = $this->get_item_schema( $request );

        // We are also renaming the fields to more understandable names.
        if ( isset( $schema['properties']['id'] ) ) {
            $post_data['id'] = (int) $post->ID;
        }

        if ( isset( $schema['properties']['content'] ) ) {
            $post_data['content'] = apply_filters( 'the_content', $post->post_content, $post );
        }

        return rest_ensure_response( $post_data );
    }

    /**
     * Prepare a response for inserting into a collection of responses.
     *
     * This is copied 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.
     */
    public function prepare_response_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;
    }

    /**
     * Get our sample schema for a post.
     *
     * @return array The sample schema for a post
     */
    public function get_item_schema() {
        if ( $this->schema ) {
            // Since WordPress 5.3, the schema can be cached in the $schema property.
            return $this->schema;
        }

        $this->schema = array(
            // This tells the spec of JSON Schema we are using which is draft 4.
            '$schema'              => 'http://json-schema.org/draft-04/schema#',
            // The title property marks the identity of the resource.
            'title'                => 'post',
            'type'                 => 'object',
            // In JSON Schema you can specify object properties in the properties attribute.
            'properties'           => array(
                'id' => array(
                    'description'  => esc_html__( 'Unique identifier for the object.', 'my-textdomain' ),
                    'type'         => 'integer',
                    'context'      => array( 'view', 'edit', 'embed' ),
                    'readonly'     => true,
                ),
                'content' => array(
                    'description'  => esc_html__( 'The content for the object.', 'my-textdomain' ),
                    'type'         => 'string',
                ),
            ),
        );

        return $this->schema;
    }

    // Sets up the proper HTTP status code for authorization.
    public function authorization_status_code() {

        $status = 401;

        if ( is_user_logged_in() ) {
            $status = 403;
        }

        return $status;
    }
}

// Function to register our new routes from the controller.
function prefix_register_my_rest_routes() {
    $controller = new My_REST_Posts_Controller();
    $controller->register_routes();
}

add_action( 'rest_api_init', 'prefix_register_my_rest_routes' );

上课的好处

此类包含您可能使用简单函数编写的所有相同组件。类的结构为我们提供了一种使用语法引用相关方法的便捷方式$this->method_name(),但与命名空间不同的是,该类还允许我们缓存值和共享逻辑。

在该get_item_schema方法中,请注意我们将生成的模式存储在类中作为$this->schema. 类属性使缓存这些生成的值变得容易。WordPress 5.3 中模式缓存的引入将一些核心 REST API 收集响应的速度提高了 40%,因此您绝对应该考虑在您自己的控制器中遵循这种模式。

类继承 & WP_REST_Controller

我们在上面看到了类如何解决全局函数封装问题,以及类实例如何用于缓存复杂值以加速响应处理。类的另一个主要优点是类继承允许您在多个端点之间共享逻辑的方式。

我们这里的示例类没有扩展任何基类,但在 WordPress 核心中,所有端点控制器都扩展了一个abstract名为WP_REST_Controller. 扩展此类可让您访问许多有用的方法,包括但不限于:

  • prepare_response_for_collection():准备插入到集合中的响应。
  • add_additional_fields_to_object():将任何已注册的 REST 字段附加到您准备好的响应对象。
  • get_fields_for_response():检查_fields查询参数以确定请求了哪些响应字段。
  • get_context_param():检索context参数。
  • filter_response_by_context():根据提供的上下文参数过滤响应形状。
  • get_collection_params():返回一组对收集端点有用的基本参数定义。

特定于端点的方法,如get_itemregister_routesupdate_item_permissions_check未由抽象类完全实现,必须在您自己的类中定义。

访问WP_REST_Controller类参考页面以获取此控制器方法的完整列表。

重要的是要注意它WP_REST_Controller是作为一个abstract类实现的,并且只包含多个类中明确需要的逻辑。继承将您的类耦合到它扩展的基类,而考虑不周的继承树会使您的端点更难维护。

例如,如果您为帖子端点编写了一个控制器类(如上面的示例)并且还想支持自定义帖子类型,则您不应该My_REST_Posts_Controller这样扩展您的: class My_CPT_REST_Controller extends My_REST_Posts_Controller. 相反,您应该为共享逻辑创建一个完全独立的基控制器类,或者处理My_REST_Posts_Controller所有可用的帖子类型。端点逻辑受不断变化的业务需求的影响,并且您不希望每次更新基本帖子控制器时都必须更改许多不相关的控制器。

在大多数情况下,您将希望创建一个基控制器类作为每个端点控制器可以实现或扩展的一个interface或一个abstract class,或者直接扩展核心 WordPress REST 类之一。

内部 WordPress REST API 类

WordPress REST API 遵循其内部类的精心设计模式,可以将其归类为_基础设施类_或_端点_类。

端点类封装了对 WordPress 资源执行CRUD操作所需的功能逻辑。WordPress 公开了许多 REST API 端点(例如WP_REST_Posts_Controller),但正如上面所讨论的,所有端点都从一个公共基控制器类扩展:

  • WP_REST_Controller:所有 WordPress 核心端点的基类。此类旨在表示用于操作 WordPress 资源的一致模式。当与实现 的端点交互时WP_REST_Controller,HTTP 客户端可以期望每个端点以一致的方式运行。

基础结构类支持端点类。它们处理 WordPress REST API 的逻辑而不执行任何数据转换。WordPress REST API 实现了三个关键的基础设施类:

  • WP_REST_Server:WordPress REST API 的主控制器。路由在 WordPress 中注册到服务器。当WP_REST_Server被要求为请求提供服务时,它会确定要调用哪个路由,并将路由回调传递给一个WP_REST_Request对象。WP_REST_Server还处理身份验证,并可以执行请求验证和权限检查。
  • WP_REST_Request:表示请求性质的对象。此对象包括请求详细信息,如请求标头、参数和方法,以及路由。它还可以执行请求验证和清理。
  • WP_REST_Response:表示响应性质的对象。此类扩展WP_HTTP_Response,其中包括标头、正文和状态,并提供辅助方法,例如add_link()添加链接媒体和query_navigation_headers()获取查询导航标头。

大多数类型的 API 驱动的应用程序不需要您扩展基础设施层或直接与基础设施层交互,但是如果您要实现自己的 REST API 端点,您的应用程序可能会受益于一个或多个扩展WP_REST_Controller.