Skip to content

Commit

Permalink
Editor: Support deferred block variation initialization on the server.
Browse files Browse the repository at this point in the history
When registering blocks on the server using `register_block_type()` or similar functions, a set of block type variations can also be registered. However, in some cases building this variation data during block registration can be an expensive process, which is not needed in most contexts. 

To address this problem, this adds support to the `WP_Block_Type` object for a new property, `variation_callback`, which can be used to register a callback for building variation data only when the block variations data is needed. The `WP_Block_Type::variations` property has been changed to a private property that is now accessed through the magic `__get()` method. The magic getter makes use of a new public method, `WP_Block_Type::get_variations` which will build variations from a registered callback if variations have not already been built.

Props spacedmonkey, thekt12, Mamaduka, gaambo, gziolo, mukesh27, joemcgill.
Fixes #59969.


git-svn-id: https://develop.svn.wordpress.org/trunk@57315 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
joemcgill committed Jan 19, 2024
1 parent ecbd137 commit 5815624
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 2 deletions.
53 changes: 51 additions & 2 deletions src/wp-includes/class-wp-block-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,18 @@ class WP_Block_Type {
* Block variations.
*
* @since 5.8.0
* @var array[]
* @since 6.5.0 Only accessible through magic getter. null by default.
* @var array[]|null
*/
public $variations = array();
private $variations = null;

/**
* Block variations callback.
*
* @since 6.5.0
* @var callable|null
*/
public $variation_callback = null;

/**
* Custom CSS selectors for theme.json style generation.
Expand Down Expand Up @@ -296,6 +305,7 @@ class WP_Block_Type {
* @type array|null $supports Supported features.
* @type array|null $example Structured data for the block preview.
* @type callable|null $render_callback Block type render callback.
* @type callable|null $variation_callback Block type variations callback.
* @type array|null $attributes Block type attributes property schemas.
* @type string[] $uses_context Context values inherited by blocks of this type.
* @type string[]|null $provides_context Context provided by blocks of this type.
Expand Down Expand Up @@ -325,6 +335,10 @@ public function __construct( $block_type, $args = array() ) {
* null when value not found, or void when unknown property name provided.
*/
public function __get( $name ) {
if ( 'variations' === $name ) {
return $this->get_variations();
}

if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
return;
}
Expand Down Expand Up @@ -353,6 +367,10 @@ public function __get( $name ) {
* or false otherwise.
*/
public function __isset( $name ) {
if ( 'variations' === $name ) {
return true;
}

if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
return false;
}
Expand All @@ -372,6 +390,11 @@ public function __isset( $name ) {
* @param mixed $value Property value.
*/
public function __set( $name, $value ) {
if ( 'variations' === $name ) {
$this->variations = $value;
return;
}

if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
$this->{$name} = $value;
return;
Expand Down Expand Up @@ -540,4 +563,30 @@ public function get_attributes() {
$this->attributes :
array();
}

/**
* Get block variations.
*
* @since 6.5.0
*
* @return array[]
*/
public function get_variations() {
if ( ! isset( $this->variations ) ) {
$this->variations = array();
if ( is_callable( $this->variation_callback ) ) {
$this->variations = call_user_func( $this->variation_callback );
}
}

/**
* Filters the registered variations for a block type.
*
* @since 6.5.0
*
* @param array $variations Array of registered variations for a block type.
* @param WP_Block_Type $block_type The full block type object.
*/
return apply_filters( 'get_block_type_variations', $this->variations, $this );
}
}
192 changes: 192 additions & 0 deletions tests/phpunit/tests/blocks/wpBlockType.php
Original file line number Diff line number Diff line change
Expand Up @@ -460,4 +460,196 @@ public function data_block_version() {
array( '<!- - wp:core/separator -->', 0 ),
);
}

/**
* @ticket 59969
*/
public function test_variation_callback() {
$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variation_callback' => array( $this, 'mock_variation_callback' ),
)
);

$this->assertSameSets( $this->mock_variation_callback(), $block_type->variations );
}

/**
* @ticket 59969
* @covers WP_Block_Type::get_variations
*/
public function test_get_variations() {
$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variation_callback' => array( $this, 'mock_variation_callback' ),
)
);

$this->assertSameSets( $this->mock_variation_callback(), $block_type->get_variations() );
}

/**
* @ticket 59969
*/
public function test_variations_precedence_over_callback() {
$test_variations = array( 'name' => 'test1' );

$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variations' => $test_variations,
'variation_callback' => array( $this, 'mock_variation_callback' ),
)
);

