fig/link-util
A small PSR-13 implementation for PHP: interfaces and classes for representing links (URI, rel, attributes) and link providers, plus serializers for HTTP Link headers and HAL-style JSON. Integrates with PSR-7 without extra dependencies.
What it is. fig/link-util is the PHP-FIG reference implementation of the PSR-13 hypermedia link contracts: Link, EvolvableLink, LinkProvider, and EvolvableLinkProvider. It is roughly four small value-object classes with no I/O, no state, no configuration, and a single dependency on psr/link. Functionally it converts a URI + rel + attributes into an object graph and back — nothing more.
Architecture fit. This only earns a place in the stack if we are actually speaking PSR-13. Laravel does not: routing, URL generation, and Illuminate\Http\Response have no notion of link providers, and Laravel's HTTP layer is built on Symfony's HttpFoundation, not PSR-7/PSR-13. Adopting this package therefore means adopting a specification our framework doesn't natively produce or consume. The legitimate use cases are narrow and concrete: emitting Link: headers (RFC 8288), building a HATEOAS/HAL/JSON:API layer on top of JsonResource, or exposing pagination relations (next/prev/first/last) through a stable contract. If none of those is a funded requirement, this is a dependency we take on for architectural symmetry we don't get value from.
Integration feasibility. Composer-only. No service provider, no config publish, no facade, no migrations. PSR-4 autoload, MIT licensed, and the interfaces (psr/link) are widely understood. The real integration work is not the package — it's the serialization to and from the wire format, which fig/link-util deliberately does not provide. We would write that ourselves.
Technical risk. Functional risk is very low; ecosystem risk is not zero. The package has been frozen since a single 2016 release, and it constrains psr/link to the 1.x line. Anything in the dependency graph that later requires psr/link ^2 (which added typed signatures) will either block that upgrade or fail at class-load with a signature mismatch. This is a "cheap now, mildly annoying in three years" dependency. A secondary risk is conceptual rather than technical: the Evolvable* classes are immutable, withHref() returns a clone, and developers who mutate-and-discard will silently lose their change.
Key questions.
Link headers? If internal only, a two-class DTO is cheaper.psr/link ^2, now or on a known roadmap?symfony/web-link be a better fit, given it provides the same PSR-13 implementations plus HttpFoundation header integration and preload helpers, with active maintenance?Stack fit. Laravel 9–11 on PHP 8.1+ runs this without modification; the classes are simple enough to be free of deprecation noise. Install is composer require fig/link-util. The natural seams are a JsonResource subclass, a response macro, or a small middleware that appends a Link header, all fed by a domain service that decides which relations a resource has.
Migration path. Contract-first. Depend on psr/link interfaces in our own code and type-hint LinkProviderInterface; bind EvolvableLinkProvider in a service provider as the concrete implementation. That keeps the swap surface to a single binding. If we later move to symfony/web-link or an in-house implementation, it is a one-line container change rather than a refactor. Given the package is four classes, the honest baseline option is to skip it entirely and write our own implementation behind the same interfaces — MIT permits either path, and the exit cost is near zero in both directions.
Compatibility. Confirm resolver behavior against the rest of the graph, particularly symfony/*, before committing. Expect the lockfile to pin psr/link to 1.x as a consequence. Validate on the oldest PHP version we support, not just the CI default.
Sequencing.
psr/link interface-only contracts to the domain layer.rel values, and relative-vs-absolute hrefs.Maintenance. Effectively zero code maintenance and a permanently carried version constraint. Treat the package as vendored code: it will not receive fixes, so any bug we find is ours to patch via fork. An ADR recording the choice, the pinned constraint, and the exit plan is the main operational artifact.
Support. No vendor. Support is PHP-FIG community and, realistically, ourselves. Acceptable only because the code surface is small enough to audit in an afternoon.
Scaling. Irrelevant to throughput — in-memory value objects with no I/O, no caching, no globals. The scaling concern is payload-shaped: link count per response grows header size, and some proxies cap Link headers around 8KB, so a resource with dozens of relations is better served by body-embedded _links than by headers.
Failure modes.
psr/link 2.x resolution — fatal at class load, caught in CI if we test the lockfile.withHref() silently drops the link.rel values across layers producing ambiguous semantics for clients.href is built from unvalidated user input; URIs must be validated and encoded before serialization.Ramp-up. Under an hour for a mid-level engineer, and most of that is learning PSR-13 semantics rather than the library. The immutability contract should be stated explicitly in the ADR so it isn't rediscovered in production.
How can I help you explore Laravel packages today?