symfony/polyfill-php85
Symfony polyfill that brings PHP 8.5 core features to older PHP versions, including get_error_handler, get_exception_handler, NoDiscard, array_first, array_last, DelayedTargetValidation, Filter exceptions, and locale_is_right_to_left. MIT licensed.
Architecture fit. symfony/polyfill-php85 is a namespace-level shim: it declares global PHP functions (array_first, array_last, get_error_handler, get_exception_handler, locale_is_right_to_left) and a handful of classes/attributes (NoDiscard, DelayedTargetValidation, Filter\FilterException, Filter\FilterFailedException), each guarded so nothing is declared when PHP already provides it. For a Laravel estate this is architecturally neutral — no service provider, no config, no bootstrap hook, no runtime container surface. It fits because Laravel already consumes the Symfony polyfill family transitively, so this adds no new dependency class, only new symbols. It is not a platform upgrade; it is a source-compatibility layer.
Integration feasibility. Trivially installable (composer require symfony/polyfill-php85) and MIT-licensed. Real feasibility blockers are not installation but three friction points: (1) attribute-based features (NoDiscard, DelayedTargetValidation) ship only the symbol, not the enforcement — on PHP 7.4 #[...] is parsed as a comment, and on 8.0–8.4 it is inert metadata; (2) locale_is_right_to_left() requires ext-intl and is deliberately not registered without it (v1.37.0 bugfix), so behavior is environment-dependent; (3) tooling — PHPStan/PHP_CodeSniffer/PHPCompatibility will flag calls to functions that do not exist at your configured platform version, requiring stub or whitelist work before the first merge.
Technical risk. Ranked:
NoDiscard will not warn on discarded returns; DelayedTargetValidation will not defer attribute validation. Treat these two as future-proofing placeholders only — do not build control flow or CI gates on them.grapheme_levenshtein, grapheme_str_split, Collator sorting, MessageFormatter rounding, and script/language detection — plus explicit PCRE-version sensitivities (PCRE < 10.44, PCRE 8). High fix velocity is good maintenance, but it is also a maturity signal: this code path is young and version-dependent. If RTL layout, locale-aware sorting, or localized formatting is revenue-relevant, native parity is not guaranteed.ValueError-on-PHP-7 where native never returned false. Any catch-and-continue logic around these calls must be tested per PHP version in your matrix, not assumed.array_first/array_last helper, or a second polyfill, risks redeclare fatals or — worse — shadowing confusion. Requires an inventory sweep before rollout.Key questions.
config.platform.php pinned to it, or to a developer machine?FilterException in payment validation), or is this pure convenience?Stack fit. Strongest inside a Laravel 10/11 or Symfony 6/7 codebase already on the Symfony polyfill family — no new architectural concept for the team to learn. Concrete mapping:
array_first/array_last → plain-array access in service classes and DTO hydration. Do not substitute for Arr::first()/Collection::first(), which accept callbacks and have different empty-collection semantics; mixing them creates subtle bugs. Guidance: array_first for arrays, Arr::first for Laravel collections, no crossover.Filter\FilterException → boundary-level validation in jobs, imports, and API ingestion. Keep it out of Laravel's Validator flow; map it to ValidationException at the controller edge so error envelopes stay consistent.get_error_handler / get_exception_handler → observability and audit-trail work in legacy modules. Genuinely useful, low risk.locale_is_right_to_left → localized admin panels. Highest value and highest risk item; pilot separately from the rest.Migration path. Adopt as an enabling dependency, not as a rewrite mandate. Sequence: (1) inventory existing helper functions and custom polyfills; (2) add the package with an explicit require constraint and pin config.platform.php; (3) remediate static analysis so the functions are recognized; (4) pilot in one bounded module; (5) write a one-page internal convention note; (6) schedule removal.
Compatibility. Requires PHP 7.4+ per Symfony's base constraints — an estate stuck on 7.4 is in scope, but note the attribute features degrade to no-ops there. Cross-version testing matrix must cover the oldest and newest supported runtimes, because the polyfill's behavior changes by design depending on whether native symbols exist.
Sequencing.
array_first/array_last and get_error_handler in one internal service. Measure static analysis friction and reviewer confusion, not just test pass rate.locale_is_right_to_left and FilterException if a specific product need is named and a parity test exists.require entry. Because symbols are guarded, the package becomes a no-op long before removal — so removal is hygiene, not urgency.Maintenance. Upstream-maintained by the Symfony core team under MIT, with a genuinely active patch cadence (v1.34 → v1.43 across the supplied changelog). That is the package's main asset and its main operational cost: frequent patch releases mean the dependency should be pinned with a caret constraint and updated on a routine cadence rather than frozen. Low blast radius — no I/O, no network, no persistence, no config surface — so upgrades are cheap to validate. Budget realistically: near-zero ongoing engineering, but do include it in SCA/automated dependency scanning for supply-chain policy compliance, and keep the pin current so you inherit the intl bugfixes.
Support. No commercial backing and no vendor SLA. Internal support burden is minimal because the surface is small and the failure modes are well-documented in changelogs, but escalation for an intl edge case means filing upstream against a 64-star repo with an active maintainer — response is likely good, timing is not contractual. Do not place this on a critical path where an upstream stall blocks a release.
Scaling. Overhead is per-call userland dispatch versus a C-level native function — measurable in a million-iteration loop, irrelevant in request-scoped code. Rule: fine anywhere except hot loops and tight serialization paths; benchmark there rather than assuming. Scaling risk is not performance, it is cognitive: as more of the codebase uses polyfilled symbols, "which symbols exist natively on this runtime" becomes a question engineers must answer, and that burden grows with adoption breadth. Counter it with PHPStan-level enforcement, not documentation.
Failure modes.
grapheme_*, Collator, MessageFormatter, locale_is_right_to_left varying by PCRE and ext-intl versions across environments — passes in CI, differs in prod.ValueError vs false return) breaking existing catch blocks after a patch bump.composer.json for years.Ramp-up. For a Laravel developer: under a day. Installation is one command, usage is indistinguishable from native functions, and the mental model ("write PHP 8.5, it just works") is easy to absorb. The non-obvious learning is governance, not syntax — 90 minutes of team enablement covering (a) attribute features are inert without PHP 8.5, (b) array_first is not Arr::first, (c) intl-backed features need parity tests, and (d) the removal trigger. For DevOps: near-zero ramp, but they must own the pinned platform version and the cross-version CI matrix, since that is where this package's behavior actually varies. Net: low ramp cost, low run cost, moderate governance value — a reasonable, cheap increment with a clear ceiling on what it can promise.
How can I help you explore Laravel packages today?