REST API: Only register one block renderer route.

Every block has a different set of attributes. These attributes are specified as a JSON Schema object. Previously, every block registered its own block renderer route using its attributes for the schema. This allowed for the attributes to be validated using the built in endpoint validation rules. It had the unfortunate side effect, however, of creating a large number of nearly identical REST API routes, one for each dynamic block. Each registered route has a performance impact. As the number of server side blocks goes up, this becomes more and more of an issue.

Now, we register a single block renderer route and dynamically validate the attributes based on the selected block.

Fixes #48079.
Props gziolo, TimothyBlynJacobs.


git-svn-id: https://develop.svn.wordpress.org/trunk@48069 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
TimothyBlynJacobs 2020-06-17 03:20:02 +00:00
parent 34b6e12529
commit 2b42ac5a12
4 changed files with 85 additions and 477 deletions

View File

@ -34,47 +34,52 @@ class WP_REST_Block_Renderer_Controller extends WP_REST_Controller {
* @see register_rest_route()
*/
public function register_routes() {
$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
foreach ( $block_types as $block_type ) {
if ( ! $block_type->is_dynamic() ) {
continue;
}
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<name>' . $block_type->name . ')',
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<name>[a-z0-9-]+/[a-z0-9-]+)',
array(
'args' => array(
'name' => array(
'description' => __( 'Unique registered name for the block.' ),
'type' => 'string',
),
),
array(
'args' => array(
'name' => array(
'description' => __( 'Unique registered name for the block.' ),
'type' => 'string',
'methods' => array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE ),
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
'attributes' => array(
'description' => __( 'Attributes for the block' ),
'type' => 'object',
'default' => array(),
'validate_callback' => static function ( $value, $request ) {
$block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] );
if ( ! $block ) {
// This will get rejected in ::get_item().
return true;
}
$schema = array(
'type' => 'object',
'properties' => $block->get_attributes(),
'additionalProperties' => false,
);
return rest_validate_value_from_schema( $value, $schema );
},
),
'post_id' => array(
'description' => __( 'ID of the post context.' ),
'type' => 'integer',
),
),
array(
'methods' => array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE ),
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
'attributes' => array(
/* translators: %s: The name of the block. */
'description' => sprintf( __( 'Attributes for %s block' ), $block_type->name ),
'type' => 'object',
'additionalProperties' => false,
'properties' => $block_type->get_attributes(),
'default' => array(),
),
'post_id' => array(
'description' => __( 'ID of the post context.' ),
'type' => 'integer',
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
@ -136,9 +141,11 @@ class WP_REST_Block_Renderer_Controller extends WP_REST_Controller {
// Set up postdata since this will be needed if post_id was set.
setup_postdata( $post );
}
$registry = WP_Block_Type_Registry::get_instance();
if ( null === $registry->get_registered( $request['name'] ) ) {
$registry = WP_Block_Type_Registry::get_instance();
$registered = $registry->get_registered( $request['name'] );
if ( null === $registered || ! $registered->is_dynamic() ) {
return new WP_Error(
'block_invalid',
__( 'Invalid block.' ),

View File

@ -46,6 +46,15 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca
*/
protected static $context_block_name = 'core/context-test-block';
/**
* Non dynamic block name.
*
* @since 5.5.0
*
* @var string
*/
protected static $non_dynamic_block_name = 'core/non-dynamic';
/**
* Test API user's ID.
*
@ -117,6 +126,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca
public function setUp() {
$this->register_test_block();
$this->register_post_context_test_block();
$this->register_non_dynamic_block();
parent::setUp();
}
@ -128,6 +138,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca
public function tearDown() {
WP_Block_Type_Registry::get_instance()->unregister( self::$block_name );
WP_Block_Type_Registry::get_instance()->unregister( self::$context_block_name );
WP_Block_Type_Registry::get_instance()->unregister( self::$non_dynamic_block_name );
parent::tearDown();
}
@ -175,6 +186,15 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca
);
}
/**
* Registers the non-dynamic block name.
*
* @since 5.5.0
*/
protected function register_non_dynamic_block() {
register_block_type( self::$non_dynamic_block_name );
}
/**
* Test render callback.
*
@ -210,9 +230,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca
$this->assertContains( self::$block_name, $dynamic_block_names );
$routes = rest_get_server()->get_routes();
foreach ( $dynamic_block_names as $dynamic_block_name ) {
$this->assertArrayHasKey( self::$rest_api_route . "(?P<name>$dynamic_block_name)", $routes );
}
$this->assertArrayHasKey( self::$rest_api_route . '(?P<name>[a-z0-9-]+/[a-z0-9-]+)', $routes );
}
/**
@ -261,7 +279,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
$this->assertErrorResponse( 'block_invalid', $response, 404 );
}
/**
@ -510,6 +528,19 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca
$this->assertErrorResponse( 'block_cannot_read', $response, 403 );
}
/**
* @ticket 48079
*/
public function test_get_item_non_dynamic_block() {
wp_set_current_user( self::$user_id );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$non_dynamic_block_name );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'block_invalid', $response, 404 );
}
/**
* Get item schema.
*

View File

@ -120,17 +120,7 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
'/wp/v2/comments',
'/wp/v2/comments/(?P<id>[\\d]+)',
'/wp/v2/search',
'/wp/v2/block-renderer/(?P<name>core/archives)',
'/wp/v2/block-renderer/(?P<name>core/block)',
'/wp/v2/block-renderer/(?P<name>core/calendar)',
'/wp/v2/block-renderer/(?P<name>core/categories)',
'/wp/v2/block-renderer/(?P<name>core/latest-comments)',
'/wp/v2/block-renderer/(?P<name>core/latest-posts)',
'/wp/v2/block-renderer/(?P<name>core/rss)',
'/wp/v2/block-renderer/(?P<name>core/search)',
'/wp/v2/block-renderer/(?P<name>core/shortcode)',
'/wp/v2/block-renderer/(?P<name>core/social-link)',
'/wp/v2/block-renderer/(?P<name>core/tag-cloud)',
'/wp/v2/block-renderer/(?P<name>[a-z0-9-]+/[a-z0-9-]+)',
'/wp/v2/settings',
'/wp/v2/themes',
);

View File

@ -4394,7 +4394,7 @@ mockedApiResponse.Schema = {
"self": "http://example.org/index.php?rest_route=/wp/v2/search"
}
},
"/wp/v2/block-renderer/(?P<name>core/archives)": {
"/wp/v2/block-renderer/(?P<name>[a-z0-9-]+/[a-z0-9-]+)": {
"namespace": "wp/v2",
"methods": [
"GET",
@ -4424,427 +4424,7 @@ mockedApiResponse.Schema = {
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/archives block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/block)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/block block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/calendar)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/calendar block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/categories)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/categories block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/latest-comments)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/latest-comments block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/latest-posts)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/latest-posts block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/rss)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/rss block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/search)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/search block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/shortcode)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/shortcode block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/social-link)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/social-link block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/tag-cloud)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET",
"POST"
],
"args": {
"name": {
"required": false,
"description": "Unique registered name for the block.",
"type": "string"
},
"context": {
"required": false,
"default": "view",
"enum": [
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"attributes": {
"required": false,
"default": [],
"description": "Attributes for core/tag-cloud block",
"description": "Attributes for the block",
"type": "object"
},
"post_id": {