innmind/url-resolver
Lightweight PHP library that maps incoming URLs to the resources that handle them. Register resolvers to match request paths, extract parameters, and return controllers or handlers, keeping routing logic decoupled, testable, and framework-agnostic.
innmind/url-resolver is a small, framework-agnostic abstraction whose job is to turn a URL into something usable — either an absolute URL (resolving a relative reference against a base) or the handler/callable responsible for it, depending on which part of the release notes you read. Before you do anything, reconcile those two readings against the actual resolver interface you install; the package's own descriptions conflict, and this package has no Laravel documentation, no dependents, and is archived, so you are on your own for discovery.
Minimal steps:
composer require innmind/url-resolver:^5.1 --with-all-dependencies
Then confirm constraints before writing code:
composer show innmind/url-resolver
composer why-not innmind/immutable 5 # diagnose resolution failures
What to check first, in order:
vendor/innmind/url-resolver/src/ — read the resolver interface and every concrete implementation. This is your real documentation. At v5.1.0 the public surface is tiny, so a 10-minute read covers it.composer.json — confirm you are on PHP 8.2+ (8.1 was dropped in 5.1.0) and that innmind/immutable resolves to ^5. If your app still pins immutable ^4, you cannot install this at all.innmind/immutable types appear in the public signatures. If they do, expect Maybe/Seq-style values at every call site — a real ramp-up cost for a Laravel team.First practical use case: canonical absolute URLs for outgoing requests and responses. Given a base URL from config('app.url') and a relative path/redirect target, resolve it to an absolute URL in one place instead of repeating url(), URL::to(), and hand-rolled sprintf('%s/%s') logic across controllers. Wire it once:
// app/Providers/AppServiceProvider.php (register)
$this->app->singleton(Resolver::class, fn () => new Resolver(new BaseUrl(config('app.url'))));
Then resolve at the boundary:
// app/Http/Controllers/RedirectController.php
public function __invoke(Request $request, Resolver $resolver)
{
return redirect()->away((string) $resolver->resolve($request->query('next')));
}
That is a day-to-day win: one audited implementation of relative → absolute resolution, testable without booting Laravel.
Bind, don't instantiate. Register the resolver as a singleton in a service provider and inject the interface. This keeps the package swappable if you later outgrow an archived dependency — you can write a 40-line Leagues\UrlResolver implementation behind the same contract.
Resolve at the edge. Do URL resolution in a middleware, form request, or the first line of a controller action; never mid-domain. Domain services should receive absolute URLs as plain strings, not resolver instances.
Middleware for redirect/return URL whitelisting. A small middleware that resolves ?redirect= / ?return= parameters and rejects anything that resolves to a foreign host is the highest-value integration. Validate the resolved absolute host against config('app.url'), not the raw input:
$absolute = (string) $resolver->resolve($request->query('return', '/'));
abort_unless(parse_url($absolute, PHP_URL_HOST) === parse_url(config('app.url'), PHP_URL_HOST), 400);
Outgoing HTTP clients. When calling an internal service with a relative path, resolve against that service's base URL from config, then Http::get($absolute). Keeps a single source of truth for each service base.
Sitemap / feed / notification generation. These are the classic places where every link must be absolute. Resolve once in a builder class and hand arrays of plain strings to the template or mailable — this also dodges queue serialization problems.
Console and jobs. The package is framework-agnostic, so it works identically in a queued job or Artisan command. Resolve in the job, ship only strings across the queue boundary.
Testing. Test your resolver wiring with absolute fixtures, asserting on the returned string or handler — not on internal object state. Because resolution is pure, these tests need no database and no HTTP fake:
it('resolves relative paths against the base', function () {
$resolver = new Resolver(new BaseUrl('https://example.com/app/'));
expect((string) $resolver->resolve('../foo?x=1'))->toBe('https://example.com/foo?x=1');
});
If you also use the handler side. For non-Laravel entry points (webhooks, a legacy front controller, a queue consumer picking a handler from a URL), the URL → handler mapping is a clean way to keep dispatch out of routes/web.php. For standard HTTP routing inside Laravel, use Laravel's router; don't build a parallel routing table.
5.1.0), and have a replacement plan. Do not make it load-bearing for routing.innmind/immutable v5. Under a ^5.0 constraint, a routine composer update silently pulls it in. Pin exactly and review lockfile diffs.innmind/immutable v5 conflicts are the most likely install failure. The error reads like a package bug; it is usually a second dependency pinning ^4. Run composer why-not innmind/immutable 5 rather than guessing.//host/path), root-relative (/path), dot segments (../, ./), query-only (?page=2), fragment-only (#top), empty string, and a base URL that lacks a trailing slash. Also test a base with a path (https://example.com/app/) — the difference between https://example.com/app/foo and https://example.com/foo is the bug you will ship.dump((string) $resolved) and dump($resolved->toArray()) are readable; the object graph is not.innmind/immutable types leaking into your app. If the resolver returns Maybe/Either-style values, every consumer needs functional idioms that most Laravel developers don't use daily. Wrap the resolver in your own thin adapter that returns ?string or throws, and keep the monads inside app/Support/.//evil.com produces https://evil.com.AppServiceProvider so swapping the implementation is a one-line change.How can I help you explore Laravel packages today?