northrook / pathfinder
Resolve paths for files and directories at runtime using predefined key-value pairs
Requires
- php: >=8.4
- northrook/core-contracts: @dev
- northrook/php-filesystem: dev-main
Requires (Dev)
- northrook/core: @dev
- northrook/php-cs: @dev
- northrook/php-debug: @dev
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^12
This package is auto-updated.
Last update: 2026-07-21 16:54:06 UTC
README
Resolves configured parameter references into typed filesystem paths (Path) and URLs (Url).
Implements Northrook\Contracts\Interfaces\PathfinderInterface.
Install
composer require northrook/pathfinder
Requires PHP 8.4+, northrook/core-contracts, and northrook/php-filesystem.
Quick start
use Northrook\Pathfinder;
$pathfinder = new Pathfinder([
'project.dir' => '/srv/app',
'url.base' => 'https://example.test',
'path.cache' => '{project.dir}/var/cache',
'url.assets' => '{url.base}/assets',
]);
$pathfinder->getPath('{path.cache}/items.json'); // Path → /srv/app/var/cache/items.json
$pathfinder->getUrl('{url.assets}/app.css'); // Url → https://example.test/assets/app.css
$pathfinder->getPath('robots.txt'); // Path → robots.txt (passthrough)
$pathfinder->getUrl('resources/icon.svg'); // Url → resources/icon.svg (passthrough)
References
| Form | Meaning |
|---|---|
{key} | Expanded parameter value |
{key}/relative/suffix | Value + joined suffix (URL suffixes insert before ? / #) |
{key}/…/{other} | Leading key joined as above; further {…} in the suffix expanded |
…/{key}/… | Any {key} in a non-leading form expanded left-to-right |
Bare / absolute / URI (no {…}) | Passed through unchanged, then type-checked |
Parameters always use braces. A bare dotted name like path.cache is a literal string — not a parameter lookup. There is no implicit join onto project.dir or url.base.
Parameter values may contain any number of {nested.key} placeholders (and literal %). Nested unknown keys inside configured values throw; unknown keys at the call site (any placeholder in the reference) return null.
Path vs URL
| Call | Resolved shape is a URL | Resolved shape is a filesystem path |
|---|---|---|
getPath | InvalidArgumentException | Path |
getUrl | Url | InvalidArgumentException |
“Filesystem path” here means Windows drive / UNC shapes (C:\…, \\server\share). POSIX absolute strings (/assets/…) are path-absolute URIs: getUrl preserves them; getPath accepts them as filesystem paths.
Non-existent filesystem targets may still yield a Path — existence is checked on the object (exists(), etc.).
Null vs throw
| Outcome | When |
|---|---|
null | Missing / unresolvable reference (unknown {key}, empty string) |
| Throw | Cycles, empty/{} or invalid placeholders, unbalanced {, wrong {key} remainder, path/URL shape mismatch |
Url mutators (with*, query helpers, append) also throw on invalid proposed state — the instance is left unchanged (validate-then-commit).
Constructor
use Northrook\Pathfinder\PathfinderConfig;
new Pathfinder(
array $parameters = [],
?PathfinderConfig $config = null,
?FilesystemInterface $filesystem = null,
?CurlInterface $curl = null, // required for Url::probe / fetch / download
?LoggerInterface $logger = null,
);
new PathfinderConfig(
bool $preload = false, // call preload() in the constructor
bool|string $useCache = false, // false = off; true = {cacheParameter} or Contracts::cache('pathfinder.cache'); string = path
bool $commitOnDestruct = true,
string $cacheParameter = 'path.pathfinder_cache',
);
Defaults live on PathfinderConfig::DEFAULTS and can be passed (or merged) into PathfinderConfig::from().
Construction is light unless $preload is true — the cache dump is not loaded until preload(). Call preload() after Autowire (logger, etc.) when you need the dump hydrated before the first resolve. getPath, getUrl, commit, and bustCache call preload() automatically if it has not run yet.
Cache
Optional PHP dump-file persistence of resolved parameter values (placeholders already expanded). Speeds up repeated boots by skipping placeholder parsing.
- The dump is keyed by a checksum of the raw constructor map. If that map changes, the dump is ignored and rewritten on the next commit.
- A malformed or unreadable cache file is logged at
criticaland treated as empty — boot continues. commit(bool $force = false): boolexpands every parameter, then writes when the source checksum changed (or always when$force).bustCache(): boolclears the in-memory resolved map and deletes the dump file (truewhen a file was removed).- When
$commitOnDestructis true and a cache file is configured,__destructpreloads (if needed) and commits; failures there are logged, not thrown.commitOnDestructis ignored when caching is off. - When
$useCacheistrue, the path comes from the parameter named by$cacheParameter(defaultpath.pathfinder_cache), elseContracts::cache('pathfinder.cache').
Parameter key names have no inherent path/URL meaning — prefixes like path. or url. are never trusted; only the resolved string value matters.
Path and Url
getPath / getUrl return concrete Northrook\Pathfinder\Path and Url, implementing the contract interfaces (append, existence/introspection, filesystem I/O on Path; scheme/host/query/with* and HTTP helpers on Url).
HTTP operations on Url require a CurlInterface injection on the Pathfinder (or Url) constructor.
Path
mkdir()creates the parent directory of this path (for file writes), not the path itself when it names a directory.glob($pattern)runs patterns relative to this path as a directory base. When the path is an existing file, the base isdirname($path).
Url
Mutations rebuild and re-validate the full URL before committing. Prefer exceptions over malformed strings.
| Mutation | Behavior |
|---|---|
withHost('') | Throws — host must not be empty |
withHost(null) on scheme://… | Throws — cannot strip host while keeping a hierarchical scheme |
withHost(null) when host already absent | No-op |
withHost(null) on protocol-relative //host/… | Drops authority (e.g. /path) |
| Query values | Must be null, scalar, or Stringable (including list items); otherwise throws |
| Malformed authority on construct / assign | Throws (https:/x, https:///x, http://, …). Empty authority remains allowed for file: (file:///…) |
Parameter keys
Validated via is_valid_key with separator . and charset alnum, _, \, -, :.
Rejected in keys: % and / (suffixes are never part of the key). Values must be non-empty after trim.