mirrored from git://develop.git.wordpress.org/
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Extend Block Metadata PHP Cache to Third-Party Blocks #7303
Closed
Closed
Changes from all commits
Commits
Show all changes
58 commits
Select commit
Hold shift + click to select a range
5f5fbd2
add WP_Block_Metadata_Registry, allow blocks to bypass block.json par…
mreishus 8a28294
update
mreishus 8ca134d
add metadata_source
mreishus 270d784
formatting fixes
mreishus 9376059
add trailing comma
mreishus d503724
horizontal align
mreishus c5b81f5
remove namespace
mreishus a914dee
remove third parameter
mreishus a25c643
make static
mreishus bb6fec3
allow metadata source to be passed to register_block_type
mreishus c7cb99a
fix alignment
mreishus 14b1c56
undo everything
mreishus f75c4fd
new implementation
mreishus 12c01b8
add tests
mreishus ae81aa6
align equals
mreishus 9779e78
fix broken test
mreishus 15c51a6
move the collection and block identification logic to the WP_Block_Me…
mreishus 2c3630b
lint fix
mreishus 1fa5877
guard against incorrect paths
mreishus a4c1455
indent fix
mreishus 26c6b0c
lint fix
mreishus 191c553
Call into WP_Block_Metadata_Registry:: looking for metadata only once…
mreishus 43915a8
add $last_matched_collection
mreishus abbf5d5
align equals
mreishus 8d7badc
update comments
mreishus 0ca6c93
variable rename
mreishus 70431b2
update comment
mreishus 504e09c
remove private constructor
mreishus db5dee2
add _doing_it_wrong calls
mreishus 61acb57
fix broken tests
mreishus 4cd8fcc
Remove $identifier_callback option
mreishus 3b13d45
comment updates
mreishus 396e6c9
make default_identifier_callback private
mreishus 78a46fb
standardize _doing_it_wrong messages
mreishus d948dde
reorganize top of register_block_type_from_metadata
mreishus 15353f4
fix one more since tag
mreishus a3b73f3
update comment
mreishus 3a5ad69
fix elseif
mreishus 3b70192
default_identifier_callback: Use str_ends_with and return early if empty
mreishus 166ed80
Update src/wp-includes/blocks.php
mreishus 63772f1
Add Tests_Blocks_RegisterBlockTypeFromMetadataWithRegistry
mreishus b069b60
retab
mreishus 2e44638
add trailing commas
mreishus 779a2e8
align arrows and equals
mreishus a9d6eb9
let core blocks skip the file_exists check
mreishus a257e6f
minor change to force test rerun
mreishus 3e4d5b6
Revert "minor change to force test rerun"
mreishus 3a55080
register_collection: Use static variables to reduce calls to wp_norma…
mreishus 6584762
extract to get_include_path and get_plugin_dir functions
mreishus f2e31d7
allow registering for themes
mreishus 2d7533b
stop someone from registering the entire themes directory
mreishus 498bbba
get_include_path -> get_wpinc_dir
mreishus c23cf0a
Revert "stop someone from registering the entire themes directory"
mreishus 7fd96b4
Revert "allow registering for themes"
mreishus 9aa134d
Merge branch 'trunk' into add/metadata-register
felixarntz a323758
Rename wpinc_path to wpinc_dir and clarify docs.
felixarntz 6467b31
Use isset instead of empty.
felixarntz 31809da
Make conditional logic more intuitive.
felixarntz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
<?php | ||
/** | ||
* Block Metadata Registry | ||
* | ||
* @package WordPress | ||
* @subpackage Blocks | ||
* @since 6.7.0 | ||
*/ | ||
|
||
/** | ||
* Class used for managing block metadata collections. | ||
* | ||
* The WP_Block_Metadata_Registry allows plugins to register metadata for large | ||
* collections of blocks (e.g., 50-100+) using a single PHP file. This approach | ||
* reduces the need to read and decode multiple `block.json` files, enhancing | ||
* performance through opcode caching. | ||
* | ||
* @since 6.7.0 | ||
*/ | ||
class WP_Block_Metadata_Registry { | ||
|
||
/** | ||
* Container for storing block metadata collections. | ||
* | ||
* Each entry maps a base path to its corresponding metadata and callback. | ||
* | ||
* @since 6.7.0 | ||
* @var array<string, array<string, mixed>> | ||
*/ | ||
private static $collections = array(); | ||
|
||
/** | ||
* Caches the last matched collection path for performance optimization. | ||
* | ||
* @since 6.7.0 | ||
* @var string|null | ||
*/ | ||
private static $last_matched_collection = null; | ||
|
||
/** | ||
* Stores the WordPress 'wp-includes' directory path. | ||
* | ||
* @since 6.7.0 | ||
* @var string|null | ||
*/ | ||
private static $wpinc_dir = null; | ||
|
||
/** | ||
* Stores the normalized WordPress plugin directory path. | ||
* | ||
* @since 6.7.0 | ||
* @var string|null | ||
*/ | ||
private static $plugin_dir = null; | ||
|
||
/** | ||
* Registers a block metadata collection. | ||
* | ||
* This method allows registering a collection of block metadata from a single | ||
* manifest file, improving performance for large sets of blocks. | ||
* | ||
* The manifest file should be a PHP file that returns an associative array, where | ||
* the keys are the block identifiers (without their namespace) and the values are | ||
* the corresponding block metadata arrays. The block identifiers must match the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The documentation included is very helpful. I think I fully understand now how it all would work. If there were any assets (CSS, JS, render.php, etc), they probably should be located in these subfolders. |
||
* parent directory name for the respective `block.json` file. | ||
* | ||
* Example manifest file structure: | ||
* ``` | ||
* return array( | ||
* 'example-block' => array( | ||
* 'title' => 'Example Block', | ||
* 'category' => 'widgets', | ||
* 'icon' => 'smiley', | ||
* // ... other block metadata | ||
* ), | ||
* 'another-block' => array( | ||
* 'title' => 'Another Block', | ||
* 'category' => 'formatting', | ||
* 'icon' => 'star-filled', | ||
* // ... other block metadata | ||
* ), | ||
* // ... more block metadata entries | ||
* ); | ||
* ``` | ||
* | ||
* @since 6.7.0 | ||
* | ||
* @param string $path The absolute base path for the collection ( e.g., WP_PLUGIN_DIR . '/my-plugin/blocks/' ). | ||
* @param string $manifest The absolute path to the manifest file containing the metadata collection. | ||
* @return bool True if the collection was registered successfully, false otherwise. | ||
*/ | ||
public static function register_collection( $path, $manifest ) { | ||
$path = wp_normalize_path( rtrim( $path, '/' ) ); | ||
|
||
$wpinc_dir = self::get_wpinc_dir(); | ||
$plugin_dir = self::get_plugin_dir(); | ||
|
||
// Check if the path is valid: | ||
if ( str_starts_with( $path, $plugin_dir ) ) { | ||
// For plugins, ensure the path is within a specific plugin directory and not the base plugin directory. | ||
$relative_path = substr( $path, strlen( $plugin_dir ) + 1 ); | ||
$plugin_name = strtok( $relative_path, '/' ); | ||
|
||
if ( empty( $plugin_name ) || $plugin_name === $relative_path ) { | ||
_doing_it_wrong( | ||
__METHOD__, | ||
__( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path.' ), | ||
'6.7.0' | ||
); | ||
return false; | ||
} | ||
} elseif ( ! str_starts_with( $path, $wpinc_dir ) ) { | ||
// If it's neither a plugin directory path nor within 'wp-includes', the path is invalid. | ||
_doing_it_wrong( | ||
__METHOD__, | ||
__( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path.' ), | ||
'6.7.0' | ||
); | ||
return false; | ||
} | ||
|
||
if ( ! file_exists( $manifest ) ) { | ||
_doing_it_wrong( | ||
__METHOD__, | ||
__( 'The specified manifest file does not exist.' ), | ||
'6.7.0' | ||
); | ||
return false; | ||
} | ||
|
||
mreishus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self::$collections[ $path ] = array( | ||
'manifest' => $manifest, | ||
'metadata' => null, | ||
); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Retrieves block metadata for a given block within a specific collection. | ||
* | ||
* This method uses the registered collections to efficiently lookup | ||
* block metadata without reading individual `block.json` files. | ||
* | ||
* @since 6.7.0 | ||
* | ||
* @param string $file_or_folder The path to the file or folder containing the block. | ||
* @return array|null The block metadata for the block, or null if not found. | ||
*/ | ||
public static function get_metadata( $file_or_folder ) { | ||
$path = self::find_collection_path( $file_or_folder ); | ||
if ( ! $path ) { | ||
return null; | ||
} | ||
|
||
$collection = &self::$collections[ $path ]; | ||
|
||
if ( null === $collection['metadata'] ) { | ||
// Load the manifest file if not already loaded | ||
$collection['metadata'] = require $collection['manifest']; | ||
} | ||
|
||
// Get the block name from the path. | ||
$block_name = self::default_identifier_callback( $file_or_folder ); | ||
|
||
return isset( $collection['metadata'][ $block_name ] ) ? $collection['metadata'][ $block_name ] : null; | ||
} | ||
|
||
/** | ||
* Finds the collection path for a given file or folder. | ||
* | ||
* @since 6.7.0 | ||
* | ||
* @param string $file_or_folder The path to the file or folder. | ||
* @return string|null The collection path if found, or null if not found. | ||
*/ | ||
private static function find_collection_path( $file_or_folder ) { | ||
if ( empty( $file_or_folder ) ) { | ||
return null; | ||
} | ||
|
||
// Check the last matched collection first, since block registration usually happens in batches per plugin or theme. | ||
mreishus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$path = wp_normalize_path( rtrim( $file_or_folder, '/' ) ); | ||
if ( self::$last_matched_collection && str_starts_with( $path, self::$last_matched_collection ) ) { | ||
return self::$last_matched_collection; | ||
} | ||
|
||
$collection_paths = array_keys( self::$collections ); | ||
foreach ( $collection_paths as $collection_path ) { | ||
if ( str_starts_with( $path, $collection_path ) ) { | ||
self::$last_matched_collection = $collection_path; | ||
return $collection_path; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Checks if metadata exists for a given block name in a specific collection. | ||
* | ||
* @since 6.7.0 | ||
* | ||
* @param string $file_or_folder The path to the file or folder containing the block metadata. | ||
* @return bool True if metadata exists for the block, false otherwise. | ||
*/ | ||
public static function has_metadata( $file_or_folder ) { | ||
return null !== self::get_metadata( $file_or_folder ); | ||
} | ||
|
||
/** | ||
* Default identifier function to determine the block identifier from a given path. | ||
* | ||
* This function extracts the block identifier from the path: | ||
* - For 'block.json' files, it uses the parent directory name. | ||
* - For directories, it uses the directory name itself. | ||
* - For empty paths, it returns an empty string. | ||
* | ||
* For example: | ||
* - Path: '/wp-content/plugins/my-plugin/blocks/example/block.json' | ||
* Identifier: 'example' | ||
* - Path: '/wp-content/plugins/my-plugin/blocks/another-block' | ||
* Identifier: 'another-block' | ||
* | ||
* This default behavior matches the standard WordPress block structure. | ||
* | ||
* @since 6.7.0 | ||
* | ||
* @param string $path The file or folder path to determine the block identifier from. | ||
* @return string The block identifier, or an empty string if the path is empty. | ||
*/ | ||
private static function default_identifier_callback( $path ) { | ||
// Ensure $path is not empty to prevent unexpected behavior. | ||
if ( empty( $path ) ) { | ||
return ''; | ||
} | ||
|
||
if ( str_ends_with( $path, 'block.json' ) ) { | ||
// Return the parent directory name if it's a block.json file. | ||
return basename( dirname( $path ) ); | ||
} | ||
|
||
// Otherwise, assume it's a directory and return its name. | ||
return basename( $path ); | ||
} | ||
|
||
/** | ||
* Gets the WordPress 'wp-includes' directory path. | ||
* | ||
* @since 6.7.0 | ||
* | ||
* @return string The WordPress 'wp-includes' directory path. | ||
*/ | ||
private static function get_wpinc_dir() { | ||
if ( ! isset( self::$wpinc_dir ) ) { | ||
self::$wpinc_dir = wp_normalize_path( ABSPATH . WPINC ); | ||
} | ||
return self::$wpinc_dir; | ||
} | ||
|
||
/** | ||
* Gets the normalized WordPress plugin directory path. | ||
* | ||
* @since 6.7.0 | ||
* | ||
* @return string The normalized WordPress plugin directory path. | ||
*/ | ||
private static function get_plugin_dir() { | ||
if ( ! isset( self::$plugin_dir ) ) { | ||
self::$plugin_dir = wp_normalize_path( WP_PLUGIN_DIR ); | ||
} | ||
return self::$plugin_dir; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So my hope for the future is the following to be possible in Gutenberg:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right! I think technically that already should be possible once this PR lands? 🤔