Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Polyfill Php86 Laravel Package

symfony/polyfill-php86

Symfony polyfill that backports PHP 8.6 features to older PHP versions, including clamp(), the ARRAY_FILTER_USE_VALUE constant, the SortDirection enum, and grapheme_strrev. A lightweight MIT-licensed compatibility layer for existing projects.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a library, not a Laravel package: no service provider, no facade, no publishable config. composer require is the entire setup, because Symfony polyfills register themselves through Composer's files autoload and gate every definition behind function_exists() / define() / enum_exists() checks. Nothing runs unless PHP is older than 8.6, in which case the global symbols simply appear.

composer require symfony/polyfill-php86:^1.43

Pin deliberately rather than using ^1.0: the feature set is cumulative across split-repo tags — clamp() arrived in v1.34, SortDirection and ARRAY_FILTER_USE_VALUE in v1.35, grapheme_strrev() in v1.37, a UTF-8 correctness fix in v1.38, and further grapheme_strrev() fixes in v1.43 (bugs #661 and #695). Anyone who pinned ^1.38 because that was "the version where grapheme_strrev got fixed" should move to ^1.43 — the function has needed more than one pass.

Where to look first. Read vendor/symfony/polyfill-php86/bootstrap.php (and its Resources/stubs directory). It's a few dozen lines and it is the authoritative answer to "what exactly got defined on my runtime" — more useful than the README, which only links to the monorepo. Then the linked RFCs for exact semantics; the polyfill aims to match them, not to define its own contract.

First use case. A pagination guard. This is the single most common place a clamp belongs in a Laravel app and it exercises the feature end-to-end:

// app/Http/Requests/IndexUsersRequest.php
protected function prepareForValidation(): void
{
    $this->merge(['per_page' => clamp((int) $this->input('per_page', 15), 1, 100)]);
}

Confirm what you actually got before writing code against it:

php -r 'var_dump(function_exists("clamp"), defined("ARRAY_FILTER_USE_VALUE"), enum_exists("SortDirection"));'

Implementation Patterns

Call the global functions directly. There is no wrapper class to inject and no reason to wrap one. Every polyfilled symbol lives in the global namespace, so inside App\ namespaced code an unqualified clamp(...) resolves via PHP's global fallback — but writing \clamp(...) (or importing use function clamp;) makes the dependency visible in review and avoids a future namespaced collision.

Use clamp() at the input boundary, not deep in the domain. The value is in replacing hand-rolled ternaries at the point where untrusted data becomes typed data — form requests, DTO constructors, config readers, job payload hydration:

$rating   = clamp($dto->rating, 1, 5);
$discount = clamp($discount, 0.0, 0.5);
$page     = clamp($request->integer('page', 1), 1, $lastPage);

The min > max ordering check and NAN handling live inside the function, which is exactly the part hand-rolled versions get wrong. These paths have been stable since v1.34, so clamp() is the low-risk part of the package.

SortDirection belongs at the API edge, not in the query builder. Model it as a typed request parameter, then translate it to what Laravel's builder actually accepts — orderBy() wants the string 'asc' / 'desc', not an enum case:

$direction = SortDirection::from($request->input('direction', 'asc')) ?? SortDirection::Ascending;

$query->orderBy('created_at', match ($direction) {
    SortDirection::Ascending  => 'asc',
    SortDirection::Descending => 'desc',
});

Keeping the enum out of Eloquent means swapping the polyfill for the native enum later touches one mapping function. Note that the v1.43 changelog's "Collator sorting" fix is not this enum — it belongs to the intl family of polyfills and does not change SortDirection behaviour here.

ARRAY_FILTER_USE_VALUE is documentation, not behaviour. It exists so array_filter($a, $cb, $flag) reads symmetrically with the USE_KEY / USE_BOTH flags. Its value is the default (0), so adopting it changes no output — write it where the explicitness helps a reviewer and skip it otherwise.

grapheme_strrev() for user-visible strings only. It reverses by grapheme cluster, so combining marks and emoji stay intact where strrev() would shatter them. Reach for it in display formatting (reversed labels for RTL layouts, mirroring in generated captions), never in identifiers, hashes, or protocol strings. Given two rounds of upstream bug fixes, keep this the only place you use it — a single helper wrapping it means the next fix is a composer bump rather than a code hunt.

Test both paths in CI. Add a PHP 8.6 (or nightly) job alongside your current version. Tests written against the polyfill only prove the polyfill works; the point is that your call sites behave identically when the guard flips to native. Assert boundary values explicitly — clamp(5, 10, 20) === 10, clamp(25, 10, 20) === 20, clamp(NAN, 0, 1), clamp(-0.0, 0.0, 1.0), numeric strings, and the min > max case. For grapheme_strrev(), pin down the exact cases that upstream has been churning on: family emoji (ZWJ sequences), regional-indicator flags, skin-tone modifiers, single combining marks ("e\u{0301}"), and mixed-script strings. Those assertions are what will tell you whether a future polyfill bump silently changed rendered output.

Keep static analysis honest. On PHP < 8.6, PHPStan and Psalm do not know these symbols exist and will report undefined function/class/constant. Provide a small stub file declaring the signatures and point phpstan.neon at it (stubFiles), or baseline the specific errors per file — but scope the baseline to files that legitimately use PHP 8.6 syntax so it can be deleted in one pass after the upgrade. Do the same for PhpStorm via a .phpstorm.meta.php or an IDE-helper stub if autocompletion matters to your team.

Gotchas and Tips

  • Do not use Symfony\Polyfill\Php86\clamp;. The namespaced implementation classes exist for internal wiring; the public surface is the global function. Importing the namespaced one gives you a different symbol that ignores the guard.
  • The grapheme_strrev() fix train is not finished. v1.38 fixed UTF-8 handling; v1.43 ships additional fixes under bugs #661 and #695. If grapheme_strrev() output is user-visible, treat any 1.x bump as a potential rendering change and re-run your grapheme fixtures rather than merging the dependency bump blind.
  • The changelog mixes package families. v1.43.0 lists "Collator sorting, English list patterns and integer rounding in MessageFormatter" — those are intl-polyfill concerns bundled into the same monorepo release. Don't infer from the changelog that SortDirection or any php86 symbol changed. Verify against bootstrap.php / the stubs, not the release notes.
  • Zero dependents means silent absence. Unlike polyfill-php80 / polyfill-mbstring / polyfill-ctype, nothing pulls this in transitively — Laravel and Symfony do not require it. If you depend on clamp() without require-ing the package, the failure surfaces only on PHP < 8.6, only when that code path executes, and only in production if your dev box happens to be newer. Add it to require (not require-dev) the moment app code calls it.
  • Watch for redeclaration against your own helpers. Composer loads files autoload entries before your app/helpers.php. If you already ship a global clamp() with a different signature, the polyfill defines first and your helper fatals. Delete the hand-rolled helper when you adopt the polyfill.
  • config.platform.php in composer.json does not disable the polyfill. The bootstrap checks the real PHP_VERSION_ID constant at runtime, so emulating 8.6 for dependency resolution still leaves the polyfill active on your 8.3 machine. Only an actual runtime upgrade turns it off.
  • clamp() raises ValueError when min > max. That is correct RFC behaviour, not a bug — validate the bounds (e.g. from a settings table or admin input) before clamping with them, or catch it at the boundary rather than letting it 500.
  • ARRAY_FILTER_USE_VALUE === 0. Identical to passing no flag. If you see a diff where someone added it and expected different filtering, the change they needed was ARRAY_FILTER_USE_KEY (or Collection::filter with a key-aware callback).
  • grapheme_strrev() returns false on invalid UTF-8. Never pass the result straight into a template — false renders as ''. Guard with mb_check_encoding($input, 'UTF-8') and decide whether to fall back to the original string or reject it. Keep this guard even after v1.43: the function's error contract has moved once already.
  • Overlap with symfony/polyfill-intl-grapheme needs version alignment, not just a one-time check. Because the v1.43 fixes span both the php86 and intl changelogs, running polyfill-php86:^1.38 next to polyfill-intl-grapheme:^1.43 (or vice versa) can produce behaviour that differs by which one wins the function_exists() race. Verify with php -r 'print_r(new ReflectionFunction("grapheme_strrev"));' after every bump and keep both constraints moving together.
  • Expect noisy releases. The split repo tags frequently with "no significant changes" (v1.36.0 was empty), and the two tags skipped between v1.41.0 and v1.43.0 are a reminder that the patch cadence is fast. Group Renovate/Dependabot updates for symfony/polyfill-* into one PR rather than fighting weekly churn — but let the grapheme_strrev fixtures run on that PR.
  • Benchmark before putting it in a hot loop. The polyfill carries argument inspection the native implementation won't. Over 100k rows in a report generator the difference is measurable, so either hoist the clamp out of the row loop or fall back to min(max(...)) there.
  • Plan the exit. Annotate adoption sites (// PHP 8.6: replace with native clamp()) and audit with composer why symfony/polyfill-php86 plus a grep -r 'clamp(\|SortDirection\|ARRAY_FILTER_USE_VALUE\|grapheme_strrev' app/ before removing on upgrade. The polyfill stays inert on 8.6, so leaving it installed is harmless — but it hides the fact that you forgot to clean up, and it keeps you subscribed to the release churn.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox