*/ private array $args; /** * Block name in `namespace/name` form (e.g. `affilun/product-card`). */ private string $blockName; /** * Construct an immutable block definition. * * @param string $blockMetadataPath Absolute path to the block's metadata directory * or `block.json` file. * @param string $blockName Block name in `namespace/name` form. * @param array $args Optional second-arg overrides forwarded to * `register_block_type_from_metadata()`. * * @throws InvalidArgumentException When `$blockMetadataPath` is empty after trimming. * @throws InvalidArgumentException When `$blockName` is empty after trimming or violates the §3.1 grammar. */ public function __construct( string $blockMetadataPath, string $blockName, array $args = array() ) { $trimmedPath = trim( $blockMetadataPath ); if ( $trimmedPath === '' ) { $message = 'BlockDefinition blockMetadataPath must be a non-empty string.'; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } $trimmedName = trim( $blockName ); if ( $trimmedName === '' ) { $message = 'BlockDefinition blockName must be a non-empty string.'; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } if ( $trimmedName !== $blockName ) { $message = sprintf( 'BlockDefinition blockName must not contain leading or trailing whitespace; got "%s".', $blockName ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } if ( preg_match( self::BLOCK_NAME_PATTERN, $trimmedName ) !== 1 ) { $message = sprintf( 'BlockDefinition blockName must match the spec §3.1 grammar "namespace/name" ' . '(lowercase ASCII + hyphen, single forward slash); got "%s".', $trimmedName ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } $this->blockMetadataPath = $trimmedPath; $this->blockName = $trimmedName; $this->args = $args; } /** * Return the absolute path of the block's metadata directory or * `block.json` file. */ public function blockMetadataPath(): string { return $this->blockMetadataPath; } /** * Return the `register_block_type_from_metadata()` second-arg * overrides. * * @return array */ public function args(): array { return $this->args; } /** * Return the block's `namespace/name` identifier. */ public function blockName(): string { return $this->blockName; } /** * Return a copy of this definition with the supplied `$args` * array replacing the current one. * * Used by downstream slices (slice 7b) to attach a * `render_callback` after the BlockDefinition has been * constructed without exposing a mutable setter. * * @param array $args Replacement args array. */ public function withArgs( array $args ): self { return new self( $this->blockMetadataPath, $this->blockName, $args ); } /** * Report whether `$other` describes the same block registration * as this definition. * * Equality is structural: the metadata path, block name, and * args array must all be `===` equivalent. Closures inside * `args` therefore compare by identity, which matches the * registrar's forwarding semantics. * * @param self $other Definition to compare against. */ public function equals( self $other ): bool { return $this->blockMetadataPath === $other->blockMetadataPath && $this->blockName === $other->blockName && $this->args === $other->args; } }