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 Php83 Laravel Package

symfony/polyfill-php83

Symfony polyfill for PHP 8.3 backports PHP 8.3 features to older PHP versions, including json_validate, the Override attribute, mb_str_pad, str_increment/str_decrement, Date* exceptions, LDAP helpers, and stream_context_set_options.

View on GitHub
Deep Wiki
Context7

Getting Started

Check before you add. In a Laravel app you almost certainly already have this package transitively — symfony/http-foundation (and most Symfony 6.3+/7 components Laravel pulls in) require it. Confirm first:

composer show symfony/polyfill-php83        # present? which version?
composer why symfony/polyfill-php83         # who pulled it in?

If it's already there at ^1.43, there is nothing to install. Pin the floor explicitly only if you are about to call the polyfilled functions in application code:

composer require symfony/polyfill-php83:^1.43

Note: put it in require, not require-dev, if production code paths call json_validate() or str_increment(). A transitive dependency happens to save you here, but relying on that is fragile.

Confirm which implementation is live. The polyfill bootstraps with function_exists() guards, so on PHP 8.3+ you get the native functions and the package is inert. Verify rather than assume:

// php artisan tinker
dump(PHP_VERSION_ID);                                          // 802xx => polyfilled
dump((new ReflectionFunction('json_validate'))->getFileName()); // null/"" => native
dump((new ReflectionClass('Override'))->getFileName());         // path => stub

Where to look first. vendor/symfony/polyfill-php83/bootstrap.php (what gets defined and under which PHP_VERSION_ID guards), Php83.php (the actual implementations), and Resources/stubs/ (the Override attribute and the Date*Exception / SQLite3Exception class declarations). If a behaviour surprises you, the answer is in Php83.php, not in the docs.

First use case: a JSON-string validation rule. Lowest-risk, highest-frequency win. json_validate() checks syntax without allocating a decoded structure, which is exactly what a "must be a JSON blob" field wants:

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class JsonString implements ValidationRule
{
    public function __construct(private int $depth = 512) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! is_string($value) || ! json_validate($value, $this->depth)) {
            $fail('The :attribute field must be a valid JSON string.');
        }
    }
}

Then 'payload' => ['required', new JsonString()]. Do this on PHP 8.1/8.2 and again after upgrading to 8.3; the test suite should not change.

Implementation Patterns

Route polyfilled calls through one app-level class. The exit strategy matters more than the call itself. Wrapping means removing the polyfill later is a one-file diff rather than a grep-and-pray:

namespace App\Support;

final class Json
{
    public static function isValid(mixed $payload, int $depth = 512): bool
    {
        return is_string($payload) && json_validate($payload, $depth);
    }
}

The same pattern works for mb_str_pad and the string increment helpers. Do not scatter bare function_exists('json_validate') guards through controllers — the polyfill already guards the definition; guarding the call site only adds noise.

Depth control where Laravel's built-in json rule is not enough. Laravel's validator already has a json rule (decode + json_last_error()), so reach for the polyfill when you need a shallower depth limit (reject 512-deep payloads at 64) or when you are validating outside the validator — queue job payloads, webhook bodies, signed-request middleware:

if (! Json::isValid($request->getContent(), depth: 64)) {
    abort(422, 'Malformed JSON payload.');
}

Validate once, decode once. Never json_validate() and then json_decode() and then check the error again — the second check is dead code and the double parse is wasted work on large payloads.

#[\Override] on abstract repositories and base services. The attribute is documentation-plus-safety-net: it asserts the method really overrides a parent or implements an interface member. Use it fully qualified (#[\Override]) rather than importing it, so the file means the same thing on PHP 8.3 and below:

abstract class BaseRepository
{
    abstract public function find(int $id): ?Model;
}

final class InvoiceRepository extends BaseRepository
{
    #[\Override]
    public function find(int $id): ?Model { /* ... */ }
}

str_increment / str_decrement for separator-free alphanumeric sequences. These are base-62-ish counters (0-9A-Za-z) that carry correctly — str_increment('AZ') gives 'BA', str_decrement('BA') gives 'AZ'. Realistic Laravel uses: spreadsheet-style column labels, human-readable short codes, suffix rotation. For INV-2024-0001 style numbers, split the suffix, increment, and re-pad with Str::padLeft() — or better, keep an integer column and pad on render:

$next = str_pad(str_increment($lastSuffix), 4, '0', STR_PAD_LEFT);

Wrap it in a creating observer and rely on a unique index plus a retry for concurrency — the polyfill is a pure function, not a sequence generator.

mb_str_pad for console and report output. Anything you print in an Artisan command, a fixed-width export, or a table is subject to byte-vs-character drift with str_pad. Pass the encoding explicitly rather than trusting mb_internal_encoding():

$this->line(mb_str_pad($label, 28, ' ', STR_PAD_RIGHT, 'UTF-8') . $value);

Date exceptions in catch blocks, written portably. Catching DateMalformedStringException / DateRangeError is safe on 8.2 because the polyfill declares the classes — but see the gotcha below before you assume behaviour parity. Keep the catch narrow and let App\Exceptions\Handler deal with the rest; do not collapse DateError (an Error) into a generic catch (\Exception).

LDAP / stream polyfills: audit before adopting. ldap_exop_sync, ldap_connect_wallet, and stream_context_set_options only matter if you have an LDAP integration (directorytree/ldaprecord does not need them) or hand-rolled stream contexts. They are not day-to-day Laravel tools.

Gotchas and Tips

Polyfills backport functions and classes, never syntax. Typed class constants, dynamic class constant fetch, and readonly deep-clone semantics are PHP 8.3 grammar — they will still be parse errors on 8.2 no matter what you composer require. Only reach for this package for the enumerated runtime features.

#[\Override] is a no-op below PHP 8.3. The stub declares the attribute so the file parses; nothing verifies it. A method with a typo'd name passes CI on 8.2 and produces a fatal compile error the moment you bump the runtime to 8.3. Mitigate with PHPStan/Psalm configured for the target phpVersion (they validate Override when the target is 8.3+), and treat the PHP-version bump PR as a code review checkpoint, not a dependency bump.

On PHP 7.x, #[ is a comment. If any environment still runs PHP 7.4 (Laravel 8/9 territory), an attribute written inline — #[Override] public function foo() — comments out the rest of the line and produces a parse error. Keep attributes on their own line, always.

Declared exception classes ≠ thrown exceptions. On PHP < 8.3 the native date functions still emit warnings and return false; they do not throw DateMalformedStringException. Your catch block is portable, but the failure mode differs by runtime. Differential tests are the only way to keep this honest.

json_validate() is strict about its argument. It takes string; passing an array is a TypeError, not false. Form-encoded input, or a field the client sent as a nested array, will blow up where you expected a validation failure — hence the is_string() guard. It also accepts only JSON_INVALID_UTF8_IGNORE / JSON_INVALID_UTF8_SUBSTITUTE in the $flags parameter; passing JSON_THROW_ON_ERROR raises ValueError.

json_validate() validates syntax only. It says nothing about shape, required keys, or types. It is not a replacement for a FormRequest or a schema validator — it is a cheap gate in front of them.

str_increment / str_decrement throw. ValueError for empty strings, non-alphanumeric input, and underflow. They are also not concurrency-safe and not a substitute for an auto-increment column. Normalise and validate input before calling, and unit-test the underflow boundaries — this is precisely the surface that #691 fixed, so pin ^1.43 and keep the regression tests.

Audit for hand-rolled polyfills before rollout. A global function json_validate() in app/Helpers/ will collide fatally, because Composer's files autoload (which includes the polyfill bootstrap) runs at require vendor/autoload.php — before any of your helper files. Grep first:

grep -rn "function json_validate\|function mb_str_pad\|function str_increment\|function str_decrement" app/ packages/ bootstrap/

Pin the floor at ^1.43. #691 (str_increment over PHP_INT_MAX), #694 (json_validate edge cases), and #670 (mb_str_pad allocation) are all correctness-relevant. A floating range means different behaviour across environments.

Composer config.platform.php does not turn the polyfill off. The bootstrap branches on PHP_VERSION_ID, the real runtime constant. But a platform config that disagrees with the CI matrix will silently hide the difference between polyfilled and native paths — keep them aligned, or better, run both.

Run a two-job CI matrix. One job on your floor version (polyfilled) and one on PHP 8.3+ (native), executing the same tests. #691 and #694 are proof that parity gaps exist and surface late; differential tests are how you find them before production does.

mb_str_pad is character-counted, not byte-counted. $length is a character length, so column widths in console output won't match str_pad, and STR_PAD_BOTH centring lands differently. If you are padding in a hot loop generating reports, measure — #670 reduced allocation pressure but did not make it free.

Debugging shortcut. When behaviour differs between environments, ask the runtime directly:

dump((new ReflectionFunction('str_increment'))->getFileName()); // polyfill path?
dump(function_exists('stream_context_set_options'));

Plan the exit. Don't composer remove symfony/polyfill-php83 when you hit PHP 8.3 — it is transitive and will come straight back. The real exit is deleting your wrapper class (App\Support\Json) and the #[\Override] attributes becoming enforced for free. Track that as a cleanup item in the PHP-bump ticket, and note the version floor in UPGRADING.md alongside the reason.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo