halloverden/symfony-route-deprecation-bundle
A Symfony bundle for deprecating routes: mark routes as deprecated so requests trigger configurable deprecation notices, helping you track usage and migrate consumers safely before removal.
This is a Symfony bundle, not a Laravel package, so the first step is acknowledging the framework boundary: you can evaluate it, but you cannot composer require it into a Laravel app and expect it to work without adaptation. For a Symfony service, the minimal path is:
composer require halloverden/symfony-route-deprecation-bundleconfig/bundles.php (Symfony 5.4+/6+ auto-registration may not apply; check the README).config/packages/route_deprecation.yaml entry — read the bundle's DependencyInjection/Configuration.php first; with only 9 stars, the README may lag behind the code.EventListener/EventSubscriber class. That is where the Deprecation header is injected, and it tells you exactly which routes and which responses are affected.The first real use case: you have a legacy /api/v1/... endpoint that still receives traffic, and you want to advertise its deprecation to clients in-band rather than only in release notes. The value of the package is that it implements the IETF Deprecation HTTP header draft (a structured-field timestamp, optionally with a link to migration docs), so clients and API gateways can discover deprecation programmatically instead of via email.
A minimal illustrative controller usage (confirm the exact attribute name against the source):
#[Route('/api/v1/orders', name: 'api_v1_orders')]
#[Deprecated(since: '2024-01-01', link: 'https://docs.example.com/migrate/v2')]
public function legacyOrders(): Response
{
// ...
}
Verify with curl -I https://app.test/api/v1/orders and look for the Deprecation: response header before building anything on top of it.
Attribute-per-action. The natural pattern is annotating individual controller actions or classes. This keeps the deprecation next to the code it applies to and survives route renames better than a central config list. Prefer this for a handful of endpoints.
Route-default / config-driven. If deprecation is driven by API version (everything under /v1), a listener that inspects the matched route name or path prefix is less repetitive than dozens of attributes. The bundle likely exposes route defaults or a config map; if not, a small custom subscriber on kernel.response that reads $request->attributes->get('_route') is a ten-line extension.
Deprecation + Sunset together. The Deprecation header says "this is deprecated"; RFC 8594's Sunset says "it will stop working on date X." Use both. If the bundle only sets Deprecation, add Sunset in the same subscriber — clients need the deadline to act.
Versioned API rollout. Deprecate v1 while v2 is live; run both for a bake period; monitor which clients still hit v1 (log User-Agent / API key on deprecated routes); then remove. The header is the announcement channel; your access logs are the enforcement channel.
Documentation and SDK integration. Point the link parameter at the migration guide, not the changelog. If you publish OpenAPI, mirror the deprecation there (deprecated: true) so generated SDKs surface it — the HTTP header and the spec should not disagree.
Testing. In Symfony's KernelBrowser/WebTestCase, assert on $client->getResponse()->headers->get('Deprecation') for a deprecated route and assert its absence for a current one. Add a test that fails if a route marked deprecated has no link.
Laravel analogue, for comparison. In Laravel you would implement the same behavior as route middleware or a global middleware reading $request->route()->getName(), appending headers in $next($request) — there is no equivalent package dependency to take on. This is worth noting if your team is evaluating whether to port the idea rather than the bundle.
kernel.response listener runs before kernel.terminate, but cache hits served by a proxy never reach PHP.@1688169599). If you hand-roll the header elsewhere, don't emit a bare ISO date — clients parsing per spec will reject or misread it.trigger_deprecation() and @deprecated are for code-level notices; this is an HTTP contract with API consumers. Don't conflate them in docs.EventSubscriberInterface on kernel.response with a route-name→metadata map. You keep the spec-compliant header format and drop the dependency.Deprecation. Pair the header with response-body warnings or a Warning header if you need humans to notice, and with account-manager outreach for high-value integrations.How can I help you explore Laravel packages today?