// If the variations are defined, the callback should not be used.
$this->assertSameSets( $test_variations, $block_type->variations );
}

/**
* @ticket 59969
*/
public function test_variations_callback_are_lazy_loaded() {
$callback_called = false;

$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variation_callback' => function () use ( &$callback_called ) {
$callback_called = true;
return $this->mock_variation_callback();
},
)
);

$this->assertSame( false, $callback_called, 'The callback should not be called before the variations are accessed.' );
$block_type->variations; // access the variations.
$this->assertSame( true, $callback_called, 'The callback should be called when the variations are accessed.' );
}

/**
* @ticket 59969
* @covers WP_Block_Type::get_variations
*/
public function test_variations_precedence_over_callback_post_registration() {
$test_variations = array( 'name' => 'test1' );
$callback_called = false;

$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variation_callback' => function () use ( &$callback_called ) {
$callback_called = true;
return $this->mock_variation_callback();
},
)
);
$block_type->variations = $test_variations;

// If the variations are defined after registration but before first access, the callback should not override it.
$this->assertSameSets( $test_variations, $block_type->get_variations(), 'Variations are same as variations set' );
$this->assertSame( false, $callback_called, 'The callback was never called.' );
}

/**
* @ticket 59969
* @covers WP_Block_Type::get_variations
*/
public function test_variations_callback_happens_only_once() {
$callback_count = 0;

$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variation_callback' => function () use ( &$callback_count ) {
$callback_count++;
return $this->mock_variation_callback();
},
)
);

$this->assertSame( 0, $callback_count, 'The callback should not be called before the variations are accessed.' );
$block_type->get_variations(); // access the variations.
$this->assertSame( 1, $callback_count, 'The callback should be called when the variations are accessed.' );
$block_type->get_variations(); // access the variations again.
$this->assertSame( 1, $callback_count, 'The callback should not be called again.' );
}

/**
* Test filter function for get_block_type_variations filter.
*
* @param array $variations Block variations before filter.
* @param WP_Block_Type $block_type Block type.
*
* @return array Block variations after filter.
*/
public function filter_test_variations( $variations, $block_type ) {
return array( array( 'name' => 'test1' ) );
}

/**
* @ticket 59969
*/
public function test_get_block_type_variations_filter_with_variation_callback() {
// Filter will override the variations obtained from the callback.
add_filter( 'get_block_type_variations', array( $this, 'filter_test_variations' ), 10, 2 );
$expected_variations = array( array( 'name' => 'test1' ) );

$callback_called = false;
$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variation_callback' => function () use ( &$callback_called ) {
$callback_called = true;
return $this->mock_variation_callback();
},
)
);

$obtained_variations = $block_type->variations; // access the variations.

$this->assertSame( true, $callback_called, 'The callback should be called when the variations are accessed.' );
$this->assertSameSets( $obtained_variations, $expected_variations, 'The variations obtained from the callback should be filtered.' );
}

/**
* @ticket 59969
*/
public function test_get_block_type_variations_filter_variations() {
// Filter will override the variations set during registration.
add_filter( 'get_block_type_variations', array( $this, 'filter_test_variations' ), 10, 2 );
$expected_variations = array( array( 'name' => 'test1' ) );

$block_type = new WP_Block_Type(
'test/block',
array(
'title' => 'Test title',
'variations' => $this->mock_variation_callback(),
)
);

$obtained_variations = $block_type->variations; // access the variations.
$this->assertSameSets( $obtained_variations, $expected_variations, 'The variations that was initially set should be filtered.' );
}

/**
* Mock variation callback.
*
* @return array
*/
public function mock_variation_callback() {
return array(
array( 'name' => 'var1' ),
array( 'name' => 'var2' ),
);
}
}
29 changes: 29 additions & 0 deletions tests/phpunit/tests/rest-api/rest-block-type-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,35 @@ protected function check_block_type_object( $block_type, $data, $links ) {
}
}

/**
* @ticket 59969
*/
public function test_variation_callback() {
$block_type = 'test/block';
$settings = array(
'title' => true,
'variation_callback' => array( $this, 'mock_variation_callback' ),
);
register_block_type( $block_type, $settings );
wp_set_current_user( self::$admin_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/block-types/' . $block_type );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertSameSets( $this->mock_variation_callback(), $data['variations'] );
}

/**
* Mock variation callback.
*
* @return array
*/
public function mock_variation_callback() {
return array(
array( 'name' => 'var1' ),
array( 'name' => 'var2' ),
);
}

/**
* The create_item() method does not exist for block types.
*
Expand Down

0 comments on commit 5815624

Please sign in to comment.