From 722898f707ff7c6944a8f43b10b9aa7569735e83 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 23 Apr 2020 12:01:16 -0400 Subject: [PATCH 1/3] Framework: WP_Block: Use reflected type hint for render_callback --- .../developers/block-api/block-context.md | 2 +- .../block-tutorial/creating-dynamic-blocks.md | 8 +- .../metabox/meta-block-4-use-data.md | 2 +- lib/class-wp-block.php | 101 +++++++----------- .../block-library/src/post-title/index.php | 2 +- packages/e2e-tests/plugins/block-context.php | 2 +- phpunit/class-block-context-test.php | 2 +- phpunit/class-wp-block-test.php | 37 +------ 8 files changed, 48 insertions(+), 108 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-context.md b/docs/designers-developers/developers/block-api/block-context.md index 56289ef088c3e2..0c14642fb79bdf 100644 --- a/docs/designers-developers/developers/block-api/block-context.md +++ b/docs/designers-developers/developers/block-api/block-context.md @@ -69,7 +69,7 @@ A block's context values are available from the `context` property of the `$bloc ```php register_block_type( 'my-plugin/record-title', [ - 'render_callback' => function( $block ) { + 'render_callback' => function( WP_Block $block ) { return 'The current record ID is: ' . $block->context['my-plugin/recordId']; }, ] ); diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index 84a6fc4f1489f5..636196cf7f8211 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -99,7 +99,7 @@ Because it is a dynamic block it doesn't need to override the default `save` imp * Plugin Name: Gutenberg examples dynamic */ -function gutenberg_examples_dynamic_render_callback( $block, $content ) { +function gutenberg_examples_dynamic_render_callback( $block_attributes, $content ) { $recent_posts = wp_get_recent_posts( array( 'numberposts' => 1, 'post_status' => 'publish', @@ -143,11 +143,11 @@ There are a few things to notice: * The built-in `save` function just returns `null` because the rendering is performed server-side. * The server-side rendering is a function taking the block and the block inner content as arguments, and returning the markup (quite similar to shortcodes) -Note that for convenience and for backward-compatibility, the first argument of a `render_callback` function can also be referenced as an associative array of the block's attributes: +By default, the first argument of `render_callback` receives an associative array of the block's attributes. If you need access to the block instance itself, you can provide a [type declaration](https://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) in the function arguments as a hint that the render callback should receive the block. ```php -function gutenberg_examples_dynamic_render_callback( $block_attributes ) { - return 'The record ID is: ' . esc_html( $block_attributes['recordId'] ); +function gutenberg_examples_dynamic_render_callback( WP_Block $block ) { + return 'The block name is: ' . esc_html( $block->name ); } ``` diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md index e10049e16c2b90..e0f5ccd5f15905 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md @@ -25,7 +25,7 @@ You can also use the post meta data in other blocks. For this example the data i In PHP, use the [register_block_type](https://developer.wordpress.org/reference/functions/register_block_type/) function to set a callback when the block is rendered to include the meta value. ```php -function myguten_render_paragraph( $block, $content ) { +function myguten_render_paragraph( $block_attributes, $content ) { $value = get_post_meta( get_the_ID(), 'myguten_meta_block_field', true ); // check value is set before outputting if ( $value ) { diff --git a/lib/class-wp-block.php b/lib/class-wp-block.php index 4e3cf5f17989d0..67fbf717075266 100644 --- a/lib/class-wp-block.php +++ b/lib/class-wp-block.php @@ -5,10 +5,38 @@ * @package Gutenberg */ +/** + * Returns true if the given block type's `render_callback` includes a type hint + * on its first argument to receive an instance of WP_Block. + * + * UPSTREAM PORT NOTE: It's suggested that this be implemented as an instance + * method of the WP_Block_Type class (WP_Block_Type::is_rendered_with_block), + * optionally reusing or composing with the existing `is_dynamic` method. It + * would also be prudent to consider performance ramifications of reflection, + * especially if many blocks of the same type are rendered. Caching the result + * of this function to reuse for all rendering of the same block type would be + * wise if reflection is non-trivial. + * + * @param WP_Block_Type $block_type Block type to check. + * + * @return bool Whether block type render callback receives block instance. + */ +function gutenberg_block_type_render_callback_receives_block( $block_type ) { + $reflect = new ReflectionFunction( $block_type->render_callback ); + $params = $reflect->getParameters(); + + if ( 0 === count( $params ) ) { + return false; + } + + $first_arg_type = $params[0]->getType(); + return $first_arg_type && $first_arg_type->getName() === WP_Block::class; +} + /** * Class representing a parsed instance of a block. */ -class WP_Block implements ArrayAccess { +class WP_Block { /** * Name of block. @@ -169,72 +197,17 @@ public function render() { } if ( $is_dynamic ) { - $global_post = $post; - $block_content = (string) call_user_func( $this->block_type->render_callback, $this, $block_content ); - $post = $global_post; + $global_post = $post; + $receives_block = gutenberg_block_type_render_callback_receives_block( $this->block_type ); + $block_content = (string) call_user_func( + $this->block_type->render_callback, + $receives_block ? $this : $this->attributes, + $block_content + ); + $post = $global_post; } return $block_content; } - /** - * Returns true if an attribute exists by the specified attribute name, or - * false otherwise. - * - * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php - * - * @param string $attribute_name Name of attribute to check. - * - * @return bool Whether attribute exists. - */ - public function offsetExists( $attribute_name ) { - return isset( $this->attributes[ $attribute_name ] ); - } - - /** - * Returns the value by the specified attribute name. - * - * @link https://www.php.net/manual/en/arrayaccess.offsetget.php - * - * @param string $attribute_name Name of attribute value to retrieve. - * - * @return mixed|null Attribute value if exists, or null. - */ - public function offsetGet( $attribute_name ) { - // This may cause an "Undefined index" notice if the attribute name does - // not exist. This is expected, since the purpose of this implementation - // is to align exactly to the expectations of operating on an array. - return $this->attributes[ $attribute_name ]; - } - - /** - * Assign an attribute value by the specified attribute name. - * - * @link https://www.php.net/manual/en/arrayaccess.offsetset.php - * - * @param string $attribute_name Name of attribute value to set. - * @param mixed $value Attribute value. - */ - public function offsetSet( $attribute_name, $value ) { - if ( is_null( $attribute_name ) ) { - // This is not technically a valid use-case for attributes. Since - // this implementation is expected to align to expectations of - // operating on an array, it is still supported. - $this->attributes[] = $value; - } else { - $this->attributes[ $attribute_name ] = $value; - } - } - - /** - * Unset an attribute. - * - * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php - * - * @param string $attribute_name Name of attribute value to unset. - */ - public function offsetUnset( $attribute_name ) { - unset( $this->attributes[ $attribute_name ] ); - } - } diff --git a/packages/block-library/src/post-title/index.php b/packages/block-library/src/post-title/index.php index 5d0ea451ea0c4f..39b17bc7ab2c43 100644 --- a/packages/block-library/src/post-title/index.php +++ b/packages/block-library/src/post-title/index.php @@ -12,7 +12,7 @@ * * @return string Returns the filtered post title for the current post wrapped inside "h1" tags. */ -function render_block_core_post_title( $block ) { +function render_block_core_post_title( WP_Block $block ) { if ( ! isset( $block->context['postId'] ) ) { return ''; } diff --git a/packages/e2e-tests/plugins/block-context.php b/packages/e2e-tests/plugins/block-context.php index 13db6436630d30..2e8d57f8b0d367 100644 --- a/packages/e2e-tests/plugins/block-context.php +++ b/packages/e2e-tests/plugins/block-context.php @@ -48,7 +48,7 @@ function gutenberg_test_register_context_blocks() { 'gutenberg/test-context-consumer', array( 'context' => array( 'gutenberg/recordId' ), - 'render_callback' => function( $block ) { + 'render_callback' => function( WP_Block $block ) { $record_id = $block->context['gutenberg/recordId']; if ( ! is_int( $record_id ) ) { diff --git a/phpunit/class-block-context-test.php b/phpunit/class-block-context-test.php index e78f375345aef6..23424af8300236 100644 --- a/phpunit/class-block-context-test.php +++ b/phpunit/class-block-context-test.php @@ -104,7 +104,7 @@ function test_provides_block_context() { 'gutenberg/contextWithAssigned', 'gutenberg/contextWithoutDefault', ), - 'render_callback' => function( $block ) use ( &$provided_context ) { + 'render_callback' => function( WP_Block $block ) use ( &$provided_context ) { $provided_context[] = $block->context; return ''; diff --git a/phpunit/class-wp-block-test.php b/phpunit/class-wp-block-test.php index 242f96f9eb462c..24ccea5c8846e8 100644 --- a/phpunit/class-wp-block-test.php +++ b/phpunit/class-wp-block-test.php @@ -259,7 +259,7 @@ function test_render_passes_instance_to_render_callback() { 'default' => '!', ), ), - 'render_callback' => function( $block ) { + 'render_callback' => function( WP_Block $block ) { return sprintf( 'Hello %s%s', $block->attributes['toWhom'], @@ -312,7 +312,7 @@ function test_passes_content_to_render_callback() { $this->registry->register( 'core/outer', array( - 'render_callback' => function( $block, $content ) { + 'render_callback' => function( WP_Block $block, $content ) { return $content; }, ) @@ -334,37 +334,4 @@ function test_passes_content_to_render_callback() { $this->assertSame( 'abc', $block->render() ); } - function test_array_access_attributes() { - $this->registry->register( - 'core/example', - array( - 'attributes' => array( - 'value' => array( - 'type' => 'string', - ), - ), - ) - ); - $parsed_block = array( - 'blockName' => 'core/example', - 'attrs' => array( 'value' => 'ok' ), - ); - $context = array(); - $block = new WP_Block( $parsed_block, $context, $this->registry ); - - $this->assertTrue( isset( $block['value'] ) ); - $this->assertFalse( isset( $block['nonsense'] ) ); - $this->assertEquals( 'ok', $block['value'] ); - - $block['value'] = 'changed'; - $this->assertEquals( 'changed', $block['value'] ); - $this->assertEquals( 'changed', $block->attributes['value'] ); - - unset( $block['value'] ); - $this->assertFalse( isset( $block['value'] ) ); - - $block[] = 'invalid, but still supported'; - $this->assertEquals( 'invalid, but still supported', $block[0] ); - } - } From 14827902df29849befe362e39e91db6d037f6423 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 23 Apr 2020 15:02:46 -0400 Subject: [PATCH 2/3] Framework: WP_Block: Use ReflectionFunction::getClass More pointed to what's expected (a WP_Block class type), and more supported in PHP versions --- lib/class-wp-block.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-block.php b/lib/class-wp-block.php index 67fbf717075266..829e09b03fc84d 100644 --- a/lib/class-wp-block.php +++ b/lib/class-wp-block.php @@ -29,8 +29,8 @@ function gutenberg_block_type_render_callback_receives_block( $block_type ) { return false; } - $first_arg_type = $params[0]->getType(); - return $first_arg_type && $first_arg_type->getName() === WP_Block::class; + $first_param_class = $params[0]->getClass(); + return $first_param_class && $first_param_class->name === WP_Block::class; } /** From 00112649dd50003be38cb62262352ffda53e0fdd Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 23 Apr 2020 15:25:27 -0400 Subject: [PATCH 3/3] Framework: WP_Block: Use Yoda condition Tired, I am --- lib/class-wp-block.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-block.php b/lib/class-wp-block.php index 829e09b03fc84d..f9dc0c58076f38 100644 --- a/lib/class-wp-block.php +++ b/lib/class-wp-block.php @@ -30,7 +30,7 @@ function gutenberg_block_type_render_callback_receives_block( $block_type ) { } $first_param_class = $params[0]->getClass(); - return $first_param_class && $first_param_class->name === WP_Block::class; + return $first_param_class && WP_Block::class === $first_param_class->name; } /**