Skip to content

Commit

Permalink
REST API: Introduce WP_Post_Type::get_rest_controller() caching metho…
Browse files Browse the repository at this point in the history
…d to prevent unnecessary REST controller construction.

Cache REST controller references on their associated post type object to prevent unnecessary controller re-instantiation, which previously caused "rest_prepare_{$post_type}" and "rest_{$post_type}_query" to run twice per request.

Props TimothyBlynJacobs, patrelentlesstechnologycom.
Fixes #45677.


git-svn-id: https://develop.svn.wordpress.org/trunk@46272 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
kadamwhite committed Sep 23, 2019
1 parent 509647e commit 49155e6
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 11 deletions.
42 changes: 42 additions & 0 deletions src/wp-includes/class-wp-post-type.php
Expand Up @@ -335,6 +335,16 @@ final class WP_Post_Type {
*/
public $rest_controller_class;

/**
* The controller instance for this post type's REST API endpoints.
*
* Lazily computed. Should be accessed using {@see WP_Post_Type::get_rest_controller()}.
*
* @since 5.3.0
* @var WP_REST_Controller $rest_controller
*/
private $rest_controller;

/**
* Constructor.
*
Expand Down Expand Up @@ -682,4 +692,36 @@ public function unregister_taxonomies() {
public function remove_hooks() {
remove_action( 'future_' . $this->name, '_future_post_hook', 5 );
}

/**
* Gets the REST API controller for this post type.
*
* Will only instantiate the controller class once per request.
*
* @since 5.3.0
*
* @return WP_REST_Controller|null The controller instance, or null if the post type
* is set not to show in rest.
*/
public function get_rest_controller() {
if ( ! $this->show_in_rest ) {
return null;
}

$class = $this->rest_controller_class ? $this->rest_controller_class : WP_REST_Posts_Controller::class;

if ( ! class_exists( $class ) ) {
return null;
}

if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) {
return null;
}

if ( ! $this->rest_controller ) {
$this->rest_controller = new $class( $this->name );
}

return $this->rest_controller;
}
}
8 changes: 2 additions & 6 deletions src/wp-includes/rest-api.php
Expand Up @@ -192,13 +192,9 @@ function rest_api_default_filters() {
*/
function create_initial_rest_routes() {
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
$class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
$controller = $post_type->get_rest_controller();

if ( ! class_exists( $class ) ) {
continue;
}
$controller = new $class( $post_type->name );
if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
if ( ! $controller ) {
continue;
}

Expand Down
Expand Up @@ -59,11 +59,13 @@ class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {
public function __construct( $parent_post_type ) {
$this->parent_post_type = $parent_post_type;
$post_type_object = get_post_type_object( $parent_post_type );
$parent_controller = $post_type_object->get_rest_controller();

// Ensure that post type-specific controller logic is available.
$parent_controller_class = ! empty( $post_type_object->rest_controller_class ) ? $post_type_object->rest_controller_class : 'WP_REST_Posts_Controller';
if ( ! $parent_controller ) {
$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
}

$this->parent_controller = new $parent_controller_class( $post_type_object->name );
$this->parent_controller = $parent_controller;
$this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
$this->rest_namespace = 'wp/v2';
$this->rest_base = 'autosaves';
Expand Down
Expand Up @@ -1592,8 +1592,14 @@ protected function handle_status_param( $new_status, $comment_id ) {
* @return bool Whether post can be read.
*/
protected function check_read_post_permission( $post, $request ) {
$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
$post_type = get_post_type_object( $post->post_type );
$posts_controller = $post_type->get_rest_controller();

// Ensure the posts controller is specifically a WP_REST_Posts_Controller instance
// before using methods specific to that controller.
if ( ! $posts_controller instanceof WP_REST_Posts_Controller ) {
$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
}

$has_password_filter = false;

Expand Down
Expand Up @@ -16,6 +16,14 @@
*/
class WP_REST_Posts_Controller extends WP_REST_Controller {

/**
* Instances of post type controllers keyed by post type.
*
* @since 5.3.0
* @var WP_REST_Controller[]
*/
private static $post_type_controllers = array();

/**
* Post type.
*
Expand Down
Expand Up @@ -49,11 +49,15 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
*/
public function __construct( $parent_post_type ) {
$this->parent_post_type = $parent_post_type;
$this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
$this->namespace = 'wp/v2';
$this->rest_base = 'revisions';
$post_type_object = get_post_type_object( $parent_post_type );
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
$this->parent_controller = $post_type_object->get_rest_controller();

if ( ! $this->parent_controller ) {
$this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
}
}

/**
Expand Down
81 changes: 81 additions & 0 deletions tests/phpunit/tests/rest-api/rest-posts-controller.php
Expand Up @@ -4526,6 +4526,87 @@ public function test_publishing_post_with_same_date_removes_floating_date() {
$this->assertNotEquals( '0000-00-00 00:00:00', get_post( $post->ID )->post_date_gmt );
}

/**
* @ticket 45677
*/
public function test_get_for_post_type_reuses_same_instance() {
$this->assertSame(
get_post_type_object( 'post' )->get_rest_controller(),
get_post_type_object( 'post' )->get_rest_controller()
);
}

/**
* @ticket 45677
*/
public function test_get_for_post_type_returns_null_if_post_type_does_not_show_in_rest() {
register_post_type(
'not_in_rest',
array(
'show_in_rest' => false,
)
);

$this->assertNull( get_post_type_object( 'not_in_rest' )->get_rest_controller() );
}

/**
* @ticket 45677
*/
public function test_get_for_post_type_returns_null_if_class_does_not_exist() {
register_post_type(
'class_not_found',
array(
'show_in_rest' => true,
'rest_controller_class' => 'Class_That_Does_Not_Exist',
)
);

$this->assertNull( get_post_type_object( 'class_not_found' )->get_rest_controller() );
}

/**
* @ticket 45677
*/
public function test_get_for_post_type_returns_null_if_class_does_not_subclass_rest_controller() {
register_post_type(
'invalid_class',
array(
'show_in_rest' => true,
'rest_controller_class' => 'WP_Post',
)
);

$this->assertNull( get_post_type_object( 'invalid_class' )->get_rest_controller() );
}

/**
* @ticket 45677
*/
public function test_get_for_post_type_returns_posts_controller_if_custom_class_not_specified() {
register_post_type(
'test',
array(
'show_in_rest' => true,
)
);

$this->assertInstanceOf(
WP_REST_Posts_Controller::class,
get_post_type_object( 'test' )->get_rest_controller()
);
}

/**
* @ticket 45677
*/
public function test_get_for_post_type_returns_provided_controller_class() {
$this->assertInstanceOf(
WP_REST_Blocks_Controller::class,
get_post_type_object( 'wp_block' )->get_rest_controller()
);
}

public function tearDown() {
_unregister_post_type( 'private-post' );
_unregister_post_type( 'youseeme' );
Expand Down

0 comments on commit 49155e6

Please sign in to comment.