<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[57315] trunk: Editor: Support deferred block variation initialization on the server.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/57315">57315</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/57315","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>joemcgill</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-01-19 20:52:06 +0000 (Fri, 19 Jan 2024)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Editor: Support deferred block variation initialization on the server.
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 <a href="https://core.trac.wordpress.org/ticket/59969">#59969</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpblocktypephp">trunk/src/wp-includes/class-wp-block-type.php</a></li>
<li><a href="#trunktestsphpunittestsblockswpBlockTypephp">trunk/tests/phpunit/tests/blocks/wpBlockType.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestblocktypecontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-block-type-controller.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpblocktypephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-block-type.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-block-type.php 2024-01-19 19:01:29 UTC (rev 57314)
+++ trunk/src/wp-includes/class-wp-block-type.php 2024-01-19 20:52:06 UTC (rev 57315)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -113,11 +113,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * Block variations.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 5.8.0
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @var array[]
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.5.0 Only accessible through magic getter. null by default.
+ * @var array[]|null
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- public $variations = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ private $variations = null;
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Block variations callback.
+ *
+ * @since 6.5.0
+ * @var callable|null
+ */
+ public $variation_callback = null;
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Custom CSS selectors for theme.json style generation.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 6.3.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -296,6 +305,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @type array|null $supports Supported features.
</span><span class="cx" style="display: block; padding: 0 10px"> * @type array|null $example Structured data for the block preview.
</span><span class="cx" style="display: block; padding: 0 10px"> * @type callable|null $render_callback Block type render callback.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @type callable|null $variation_callback Block type variations callback.
</ins><span class="cx" style="display: block; padding: 0 10px"> * @type array|null $attributes Block type attributes property schemas.
</span><span class="cx" style="display: block; padding: 0 10px"> * @type string[] $uses_context Context values inherited by blocks of this type.
</span><span class="cx" style="display: block; padding: 0 10px"> * @type string[]|null $provides_context Context provided by blocks of this type.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -325,6 +335,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * null when value not found, or void when unknown property name provided.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function __get( $name ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'variations' === $name ) {
+ return $this->get_variations();
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> return;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -353,6 +367,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * or false otherwise.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function __isset( $name ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'variations' === $name ) {
+ return true;
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> return false;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -372,6 +390,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @param mixed $value Property value.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function __set( $name, $value ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'variations' === $name ) {
+ $this->variations = $value;
+ return;
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $this->{$name} = $value;
</span><span class="cx" style="display: block; padding: 0 10px"> return;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -540,4 +563,30 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->attributes :
</span><span class="cx" style="display: block; padding: 0 10px"> array();
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /**
+ * 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 );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsblockswpBlockTypephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/blocks/wpBlockType.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/blocks/wpBlockType.php 2024-01-19 19:01:29 UTC (rev 57314)
+++ trunk/tests/phpunit/tests/blocks/wpBlockType.php 2024-01-19 20:52:06 UTC (rev 57315)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -460,4 +460,196 @@
</span><span class="cx" style="display: block; padding: 0 10px"> array( '<!- - wp:core/separator -->', 0 ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /**
+ * @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' ),
+ );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestblocktypecontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-block-type-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-block-type-controller.php 2024-01-19 19:01:29 UTC (rev 57314)
+++ trunk/tests/phpunit/tests/rest-api/rest-block-type-controller.php 2024-01-19 20:52:06 UTC (rev 57315)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -735,6 +735,35 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @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' ),
+ );
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * The create_item() method does not exist for block types.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @doesNotPerformAssertions
</span></span></pre>
</div>
</div>
</body>
</html>