symfony/polyfill-util
A small Symfony utility polyfill that provides shared helper classes and functions used by other Symfony polyfill packages. It is not intended for direct use; include it when a polyfill depends on it to support compatibility across PHP versions.
Do not composer require this package. symfony/polyfill-util is an internal shim that exists so other Symfony polyfills and legacy components can behave consistently on ancient PHP runtimes. Ninety-nine percent of the time you see it in a project, it arrived transitively and you should never name it in a composer.json.
First commands to run:
composer why symfony/polyfill-util # Composer 2.x (alias: composer depends)
composer show symfony/polyfill-util # installed version + description
If nothing claims it as a direct dependency, it is implementation detail — treat it like a compiled artifact, not an API.
Where to look first. The package is deliberately tiny: open vendor/symfony/polyfill-util/ and read the whole src/ directory plus composer.json in one sitting. Pay attention to the autoload block — this package has historically used a classmap, which means stale optimized autoloaders (composer dump-autoload -o) can be the cause of "class not found" surprises. You are looking for the stream-wrapper class (the "binary on colon" helper) whose job is to make binary read/write modes work on php:// stream URLs on runtimes where the core implementation is deficient.
First real use case. You are keeping a legacy application alive on an old PHP branch, and something calls:
$fp = fopen('php://temp', 'w+b');
…and the handle comes back false or misbehaves because the b flag is not honoured by the platform's php:// wrapper. The package lets you register a replacement wrapper once at bootstrap and leave every other fopen/file_get_contents call in the codebase untouched. The second common case is simply unblocking composer update on an old runtime where a Symfony component declares the polyfill in require.
Register once, at the outermost edge. Bootstrap the wrapper in your front controller, AppServiceProvider::register(), or a Composer files autoload entry — never in library code, and never twice:
if (PHP_VERSION_ID < 70000 && !in_array('php', stream_get_wrappers(), true)) {
\Symfony\Polyfill\Util\BinaryOnColon::register(); // static entry point, verify signature in src/
}
Guard on PHP_VERSION_ID first, registration state second. On PHP 7+ the whole package is dead weight and should be short-circuited.
Quarantine it behind your own function. If application code genuinely needs the behaviour, do not sprinkle the polyfill class through your domain logic. Write one adapter:
function binary_temp_stream(): resource
{
$fp = @fopen('php://temp', 'w+b');
if ($fp === false) {
throw new \RuntimeException('Unable to open binary temp stream');
}
return $fp;
}
That function is the only place that knows the polyfill exists, which makes the eventual removal a one-file change.
Test-suite usage. Polyfill packages are frequently pulled in only because a test harness or fixture loader writes binary content (uploaded files, multipart bodies, ZIP fixtures). In that case, keep it in require-dev at the root and let the production graph stay clean.
CI matrix pattern. Split the job:
legacy:
php: 5.6
install: composer update --prefer-lowest
modern:
php: 8.3
install: composer update
Only the legacy lane actually exercises the polyfill; the modern lane proves you can delete it. Run the modern lane with composer update --no-dev and grep the lock file to confirm the package is gone.
Monorepo / library publishing. If you ship a library, never add this to require. Either declare "replace": {"symfony/polyfill-util": "*"} when you have inlined the behaviour, or simply raise your php constraint and let consumers drop the branch. Pinning it explicitly in a library is how you end up in dependency-hell conversations with downstream users.
Upgrade path as a workflow. Treat every major PHP bump as a scheduled task: run composer why, delete the manual registration, run the test suite, then composer remove symfony/polyfill-util if it was a root requirement. Do it in the same PR as the php platform bump so the reviewer sees the causality.
stream_get_wrappers() before calling the registration helper, and be aware that another library (or a test bootstrap file) may have registered php already.php wrapper you own php://temp, php://memory, php://stdin, and friends for the rest of the request. If the polyfill's stream_stat(), stream_eof(), stream_seek(), or stream_tell() implementations differ from core — and they will, subtly — fstat(), rewind(), and feof() can behave differently than your tests assume. Assert on stream_get_meta_data($fp) rather than trusting fopen success alone.b flag is meaningless on Unix. Every failure mode here is Windows-only or HHVM-only. If your CI is Linux-only you will ship a wrapper that has never been exercised. Use stream_get_meta_data($fp)['mode'] for assertions, not flock-style side effects.r+b, wb, w+b, and a+b all funnel through the wrapper's mode parser. Test the full matrix; do not assume r and rb behave the same after registration.vendor/ (which you shouldn't do anyway) fails silently until first use. After any composer update, run composer dump-autoload -o in deploy scripts.composer audit and, if you are strict, add an allow-plugins/ignore rule or a replace in your own package.vendor/symfony/polyfill-util explicitly or, better, add a stub in stubs/ so IDE completion does not autocomplete these internals into your code.stream_get_wrappers() (is it registered?), stream_get_meta_data($fp) (what mode did you actually get?), stream_get_contents($fp, -1, 0) (is anything there?), then error_get_last() (was there a suppressed warning from fopen?).use Symfony\Polyfill\Util\... will fatal at runtime, not at composer remove time.How can I help you explore Laravel packages today?