symfony/deprecation-contracts
This package provides a single global function, trigger_deprecation(), that triggers silenced deprecation notices so a custom PHP error handler can catch and log them in dev and prod environments. It takes the package name, version, and a printf-style message.
Architecture fit: symfony/deprecation-contracts is a zero-dependency, single-function contract package that slots into Laravel's Symfony-derived foundation with essentially no friction. Laravel already ships Symfony components (console, http-foundation, process, error-handler) transitively, meaning the mechanism to capture E_USER_DEPRECATED notices—the only thing this package produces—is present in virtually every Laravel application by default. There is no service provider to register, no config to publish, and no container binding required. Architecturally it is a pure convention layer, not a framework extension. The strategic value is not the code (roughly ten guarded lines) but the shared vocabulary it establishes: Since <package> <version>:<message> is a machine-parseable, tool-friendly prefix that both Symfony and any adopting Laravel team emit identically.
Integration feasibility: High, but with a critical nuance for a TPM to internalize: the package is trivially installable (composer require symfony/deprecation-contracts) yet inert by default, twice over. The function calls @trigger_error, and Laravel's LOG_DEPRECATIONS_CHANNEL defaults to null. The dependency is a five-minute install wrapped around a multi-day observability decision: log channel configuration, CI thresholds, dev display settings, and a removal-timeline policy. A TPM should scope effort to the policy and pipeline, and treat the composer line as a formality. Because it is almost certainly already in the lockfile, the "adoption" story is really a standardization and instrumentation story, which is a governance conversation, not an engineering one.
Technical risk: Low on the dependency side (MIT, no runtime deps, stable and effectively frozen API surface, long release history through v3.7.x). The material risks are behavioral and organizational:
function trigger_deprecation() {}—in a test bootstrap, a shared utility package, or legacy code—and suppress notices project-wide with no compiler-level protection. Critically, attempting this from the root composer.json files autoload will instead produce a fatal redeclare error, because Composer loads dependency files first.'next', 'TBD') destroy the semantic value of the prefix and break downstream greps and alerting.once() guards), not the package.Key questions for engineering:
trigger_deprecation() to a queryable log/telemetry sink in production, not just local?function trigger_deprecation() {} override anywhere in our codebase or vendor tree that silently no-ops our notices?failOnDeprecation, --fail-on-deprecation), and who owns the threshold and the exception process?deprecations.log, on what cadence, and what is the SLA from first-seen to removal ticket?Stack fit: Excellent. Laravel's error handling is built on Symfony components, so E_USER_DEPRECATED capture is native rather than bolt-on; Illuminate\Foundation\Bootstrap\HandleExceptions already routes deprecations to a dedicated channel—it is simply null by default. The package's philosophy—standardize a convention, stay out of the way—matches Laravel's "batteries included but replaceable" posture. The strongest fit is for teams maintaining reusable internal packages (composer packages, shared modules, monorepo libraries) consumed by multiple Laravel services, where a single deprecation standard converts "we think nobody uses this" into an evidence-backed removal decision.
Migration path: There is essentially no migration, which is itself the point. Sequence the work in this order:
composer show symfony/deprecation-contracts and composer why. Do not add work where none is needed.deprecations log channel and set LOG_DEPRECATIONS_CHANNEL, with LOG_DEPRECATIONS_TRACE=true in local/staging only. Verify end-to-end with a single tinker call before writing a line of deprecation code.App\Support\Deprecations::since() wrapper that injects the app slug/version, so a version bump is one edit and there is one obvious place to swap in Log::warning() if the package is ever dropped.failOnDeprecation in phpunit.xml once the baseline is clean, not before.Compatibility: Broad and undemanding. PHP-version requirements track Symfony's supported matrix, which for current v3.7.x lines is well within any maintained Laravel release; MIT licensing is policy-clean for commercial and redistributed code. The one incompatibility to actively prevent is the root-package empty-function stub described above—document the correct silencing lever (set the channel to null) and forbid the stub in code review.
Cross-team sequencing: Because this is a contract, the ordering that matters is consumer before producer. Establish the log channel and the Deprecations wrapper (platform team) → publish the convention and a one-page "how to deprecate" guide → only then let package owners emit notices. Emitting deprecations before the pipeline exists trains the org to ignore them.
Maintenance: Near-zero for the dependency itself—the API is frozen, releases are mostly housekeeping (v3.7.0 was "no significant changes"; v3.7.1 touched only PR automation). The real recurring maintenance is the deprecation debt register: each emitted notice is a promise with a version and, ideally, a removal date. Assign a named owner for that register and review it on a fixed cadence (monthly is typical), or the notices become permanent log noise and the whole exercise loses credibility.
Support: No vendor support path; the support model is internal enablement. Budget for a short ramp artifact—a worked example, the log-channel snippet, the test pattern for asserting a notice fires, and the "deprecation not firing" triage checklist (channel misconfigured; code path never reached due to cached config/routes/opcache; or a rogue empty-function override). Expect ~1–2 days of platform-team effort to build the pipeline and the guide, then negligible ongoing cost.
Scaling: Compute cost is effectively nil—a guarded global function emitting a silenced notice is cheap enough for hot paths. The scaling concern is log volume and signal-to-noise: per-row or per-loop deprecations can multiply fast, and once enabled you will also ingest Symfony's own notices. Mitigations are organizational: the once() guard for hot paths, tagging first-party calls through the wrapper so streams can be split from vendor noise, and a retention policy on the deprecations channel (14 days is a reasonable default). For fleets, ship the log to the aggregator and alert on first occurrence per Since … string (something still calls it) and last occurrence (safe to delete)—that is the highest-leverage operational payoff of the whole package.
Failure modes and mitigations:
| Failure mode | Observable symptom | Mitigation |
|---|---|---|
Channel left null |
No log entries ever | Verify with one tinker call at setup; assert in CI |
| Empty-function stub added | All notices silently vanish project-wide | Ban the stub in review; prefer channel-null silencing |
Stub in root composer.json files |
Fatal redeclare error on boot | Never autoload a stub from the root package |
| Wrong arg count / placeholder mismatch | ArgumentCountError or ValueError crashes the request |
Treat messages as formatted strings; unit-test them |
| Non-release version strings | Broken greps, unreliable alerting | Require the version about to be tagged |
| Queue/Octane context loss | Notices attributed to worker, not request | Log surrounding context alongside the notice |
| Log flooding | Storage growth, alert fatigue | once() guard; retention; split vendor stream |
| Permanent deprecations | Debt register bloat, credibility loss | Owner + removal SLA per notice |
Ramp-up: Minimal for individual engineers—one short example and they are productive. The genuine ramp is cultural: getting teams to emit deprecations at the version they ship, to phrase messages as "X is deprecated, use Y instead," and to treat CI failures on new deprecations as blocking rather than advisory. Plan a brief org-wide walkthrough and a documented policy. Once in place, the package pays back by turning upgrade and removal decisions from opinion into data: deprecations.log becomes the evidence trail for deleting legacy code, sequencing major-version upgrades of internal packages, and de-risking framework upgrades—which is precisely the leverage a TPM wants from a near-zero-cost dependency.
How can I help you explore Laravel packages today?