Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Framework: WP_Block: Use reflected type hint for render_callback #21833

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
},
] );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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 );
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down
101 changes: 37 additions & 64 deletions lib/class-wp-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -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_param_class = $params[0]->getClass();
return $first_param_class && WP_Block::class === $first_param_class->name;
}

/**
* Class representing a parsed instance of a block.
*/
class WP_Block implements ArrayAccess {
class WP_Block {

/**
* Name of block.
Expand Down Expand Up @@ -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 ] );
}

}
2 changes: 1 addition & 1 deletion packages/block-library/src/post-title/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
}
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e-tests/plugins/block-context.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ) {
Expand Down
2 changes: 1 addition & 1 deletion phpunit/class-block-context-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
Expand Down
37 changes: 2 additions & 35 deletions phpunit/class-wp-block-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down Expand Up @@ -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;
},
)
Expand All @@ -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] );
}

}