Remove Unnecessary PHP Composer Polyfills

Thu, 2019-11-14 18:37

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:

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 underline 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 underline 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 requires 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.