jwage/purl
Purl is a lightweight, dependency-free PHP library for parsing and manipulating URLs. It provides a fluent object-oriented wrapper around parse_url, making it easy to inspect and modify schemes, hosts, paths, query strings, and fragments in your applications.
Architecture fit. Purl is a framework-agnostic, object-oriented URL manipulation library — not a Laravel package. It ships no service provider, facade, config, or publishable assets, so it slots into a Laravel codebase as a plain Composer dependency rather than as an integrated extension. Architecturally that is a mild positive (no hidden bootstrapping, no container coupling, no global state) but also a signal that the library does one narrow job: mutating and composing URLs through a parsed object graph (Purl\Url with path, query, fragment sub-objects, plus join() and getQuery()-style helpers). For a Laravel product, that job is already ~80% covered by parse_url, http_build_query, Uri-style value objects, and Laravel's own UrlGenerator/Request helpers. The marginal capability is ergonomic chained mutation and relative-URL resolution — real, but small.
Integration feasibility. Technically trivial: composer require jwage/purl, no configuration, no migrations, no service registration. Any engineer can adopt it in an afternoon. The blocker is governance, not effort — the package is archived, with a final release dated 2019-04-11. That means no upstream security patching, no PHP-version compatibility work, and no issue triage. Adopting an archived dependency for URL handling is a low-severity but permanent liability: URL parsing touches user input, redirects, and outbound HTTP, so an unpatched parser edge case becomes our bug to own.
Technical risk. Ranked:
parse_url-adjacent behavior and deprecations (e.g., null-to-string coercion in string functions, dynamic property creation) are plausible failure points. Must be verified empirically before any commitment.Key questions.
parse_url + http_build_query + Laravel's URL helpers do not provide, and is it worth owning a fork?Verdict framing for stakeholders: the library is well-liked, small, and pleasant, but it is a 2019 artifact with no steward. Treat it as vendored source code we would own, never as a supported third-party dependency.
Stack fit. Poor-to-moderate for a greenfield Laravel service. Laravel's ecosystem bias is toward maintained, PHP-8-current packages; Purl sits outside that. Its natural home is a codebase that already depends on it — where the cost of removal exceeds the cost of containment. For new work, league/uri is the maintained, PHP 8-ready, PSR-aligned alternative and should be the default recommendation; native parse_url/http_build_query covers the trivial cases with zero dependency weight.
Migration path. Two directions, and the decision is asymmetric:
UrlMutator/UrlBuilder service in our own namespace that delegates to Purl — so the underlying library is swappable in one file. No direct Purl\Url usage in controllers, jobs, or Blade.Purl\ references; (2) classify each call site as parse, mutate, or resolve-relative; (3) replace parse and mutate with parse_url/http_build_query or league/uri; (4) handle resolve-relative last, since join() semantics have no one-line native equivalent; (5) delete the dependency and add a CI guard (a grep or architecture test) preventing reintroduction.Compatibility. Verify before anything else: run the existing test suite against PHP 8.1+ with E_ALL and deprecations promoted to errors, on a branch. Expect failures around null handling and dynamic properties. Also confirm the package's Composer constraint does not conflict with the framework's — an archived 2019 package often carries a stale php requirement that Composer will happily resolve on 8.x but that the code was never tested against. If we fork, we take on adding ^8.0|^8.1|^8.2|^8.3 to require and re-running upstream tests.
Sequencing.
league/uri or native; run differential tests comparing old vs. new output on a corpus of real URLs from logs. (1–2 sprints)Total realistic effort for a mid-sized codebase: small — on the order of two to four sprints of part-time work, which is precisely why "just replace it" beats "just fork it."
Maintenance. This is the dominant cost and the reason for a negative recommendation. Upstream maintenance is zero, permanently — the repository is archived. Every future PHP release, every deprecation, every parse_url behavior change becomes our work. A fork means owning a package: repo, CI matrix, release tags, security triage, and an on-call name. That is disproportionate overhead for a URL helper. Our own wrapper layer is the mitigation — it caps the blast radius at one file and makes the eventual swap a config change rather than a refactor.
Support. No vendor, no SLA, no security advisories. Internal support falls to whoever last touched it. For an archived dependency this is acceptable only where failure is cosmetic (building a link) and unacceptable where failure is behavioral (deciding whether a redirect is safe). Segment accordingly: tolerate Purl in presentation code, remove it from anything touching auth, redirects, webhooks, or outbound HTTP.
Scaling. Runtime scaling is a non-issue — pure string manipulation, no I/O, no memory growth, negligible CPU. It will not be a bottleneck at any traffic level. The scaling concern is organizational: the number of engineers who know the library's quirks does not grow, and the number of call sites does. Left unmanaged, an archived dependency accretes usage precisely because it is easy to use, and each new call site raises the eventual replacement cost. Add the CI guard early.
Failure modes. In rough order of likelihood and impact:
Ramp-up. Near-zero learning curve — the API is small and idiomatic, and an engineer is productive in under an hour. That ease is a trap: low ramp-up plus zero maintenance means adoption outpaces governance. New hires will use it because it is there, not because it was chosen. Document the decision explicitly (an ADR stating "contained, pending replacement, do not extend") so the low ramp-up does not silently expand the footprint.
Bottom line. Purl is pleasant, small, MIT-licensed, and effectively unowned. With an archived repo, a 2019 final release, and an opportunity score of 1.33 against capabilities Laravel already provides, the TPM recommendation is: do not adopt for new work; contain and replace where it already exists; use league/uri or native PHP for URL manipulation going forward. The only scenario justifying continued use is a stable, PHP-8-validated codebase where Purl is confined to cosmetic URL assembly behind a wrapper — and even then, schedule the removal.
How can I help you explore Laravel packages today?