*/ private array $definitions = array(); /** * Callable that forwards a single block registration to WordPress. * * Signature: `function (string $blockMetadataPath, array $args): mixed`. * * @var callable */ private $registerFn; /** * Construct a registrar with an optional override for the * registration callable. * * The default callable forwards to the global * `register_block_type_from_metadata()` when it is available (the * normal runtime path) and raises a {@see LogicException} otherwise * so a misconfigured production runtime fails loudly rather than * silently dropping block registrations. Tests pass an in-memory * recording callable to drive the registrar without booting * WordPress. * * @param callable|null $registerFn Override for the registration callable. * * @phpstan-param (callable(string, array): mixed)|null $registerFn */ public function __construct( ?callable $registerFn = null ) { $this->registerFn = $registerFn ?? static function ( string $blockMetadataPath, array $args ): mixed { if ( ! function_exists( 'register_block_type_from_metadata' ) ) { throw new LogicException( // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. 'BlockRegistrar default forwarder requires the global ' . 'register_block_type_from_metadata() function.' ); } return register_block_type_from_metadata( $blockMetadataPath, $args ); }; } /** * Append a single {@see BlockDefinition} to the registrar. * * Returns the registrar for fluent chaining. * * @param BlockDefinition $definition Definition to append. */ public function add( BlockDefinition $definition ): self { $this->definitions[] = $definition; return $this; } /** * Append every definition in `$definitions` in order. * * @param list $definitions Definitions to append. */ public function addAll( array $definitions ): self { foreach ( $definitions as $definition ) { $this->add( $definition ); } return $this; } /** * Forward every accumulated definition to the configured * registration callable. * * The registrar does not clear its internal list after * registration so tests can re-assert against the configured * definitions; production callers MUST NOT invoke this method * more than once per request because WordPress's * `register_block_type_from_metadata()` is not idempotent on * repeated calls with identical args. */ public function register(): void { foreach ( $this->definitions as $definition ) { ( $this->registerFn )( $definition->blockMetadataPath(), $definition->args() ); } } /** * Return every definition that has been added, in insertion order. * * @return list */ public function definitions(): array { return $this->definitions; } }