Remove Unnecessary PHP Composer Polyfills
A Polyfill is a package that provides functionality that could be missing in a given system. They seamlessly add the necessary functionality if it is not available already in your server environment.
One of the most popular polyfills we have in PHP/Composer world is symfony/polyfill-mbstring
. Without any surprises, this package provides the functionality provided by the mbstring
extension if it not already available on the server. At the time of writing, there are over 200 dependents for this package, and over whopping 177 million installations of it.
PHP mbstring
extension is a popular extension that is available in pretty much every sane PHP setup, but package that require this polyfill makes all projects that have this polyfill require some minimal PHP code, even if you already have mbstring
extension already enabled in its PHP setup. While PHP's opcache should help minimize the impact, this is still code that does not help if you have a fully compliant PHP environment.
Composer does not yet provide functionality that makes it possible for polyfill providers to specify if the package can be skipped if the PHP environment already has the necessary functionality.
This post is about a workaround that you can override and prevent the installation of polyfills if you are certain that the target server that the application runs on already has all the necessary functionality the polyfills are about to provide.
This has been discussed quite extensively in Composer, Symfony Polyfills, and even at Debian dependencies:
- Composer#751: Support for alternative requirement
- Composer#6041: Include a package based upon the version of PHP running?
- Composer#6525: Require one package OR another
- Symfony/polyfill#54: Add proper provides rules for mbstring and iconv polyfills
This is complicated because not every PHP application uses Composer at deployment, and the target server may need the polyfill although the dev environment fulfilled the polyfill functionality natively.
While working on a private project that I'm planning to deploy on a server that I have full control over, I was a bit annoyed to have Symfony polyfills automatically installed because another package that I have added as a dependency required the polyfill.
Composer replace
option
Composer has a configuration directive called replace
, that a package can declare a list of other packages it replaces. Composer will happily skip/remove the packages that are being replaced when you require the package. You can specify a list of polyfills that you wish to not install, in either the root composer.json
file, or as a local package that in turn replaces the said package.
"replace": {
"symfony/polyfill-ctype": "*",
"ralouphie/getallheaders": "*",
"symfony/polyfill-mbstring": "*",
}
The snippet above, when added to your root composer.json
file will prevent the three mentioned packages from being installed. This will not check if the underlying functionality is already available, which you can ensure by requiring the necessary server environment from the same composer.json
file.
"require": {
"php": "^7.3",
"ext-mbstring": "*",
"ext-ctype": "*",
}
After adding the replace
directives, make sure to run composer update
, so composer can remove the packages if they are already installed.
If you would like to check the extensions installed in your PHP setup, run composer show --platform
, and it will show all extensions and other information such as underlying libraries, thread-safety, etc that you can use in your composer.json
file as replacements.
Replacements as a separate package
If putting multiple replace
directives in your root composer.json
file is not your thing, you can create a separate package with its own composer.json
file that require
s the extensions/versions and their counterpart replace
packages. You do not have to publish this package to Packagist where composer local packages come handy:
"repositories": [
{
"type": "path",
"url": "app/packages"
}
],
Above snippet, when used in a root composer.json
file, will tell composer to look for a package in the app/packages
directory, where you can create a separate composer.json
file that declares a new package with its own (and likely more strict) requirements for the platform and packages you'd like to skip polyfills for.