From 547a08ee03905d2aab94b027722c1cedc3299c1f Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 30 Jan 2020 20:20:30 +0000 Subject: [PATCH] REST API: Reuse previously-generated embedded objects when building collection response. Store each generated embedded object in a temporary cache when querying for linked resources so that repeated links to the same resource do not trigger repeated queries or processing. Props TimothyBlynJacobs. Fixes #48838. git-svn-id: https://develop.svn.wordpress.org/trunk@47138 602fd350-edb4-49c9-b593-d223f7449a82 --- .../rest-api/class-wp-rest-server.php | 46 +++++--- tests/phpunit/tests/rest-api/rest-server.php | 102 ++++++++++++++++++ 2 files changed, 132 insertions(+), 16 deletions(-) diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index c869bdbc05..a14de5c225 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -78,6 +78,14 @@ class WP_REST_Server { */ protected $route_options = array(); + /** + * Caches embedded requests. + * + * @since 5.4.0 + * @var array + */ + protected $embed_cache = array(); + /** * Instantiates the REST server. * @@ -462,12 +470,14 @@ class WP_REST_Server { } if ( $embed ) { + $this->embed_cache = array(); // Determine if this is a numeric array. if ( wp_is_numeric_array( $data ) ) { $data = array_map( array( $this, 'embed_links' ), $data ); } else { $data = $this->embed_links( $data ); } + $this->embed_cache = array(); } return $data; @@ -588,24 +598,28 @@ class WP_REST_Server { continue; } - // Run through our internal routing and serve. - $request = WP_REST_Request::from_url( $item['href'] ); - if ( ! $request ) { - $embeds[] = array(); - continue; + if ( ! array_key_exists( $item['href'], $this->embed_cache ) ) { + // Run through our internal routing and serve. + $request = WP_REST_Request::from_url( $item['href'] ); + if ( ! $request ) { + $embeds[] = array(); + continue; + } + + // Embedded resources get passed context=embed. + if ( empty( $request['context'] ) ) { + $request['context'] = 'embed'; + } + + $response = $this->dispatch( $request ); + + /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ + $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request ); + + $this->embed_cache[ $item['href'] ] = $this->response_to_data( $response, false ); } - // Embedded resources get passed context=embed. - if ( empty( $request['context'] ) ) { - $request['context'] = 'embed'; - } - - $response = $this->dispatch( $request ); - - /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ - $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request ); - - $embeds[] = $this->response_to_data( $response, false ); + $embeds[] = $this->embed_cache[ $item['href'] ]; } // Determine if any real links were found. diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php index ec9870f033..671a860f07 100644 --- a/tests/phpunit/tests/rest-api/rest-server.php +++ b/tests/phpunit/tests/rest-api/rest-server.php @@ -677,6 +677,108 @@ class Tests_REST_Server extends WP_Test_REST_TestCase { $this->assertEquals( 403, $up_data['data']['status'] ); } + /** + * @ticket 48838 + */ + public function test_link_embedding_clears_cache() { + $post_id = self::factory()->post->create(); + + $response = new WP_REST_Response(); + $response->add_link( 'post', rest_url( 'wp/v2/posts/' . $post_id ), array( 'embeddable' => true ) ); + + $data = rest_get_server()->response_to_data( $response, true ); + $this->assertArrayHasKey( 'post', $data['_embedded'] ); + $this->assertCount( 1, $data['_embedded']['post'] ); + + wp_update_post( + array( + 'ID' => $post_id, + 'post_title' => 'My Awesome Title', + ) + ); + + $data = rest_get_server()->response_to_data( $response, true ); + $this->assertArrayHasKey( 'post', $data['_embedded'] ); + $this->assertCount( 1, $data['_embedded']['post'] ); + $this->assertEquals( 'My Awesome Title', $data['_embedded']['post'][0]['title']['rendered'] ); + } + + /** + * @ticket 48838 + */ + public function test_link_embedding_cache() { + $response = new WP_REST_Response( + array( + 'id' => 1, + ) + ); + $response->add_link( + 'author', + rest_url( 'wp/v2/users/1' ), + array( 'embeddable' => true ) + ); + $response->add_link( + 'author', + rest_url( 'wp/v2/users/1' ), + array( 'embeddable' => true ) + ); + + $mock = new MockAction(); + add_filter( 'rest_post_dispatch', array( $mock, 'filter' ) ); + + $data = rest_get_server()->response_to_data( $response, true ); + + $this->assertArrayHasKey( '_embedded', $data ); + $this->assertArrayHasKey( 'author', $data['_embedded'] ); + $this->assertCount( 2, $data['_embedded']['author'] ); + + $this->assertCount( 1, $mock->get_events() ); + } + + /** + * @ticket 48838 + */ + public function test_link_embedding_cache_collection() { + $response = new WP_REST_Response( + array( + array( + 'id' => 1, + '_links' => array( + 'author' => array( + array( + 'href' => rest_url( 'wp/v2/users/1' ), + 'embeddable' => true, + ), + ), + ), + ), + array( + 'id' => 2, + '_links' => array( + 'author' => array( + array( + 'href' => rest_url( 'wp/v2/users/1' ), + 'embeddable' => true, + ), + ), + ), + ), + ) + ); + + $mock = new MockAction(); + add_filter( 'rest_post_dispatch', array( $mock, 'filter' ) ); + + $data = rest_get_server()->response_to_data( $response, true ); + + $embeds = wp_list_pluck( $data, '_embedded' ); + $this->assertCount( 2, $embeds ); + $this->assertArrayHasKey( 'author', $embeds[0] ); + $this->assertArrayHasKey( 'author', $embeds[1] ); + + $this->assertCount( 1, $mock->get_events() ); + } + /** * Ensure embedding is a no-op without links in the data. */