*/ private array $definitions = array(); /** * Callable that forwards a single route registration to * WordPress. * * Signature: `function (string $namespace, string $route, array $args): void`. * * @var callable */ private $registerRouteFn; /** * Construct a registrar with an optional override for the * registration callable. * * The default callable forwards to the global * `register_rest_route()` 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 routes. Tests pass an in-memory recording * callable to drive the registrar without booting WordPress. * * @param callable|null $registerRouteFn Override for the registration callable. * * @phpstan-param (callable(string, string, array): void)|null $registerRouteFn */ public function __construct( ?callable $registerRouteFn = null ) { $this->registerRouteFn = $registerRouteFn ?? static function ( // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.namespaceFound -- "namespace" is the canonical REST domain term per spec ยง2.1; the parameter name mirrors WordPress's own register_rest_route() signature. string $namespace, string $route, array $args ): void { if ( ! function_exists( 'register_rest_route' ) ) { throw new LogicException( // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. 'RouteRegistrar default forwarder requires the global register_rest_route() function.' ); } register_rest_route( $namespace, $route, $args ); }; } /** * Append a single {@see RouteDefinition} to the registrar. * * Returns the registrar for fluent chaining; subsequent slices * typically append several definitions at once via * {@see self::addAll()}. * * @param RouteDefinition $def Definition to append. */ public function add( RouteDefinition $def ): self { $this->definitions[] = $def; return $this; } /** * Append every definition in `$defs` in order. * * @param list $defs Definitions to append. */ public function addAll( array $defs ): self { foreach ( $defs as $def ) { $this->add( $def ); } 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_rest_route()` is not idempotent on repeated calls * with identical args. */ public function register(): void { foreach ( $this->definitions as $def ) { ( $this->registerRouteFn )( $def->namespace(), $def->route(), $def->toArgsArray() ); } } /** * Return every definition that has been added, in insertion * order. * * @return list */ public function definitions(): array { return $this->definitions; } }