*/ private array $searchedPaths; /** * Construct an exception with explicit context. * * @param string $message Developer-facing message. * @param string $key Requested §6.4 template key. * @param array $searchedPaths Ordered list of probed candidate paths. * @param Throwable|null $previous Previous exception in the chain. * * @throws InvalidArgumentException When `$searchedPaths` is not a * list of non-empty strings. */ public function __construct( string $message, string $key, array $searchedPaths, ?Throwable $previous = null ) { parent::__construct( $message, 0, $previous ); $this->key = $key; $this->searchedPaths = self::normalizeSearchedPaths( $searchedPaths ); } /** * Construct the canonical "not found" exception for a key and * the ordered list of paths that were probed. * * @param string $key Requested §6.4 template key. * @param array $searchedPaths Ordered list of probed candidate paths. */ public static function notFound( string $key, array $searchedPaths ): self { $normalized = self::normalizeSearchedPaths( $searchedPaths ); $message = sprintf( "TemplateResolver: no template file found for key '%s'. Searched: %s.", $key, implode( ', ', $normalized ) ); return new self( $message, $key, $normalized ); } /** * Requested §6.4 template key. */ public function key(): string { return $this->key; } /** * Ordered list of candidate filesystem paths the resolver * checked. * * @return list */ public function searchedPaths(): array { return $this->searchedPaths; } /** * Validate `$searchedPaths` is a list of non-empty strings and * return it as a strict `list`. * * @param array $searchedPaths Raw input. * * @return list * * @throws InvalidArgumentException When the input contains a * non-string entry, an empty * string, or has non-list keys. */ private static function normalizeSearchedPaths( array $searchedPaths ): array { $normalized = array(); $expected = 0; foreach ( $searchedPaths as $index => $path ) { if ( $index !== $expected ) { $message = 'TemplateResolutionException searchedPaths must be a list (sequential int keys from 0).'; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } ++$expected; if ( ! is_string( $path ) ) { $message = 'TemplateResolutionException searchedPaths must contain only strings.'; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } if ( trim( $path ) === '' ) { $message = 'TemplateResolutionException searchedPaths must not contain empty strings.'; // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } $normalized[] = $path; } return $normalized; } }