sabre/uri
sabre/uri is a lightweight PHP library for working with URIs per RFC3986. It offers resolve, normalize, parse, build, and split functions for handling relative URLs, comparing URLs, and Windows-style paths. Fully unit tested, with many tests drawn from RFC3986 examples.
Build vs. buy on URL/URI handling. Any roadmap item that involves manipulating URLs — webhook registration, connector/integration setup, sitemap or crawler ingestion, link rewriting, signed-URL validation, asset/CDN path handling, URL shorteners — currently gets solved either with parse_url() + string concatenation or with a hand-rolled helper class. This package is the "buy" answer: five pure functions (parse, build, resolve, normalize, split) that are RFC3986-conformant and unit-tested against the RFC's own examples. The decision is less "adopt a library" and more "stop owning URI edge cases as a product risk."
Security posture and open-redirect / SSRF-adjacent surface. Wherever we resolve a user-supplied or third-party-supplied URL against a base (OAuth return URLs, magic-link flows, "redirect back to" parameters, webhook target validation), normalize() + resolve() gives us a deterministic canonical form to compare against an allowlist before issuing a redirect. That is a defensible control to point at during a security review or pen test, and it closes a class of bugs that regex-based allowlists reliably miss.
Canonicalization as a data contract. Duplicate detection, cache keys, analytics attribution, and "same resource or not?" comparisons all need a single canonical URI form. normalize() turns this from a per-team convention into a shared primitive. This is the highest-leverage use case in a Laravel monolith with several teams, because inconsistency here silently corrupts metrics rather than throwing errors.
Edge-case correctness our current code gets wrong. The changelog documents real, recurring PHP failure modes: parse_url() corrupting non-ASCII (Chinese/Hebrew) strings, triple-slash URIs, relative URIs containing an unencoded colon, mailto: resolution, file:// URIs, Windows-style paths (file:///C:/path), and paths consisting only of "0". If any of those appear in imported data, partner payloads, or uploaded link lists, we are already shipping incorrect behavior. This is a bug-fix decision, not a greenfield one.
Dependency and platform hygiene. 3.1.0 (Apr 2026) requires PHP 8.2+ and matches our Laravel baseline; 3.0.x continues to receive backports for PHP 7.4–8.1. BSD-3-Clause, no transitive dependencies, no framework coupling, no service provider to register. That makes it a low-friction item to bundle into a PHP-version upgrade epic rather than a standalone initiative.
What it should not drive. Treat this as infrastructure, not a differentiator. It does not unlock a new customer-facing capability on its own, and it should not appear on a roadmap slide as a feature. Frame it as risk reduction and technical-debt paydown inside an existing initiative.
Diligence caveat for the decision record. The package has ~297 stars and the registry reports zero dependents, which understates reality (its provenance is sabre/dav, and sabre/http before that — a long-lived, widely deployed codebase). Stars are a weak proxy here; the stronger signals are RFC-derived test coverage, PHPStan enforcement, Rector adoption, Dependabot, and a maintainer group still shipping in 2026. Still, this is a small-team project with a real bus-factor, mitigated by the fact that it is ~5 functions of pure PHP with no dependencies — we can fork and vendor it in an afternoon if the upstream ever stalls.
Adopt when:
resolve). Laravel's url() / UrlGenerator builds absolute URLs; it does not implement RFC3986 §5.2 reference resolution. If you are doing anything with crawled pages, HTML <base> tags, feed entries with relative links, or link rewriting, this is the gap it fills.normalize). Laravel has no equivalent. Case-folding scheme/host, removing default ports, and dot-segment removal are exactly what allowlist checks and dedupe keys require.parse_url() is demonstrably insufficient for your input population — non-ASCII paths, triple-slash URIs, file:// forms, Windows paths, or relative references with colons. Verify against your own data first; don't adopt speculatively.split() (dirname/basename for URLs) and don't want to reason about pathinfo() quirks on query strings.Splitshade\... — actually sabre\Uri\parse(), etc.) directly rather than injecting an interface.Look elsewhere / don't adopt when:
Illuminate\Support\Uri and the Url façade already cover parsing, query manipulation, and host/scheme inspection idiomatically. Adding a second URI library for cases Laravel handles creates two conventions in one codebase — worse than the problem.league/uri gives you immutable component objects, PSR-7 interop, IDN/punycode handling, URI templates, and strict validation with precise exceptions. If you need IDN, templating ({var} expansion), data:/urn: handling, or per-component validation errors surfaced to users, league/uri is the right buy — sabre/uri will not get you there.guzzlehttp/psr7 and symfony/http-foundation already ship Uri classes; don't add a third if one is already in the tree.parse() throws InvalidUriException, but this is a parsing library, not a validator with a rule/message API. Keep using Laravel validation rules for form-level feedback and use this only for the transformation layer.Adoption gate before committing: inventory the actual call sites. If you cannot point to at least two or three places where parse_url() or custom string logic produces wrong or inconsistent output, the ROI is speculative and this should sit in the backlog.
To executives (30 seconds): We have URL-handling logic scattered across teams, written four or five different ways, and it has produced real bugs with non-English characters, relative links, and redirected URLs. This package replaces that with one small, standards-compliant, zero-dependency library that is already a de facto standard in the PHP ecosystem. It costs roughly a day or two of engineering, has no licensing or hosting cost, and directly reduces a security exposure (unvalidated redirects) while cleaning up a data-quality problem in our analytics. It is not a feature — it's a de-risking item we should fold into the next PHP upgrade or integration work already on the roadmap.
To engineering (the technical case):
resolve (relative reference resolution), normalize (canonical comparison), parse (a parse_url() replacement that handles non-ASCII, triple-slash, and Windows paths), build, split (URL-safe dirname/basename).resolve and normalize — the two things neither parse_url() nor Laravel's Uri helper gives us. Everything else is incremental.league/uri instead — this is deliberately the smaller tool.Risks to name up front:
How can I help you explore Laravel packages today?