Symfony Polyfill 1.34.0 ships ten new polyfills that cover features from PHP 8.4, 8.5, and 8.6, along with a new polyfill for the deepclone Symfony PHP extension. This release lets you write forward-compatible code against upcoming PHP APIs while still running on the PHP versions your projects support today.

PDO driver-specific subclasses

PHP 8.4 introduced dedicated Pdo\Mysql, Pdo\Pgsql, Pdo\Sqlite, Pdo\Odbc, Pdo\Firebird, and Pdo\Dblib subclasses with their own driver-specific constants and methods, and PHP 8.5 deprecates the equivalents on the base PDO class. This polyfill makes the new classes and constants available on earlier PHP versions, so you can move away from the deprecated API without waiting for every target runtime to reach PHP 8.4:

1
2
3
4
use Pdo\Mysql;

$connection = new Mysql('mysql:host=localhost;dbname=shop', $user, $pass);
$connection->setAttribute(Mysql::ATTR_MULTI_STATEMENTS, false);

Note that the PDO::connect() static factory introduced alongside the subclasses is not polyfilled.

Thanks to @nicolas-grekas and @jnoordsij for #549.

bcmath rounding functions

PHP 8.4 added bcround(), bcceil(), and bcfloor() to round arbitrary-precision decimal numbers without ever converting them to floats. The polyfill makes the three functions available on earlier PHP versions, together with the RoundingMode constants that bcround() expects:

1
2
3
$total = bcround('19.995', 2, RoundingMode::HalfAwayFromZero); // '20.00'
$floor = bcfloor('19.999');                                    // '19'
$ceil  = bcceil('19.001');                                     // '20'

Thanks to @Dean151 for #546.

IntlListFormatter

PHP 8.5 ships a new IntlListFormatter class that joins items into a locale-aware list, picking the correct conjunction and punctuation for each language. The ICU polyfill provides it back to PHP 7.2 using the CLDR list patterns:

1
2
3
4
5
6
7
$formatter = new IntlListFormatter('en');
echo $formatter->format(['apples', 'oranges', 'pears']);
// apples, oranges, and pears

$formatter = new IntlListFormatter('fr');
echo $formatter->format(['pommes', 'oranges', 'poires']);
// pommes, oranges et poires

Thanks to @Ayesh for #532.

locale_is_right_to_left()

PHP 8.5 also exposes locale_is_right_to_left() (along with the Locale::isRightToLeft() method), which returns true for locales written from right to left such as Arabic or Hebrew. You can use it to flip the layout direction of a response without shipping your own list of RTL language codes:

1
$direction = locale_is_right_to_left($request->getLocale()) ? 'rtl' : 'ltr';

Thanks to @alexander-schranz for #527.

grapheme_levenshtein()

PHP's built-in levenshtein() counts bytes, which produces incorrect results as soon as a string contains multi-byte characters or combining marks. PHP 8.5 adds grapheme_levenshtein(), which operates on grapheme clusters so that user-visible characters are counted as one unit:

1
2
grapheme_levenshtein('café', 'cafe');  // 1
grapheme_levenshtein('Å', 'A');        // 1

Thanks to @sudam802 for #558.

Filter exception classes

PHP 8.5 introduces a FILTER_THROW_ON_FAILURE flag that makes filter_var() throw instead of returning false on invalid input. The throwing behavior itself cannot be emulated, but the new exception classes can, which lets library authors write catch blocks that compile on both PHP 8.5 and earlier releases:

1
2
3
4
5
6
7
8
9
10
use Filter\FilterException;
use Filter\FilterFailedException;

$flags = FILTER_THROW_ON_FAILURE;

try {
    $email = filter_var($input, FILTER_VALIDATE_EMAIL, $flags);
} catch (FilterFailedException $e) {
    // ... report the invalid input
}

Thanks to @Ayesh for #557.

DelayedTargetValidation attribute

PHP 8.5 adds the #[DelayedTargetValidation] attribute, which tells the engine to defer target validation of a user attribute until reflection actually instantiates it. This unblocks attribute authors who need to apply an attribute to a target that the engine would otherwise reject. The polyfill provides an empty stub so the attribute compiles and is visible through reflection on earlier PHP versions:

1
2
3
4
5
#[DelayedTargetValidation]
#[Route('/invoices')]
class InvoiceController
{
}

Thanks to @DanielEScherzer for #541.

clamp()

Looking further ahead, the release ships a polyfill for the upcoming PHP 8.6 clamp() function, which constrains a numeric value between a minimum and a maximum:

1
2
3
clamp($volume, 0, 100);  // caps volume to the 0..100 range
clamp(-5, 1, 10);        // 1
clamp(42, 1, 10);        // 10

Thanks to @kylekatarnls for #554.

Polyfill for the deepclone extension

Symfony now ships an optional symfony/php-ext-deepclone native extension that round-trips arbitrary PHP value graphs through an array representation, preserving object identity, references, cycles, and private property state. It is several times faster than unserialize(serialize(...)) and produces output that OPcache can map into shared memory when dumped via var_export().

The new symfony/polyfill-deepclone package provides the same deepclone_to_array() and deepclone_from_array() functions in pure PHP, reusing the wire format already used by Symfony\Component\VarExporter\DeepCloner:

1
$snapshot = deepclone_from_array(deepclone_to_array($order));

When the native extension is loaded, the polyfill steps aside.

Thanks to @nicolas-grekas and @GromNaN for #561.

Collator::compare() fallback

Before this release, calling Collator::compare() on the ICU polyfill raised a MethodNotImplementedException. It now falls back to a deterministic string comparison based on the spaceship operator, so code that needs a stable ordering (rather than full ICU collation) works out of the box:

1
2
$collator = new Collator('en');
usort($products, fn ($a, $b) => $collator->compare($a->name, $b->name));

Thanks to @aymericcucherousset for #560.

Full Changelog

Published in #Releases