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

Backport: Templates perf: resolve patterns server side #6673

Closed
wants to merge 11 commits into from

Conversation

ellatrix
Copy link
Member

@ellatrix ellatrix commented May 29, 2024

Note that this replaces #6559, which I hadn't created from my fork.

Trac ticket: https://core.trac.wordpress.org/ticket/61228

This is a single backport for the following PRs:

To test: check the patterns endpoint and search for wp:pattern. There should be 0 results.

Screenshot 2024-05-16 at 11 23 14

This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

Copy link

github-actions bot commented May 29, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props ellatrix, antonvlasenko.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Copy link

@anton-vlasenko anton-vlasenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding PHPUnit tests would be beneficial since just looking at the source code makes it hard to tell if this function works as intended.

By the way, I noticed that the $inner_content function argument might not be necessary because it seems to only pass information that's already in the $blocks function argument.

What do you think?

Comment on lines +1352 to +1354
* @param array $blocks An array blocks.
*
* @return array An array of blocks with patterns replaced by their content.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I know, there’s no need to add an empty line between the @param and @return tags. Also, I noticed the description for the @inner_content function argument is missing. Could that be added?

Suggested change
* @param array $blocks An array blocks.
*
* @return array An array of blocks with patterns replaced by their content.
* @param array $blocks An array blocks.
* @param array $inner_content An optional array of inner content.
* @return array An array of blocks with patterns replaced by their content.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to be a private arg, not sure if there's a better way https://github.com/WordPress/wordpress-develop/pull/6673/files#r1623854851

Copy link

@anton-vlasenko anton-vlasenko Jun 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, there is no concept of a private argument in PHP. I'm also unsure how common it is to omit a function argument from a DocBlock if it is not intended for use.

However, there might be a solution to eliminate the need for the $inner_content function argument:

/**
 * Replaces patterns in a block tree with their content.
 *
 * @since 6.6.0
 *
 * @param array $blocks An array of blocks or a parent block.
 * @return array An array of blocks with patterns replaced by their content.
 */
function resolve_pattern_blocks( $blocks ) {
	// Keep track of seen references to avoid infinite loops.
	static $seen_refs = array();
	$i                = 0;
	$is_parent_block = ! empty( $blocks['innerBlocks'] );

	if ( $is_parent_block ) {
		// If it is a single block, $blocks must be reinitialized before it can be used.
		$block         = $blocks;
		$blocks        = $block['innerBlocks'];
		$inner_content = $block['innerContent'];
	}
	while ( $i < count( $blocks ) ) {
		if ( 'core/pattern' !== $blocks[ $i ]['blockName'] ) {
			if ( $is_parent_block ) {
				$blocks[ $i ] = resolve_pattern_blocks( $blocks[ $i ] );
			}
			++$i;
			continue;
		}
		$attrs = $blocks[ $i ]['attrs'];

		if ( empty( $attrs['slug'] ) ) {
			++$i;
			continue;
		}

		$slug = $attrs['slug'];

		if ( isset( $seen_refs[ $slug ] ) ) {
			// Skip recursive patterns.
			array_splice( $blocks, $i, 1 );
			continue;
		}

		$registry = WP_Block_Patterns_Registry::get_instance();
		$pattern  = $registry->get_registered( $slug );

		// Skip unknown patterns.
		if ( ! $pattern ) {
			++$i;
			continue;
		}

		$blocks_to_insert   = parse_blocks( $pattern['content'] );
		$seen_refs[ $slug ] = true;
		$blocks_to_insert   = resolve_pattern_blocks( $blocks_to_insert );
		unset( $seen_refs[ $slug ] );
		array_splice( $blocks, $i, 1, $blocks_to_insert );

		// If we have inner content, we need to insert nulls in the
		// inner content array, otherwise serialize_blocks will skip
		// blocks.
		if ( !empty( $inner_content ) ) {
			$null_indices  = array_keys( $inner_content, null, true );
			$content_index = $null_indices[ $i ];
			$nulls         = array_fill( 0, count( $blocks_to_insert ), null );
			array_splice( $inner_content, $content_index, 1, $nulls );
		}

		// Skip inserted blocks.
		$i += count( $blocks_to_insert );
	}

	if ( $is_parent_block ) {
		$block['innerBlocks']  = $blocks;
		$block['innerContent'] = $inner_content;
		return $block;
	}

	return $blocks;
}

Probably not optimal, but it passes the tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh, I find it a bit more confusing. Also, why is it now sometimes returning a blocks array and sometimes one block?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh, I find it a bit more confusing.

I feel the same way.

why is it now sometimes returning a blocks array and sometimes one block?

I made this change to eliminate the $inner_content parameter, aiming to streamline the function's interface.
I've tried to refactor this function into two separate functions, but it didn't go well either. This likely indicates that the current structure might be the most efficient approach.

// If we have inner content, we need to insert nulls in the
// inner content array, otherwise serialize_blocks will skip
// blocks.
if ( $inner_content ) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If @inner_content is expected to be an array, it might be a good idea to explicitly check to ensure it is an array to avoid PHP warnings. Is there a guarantee that $blocks[$i]['innerBlocks'] is always an array?

Suggested change
if ( $inner_content ) {
if ( is_array( $inner_content ) && $inner_content ) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is always an array, yes, there's also an empty check below. Also this argument is meant to be private, not sure if there's a better way to do it.

@ellatrix
Copy link
Member Author

@anton-vlasenko Yes, it needs unit tests, but I'm unfamiliar with unit test in PHP and core. Would you be able to assist?

@anton-vlasenko
Copy link

@anton-vlasenko Yes, it needs unit tests, but I'm unfamiliar with unit test in PHP and core. Would you be able to assist?

Yes, I can look into it.

@ellatrix
Copy link
Member Author

ellatrix commented Jun 3, 2024

By the way, I noticed that the $inner_content function argument might not be necessary because it seems to only pass information that's already in the $blocks function argument.

I inline commented on that, it is necessary to change because serialise will break if it's not adjusted

@ellatrix
Copy link
Member Author

ellatrix commented Jun 3, 2024

@anton-vlasenko Added unit tests

Copy link

@anton-vlasenko anton-vlasenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been testing this PR for some time and it worked as intended. The only change I would make is to remove the else operator, as it's not needed and allows to remove nesting. However, this is not a blocker:

function resolve_pattern_blocks( $blocks, &$inner_content = null ) {
	// Keep track of seen references to avoid infinite loops.
	static $seen_refs = array();
	$i                = 0;
	while ( $i < count( $blocks ) ) {
		if ( 'core/pattern' !== $blocks[ $i ]['blockName'] ) {
			if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) {
				$blocks[ $i ]['innerBlocks'] = resolve_pattern_blocks(
					$blocks[ $i ]['innerBlocks'],
					$blocks[ $i ]['innerContent']
				);
			}
			++ $i;
			continue;
		}

		$attrs = $blocks[ $i ]['attrs'];

		if ( empty( $attrs['slug'] ) ) {
			++$i;
			continue;
		}

		$slug = $attrs['slug'];

		if ( isset( $seen_refs[ $slug ] ) ) {
			// Skip recursive patterns.
			array_splice( $blocks, $i, 1 );
			continue;
		}

		$registry = WP_Block_Patterns_Registry::get_instance();
		$pattern  = $registry->get_registered( $slug );

		// Skip unknown patterns.
		if ( ! $pattern ) {
			++$i;
			continue;
		}

		$blocks_to_insert   = parse_blocks( $pattern['content'] );
		$seen_refs[ $slug ] = true;
		$blocks_to_insert   = resolve_pattern_blocks( $blocks_to_insert );
		unset( $seen_refs[ $slug ] );
		array_splice( $blocks, $i, 1, $blocks_to_insert );

		// If we have inner content, we need to insert nulls in the
		// inner content array, otherwise serialize_blocks will skip
		// blocks.
		if ( $inner_content ) {
			$null_indices  = array_keys( $inner_content, null, true );
			$content_index = $null_indices[ $i ];
			$nulls         = array_fill( 0, count( $blocks_to_insert ), null );
			array_splice( $inner_content, $content_index, 1, $nulls );
		}

		// Skip inserted blocks.
		$i += count( $blocks_to_insert );
	}
	return $blocks;
}

pento pushed a commit that referenced this pull request Jun 3, 2024
See WordPress/gutenberg#60349.
See WordPress/gutenberg#61757.
See #6673.

Fixes #61228.

Props ellatrix, antonvlasenko.



git-svn-id: https://develop.svn.wordpress.org/trunk@58303 602fd350-edb4-49c9-b593-d223f7449a82
@ellatrix ellatrix closed this Jun 3, 2024
@ellatrix ellatrix deleted the backport/60349 branch June 3, 2024 18:31
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Jun 3, 2024
github-actions bot pushed a commit to platformsh/wordpress-performance that referenced this pull request Jun 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants