symfony/polyfill-php71
Symfony polyfill backporting PHP 7.1 features—like the iterable pseudo-type—to older runtimes (PHP 5.3+). It lets libraries and frameworks use newer syntax while remaining compatible with legacy PHP installations.
First: decide whether you need it at all. This polyfill only exists to make a handful of PHP 7.1 runtime features available on PHP 7.0. Every Laravel version you can realistically install (5.5 → 12) either runs on PHP 8.x or has an official minimum well above 7.1, so on a modern app this package is dead weight that arrived transitively through some Symfony component. Audit it before touching it:
php -v # expect 8.1+
composer why symfony/polyfill-php71 # who actually pulled it in
composer show symfony/polyfill-php71 # confirm it's installed & version
If php -v is ≥ 7.1 and nothing in composer why output is a hard requirement you can't drop, remove it and re-run your test suite:
composer remove symfony/polyfill-php71
Where to look first if you do need it (i.e. you maintain a package or legacy app targeting PHP 7.0):
vendor/symfony/polyfill-php71/bootstrap.php — this is the whole point of the package. Composer's files autoload includes it on every request; it short-circuits with if (\PHP_VERSION_ID >= 70100) return;, so on native PHP 7.1+ it costs essentially nothing.vendor/symfony/polyfill-php71/Php71.php — the Symfony\Polyfill\Php71\Php71 helper class containing the actual implementations (isIterable(), plus floating-point constant values). It is marked @internal.Scope reality check — the package gives you exactly two things:
is_iterable() function (backed by Php71::isIterable()).PHP_FLOAT_DIG, PHP_FLOAT_EPSILON, PHP_FLOAT_MIN, PHP_FLOAT_MAX constants.Everything else people associate with 7.1 — void return types, iterable type declarations, nullable ?Type hints, class-constant visibility, symmetric array destructuring, Closure::fromCallable() — is parser/engine level and cannot be backported. If your first use case is "I want to write iterable $items and still support 7.0," this package does not solve that.
First realistic use case: you maintain a Laravel package with "php": "^7.0|^8.0" in composer.json and you want a single runtime check for "array or Traversable" without hand-rolling it in every guard clause.
Call the global function, not the class. The Php71 class is internal and bypasses the native implementation; the global function is native on 7.1+ and polyfilled below:
// Good — resolves to natively on 7.1+, polyfilled on 7.0
if (is_iterable($payload)) {
foreach ($payload as $item) { /* ... */ }
}
// Works everywhere but always goes through userland on 7.0-7.1... and 8.x
if (\Symfony\Polyfill\Php71\Php71::isIterable($payload)) { /* ... */ }
Unqualified calls inside a namespace fall back to the global function, so is_iterable($x) inside App\Support\... is fine — you do not need a use function statement.
Require it in require, never require-dev. A polyfill is runtime code. For a package:
"require": {
"php": "^7.0|^8.0",
"symfony/polyfill-php71": "^1.0"
}
Composer's files autoload mechanism is what defines the function, so after adding or removing it always run composer dump-autoload -o; a stale autoload_files.php is the usual cause of "Call to undefined function is_iterable()" in CI.
Declare types you can't enforce in docblocks. Since iterable hints won't parse on 7.0, the pattern is docblock + runtime guard:
/**
* @param iterable $rows
* @return array
*/
public function normalize($rows)
{
if (!is_iterable($rows)) {
throw new \InvalidArgumentException('Expected iterable.');
}
return is_array($rows) ? $rows : iterator_to_array($rows);
}
Floating-point constants are defined globally by bootstrap.php behind defined() guards, so you reference them as plain constants — no import, no wrapper:
$epsilon = PHP_FLOAT_EPSILON; // 2.22044604925031E-16
$digits = PHP_FLOAT_DIG; // 15
if (abs($a - $b) < PHP_FLOAT_EPSILON) { /* equal */ }
This is the common case in Laravel: money/geo/statistics code in a domain service that must run on an old host.
Feature-detection over version sniffing. If you're bridging a codebase across many PHP versions, guard on capability, not PHP_VERSION_ID:
if (function_exists('is_iterable')) { /* ... */ }
// or, for constants:
defined('PHP_FLOAT_EPSILON') || define('PHP_FLOAT_EPSILON', 2.220446049250313E-16);
Testing pattern. Add PHP 7.0 to your CI matrix (e.g. shivammathur/setup-php with php-version: 7.0) alongside 8.3. That's the only place the polyfill path actually executes — on 8.x your tests exercise the native path and will silently never touch the backport.
iterable $x, : void, ?string $x, [$a, $b] = $pair, Closure::fromCallable(), public const — all of these produce parse errors on PHP 7.0, not runtime failures, so no polyfill of any kind can help. Plan your minimum PHP accordingly; this package only helps with the runtime surface listed above.is_iterable() is subtly broader than people assume. It returns true for arrays, Iterator, IteratorAggregate, Generator, and any Traversable. It returns false for plain stdClass and for JsonSerializable that isn't also Traversable — a common Laravel-era bug when people expect JSON-castable models to "just work."vendor/autoload.php is included (e.g. a phpunit.xml bootstrap that does raw file includes, or an artisan command bootstrapped oddly) will not have is_iterable(). If PHPUnit's bootstrap attribute points at something other than vendor/autoload.php, chain the autoload in.Functions\expect('is_iterable') (php-mock, Mockery aliases, or Brain Monkey) will shadow the polyfilled global. On PHP 7.0 the polyfill defines it as a real function first, which can cause "cannot redeclare" if your mocking layer defines it unconditionally — guard with function_exists.PHP_FLOAT_MIN/MAX are absolute-value bounds. PHP_FLOAT_MAX is the largest finite double, PHP_FLOAT_MIN is the smallest positive normalized double (not the most negative). Don't use PHP_FLOAT_MIN as a clamp floor for negative numbers.files bootstrap in some server configurations. If you preload a class whose top-level code calls is_iterable(), you'll get a fatal error on PHP 7.0. Keep preloading limited to classes without top-level function calls.is_iterable + float constants, fork the two files into your own support package rather than expecting configurability.composer why shows nothing requires it, keeping it means an extra autoload file on every request and a dead entry in your SBOM. Conversely, if you're pruning: use composer remove (not manual composer.json edits) so the lock file and autoload_files.php stay consistent, and run the full test suite afterwards — occasionally a package declares the polyfill only in require-dev of a dependency and silently relies on it in production code.composer dump-autoload, then php -r "var_dump(function_exists('is_iterable'), defined('PHP_FLOAT_EPSILON'));". If false, the bootstrap file isn't in vendor/composer/autoload_files.php — usually a corrupted lock file or an --no-scripts install.^1.0. The polyfill API surface is frozen; there is no 2.x, and Symfony has effectively stopped adding features to the php7x polyfills, so upgrades are only ever security/consistency patches. Verify with composer outdated --direct rather than expecting changelog excitement.How can I help you explore Laravel packages today?