mrkeyoor.com_
Fri 18 Sept 16:23 UTC
Open Source6 min read

A 19.9M-Install PHP Polyfill Retires Rather Than Change Hands

Jake Smith's 174-line URL shim is abandoned after nearly 20 million installs. PHP 8.5 offers the exit, while one known path bug will remain unfixed.

Handing a 19.9-million-install package to a willing new maintainer can look like responsible succession. Jake A. Smith decided that, for his old PHP polyfill, a handover would create more risk than a clean retirement. He marked jakeasmith/http_build_url deprecated on September 15 and told users to move away, even though the package was still recording more than 400,000 installs a month.

The package remains available, but its known path-mangling bug will stay unfixed. PHP 8.5 now contains a replacement. Packagist counts 19,887,027 installs and 107 dependent packages. Its page lists no security advisories, so deprecation should not be mistaken for evidence of a compromise. It is a warning that nobody is maintaining the code.

A one-upgrade patch became infrastructure

Smith wrote the package in 2014 while AOL was moving its content management system from PHP 5.2 to PHP 5.3. That upgrade removed version 1 of the pecl_http extension, including an http_build_url() function called in dozens of places. His answer was 174 lines that defined a compatible function only when the original was missing. The CMS could continue calling the same API, according to Smith's account of the package's history.

That makes http_build_url a polyfill in the literal sense: it filled a hole in one runtime so old application code could keep working. Composer was becoming popular, so Smith published the patch on Packagist in case another PHP project had the same problem. He expected it to be useful for a year or two. AOL's CMS kept the supposed stopgap until the platform closed around 2020.

The download counter kept moving after Smith left PHP work. Packagist's total is a count of installation events, not 19.9 million distinct applications or active sites. Even that large number misses copies placed directly inside other products. Smith says the WPML WordPress plugin bundles the file and reports more than 1.5 million installations. He also traces the package through the idna-convert library into SPIP, as well as Debian and Ubuntu packages. Those distribution paths are documented in his retirement post, rather than inferred from the Packagist total.

One trailing slash can remove every 'a'

The clearest reason to leave is GitHub issue 25, opened in November 2020. Joining a relative path onto a URL whose existing path ends in a slash can delete every lowercase a from that existing path. The reporter supplied a URL containing segments such as apreview, a, 09-20a13, and pa0a4. The result turned those into preview, an empty segment, 09-2013, and p04.

Smith explains that his trailing-slash workaround appends an a as a disposable final segment and then removes it with a replacement. When the original path already ends in /, that operation catches the other instances of the letter too. The issue was closed on September 15, 2026, the same day as the deprecation, without a code fix.

This is a correctness bug, not a listed security advisory. Packagist reports zero advisories, while the issue demonstrates a reproducible change to a URL path. An affected application could request the wrong route or emit a broken link. Whether that causes a more serious failure depends on how the application uses the result, and neither Smith nor the issue reporter claims a security exploit.

Smith chose to leave even this small-looking patch alone. His concern is that changing a package after years of near silence could alter behavior for unknown downstream users, and he is no longer available to support the fallout. The existing releases will remain installable. Developers therefore get time to migrate, but they should not wait for a final bug-fix release.

Why the repository will not change hands

In May 2021, Smith opened a request for a new maintainer. He wrote that he had not worked in PHP for years and wanted members of the community to vouch for any successor because the package already had surprising download volume. Three people offered, but no transfer followed. Smith closed that request when he retired the project.

Smith says that giving an unvetted person control of a dependency with this reach would add a supply-chain risk. That is his assessment of a future transfer, rather than a claim about the volunteers or the current package. The repository remains under his account, and Packagist's advisory count remains zero.

Composer gives maintainers a precise way to communicate this state. Its abandoned package field can be set to true, or it can name a recommended replacement. http_build_url now uses the boolean form. Packagist warns that the package is abandoned, yet it does not point Composer at one universal substitute. The right migration depends partly on the PHP version a project can run.

Retirement freezes one trust boundary. A transfer would ask every downstream user to accept new code published under an old package name. Deprecation preserves the released artifacts and pushes the work into each consuming project, where maintainers can choose a replacement and test it against their own URLs. That is slower for users, but the responsibility is visible.

PHP 8.5 supplies the exit

PHP 8.5, released on November 20, 2025, added an always-available URI extension. The PHP release announcement says it supports both RFC 3986 URIs and the WHATWG URL standard, with separate implementations. The two standards do not treat every input the same way. The WHATWG parser applies transformations associated with browser URLs, while the RFC 3986 parser preserves a different set of URI semantics.

The native API is a reason to remove the polyfill, though it is not a drop-in function rename. Existing projects may use http_build_url() flags to modify several URL components at once. Smith's replacement example expresses those operations through an immutable Uri\Rfc3986\Uri object:

use Uri\Rfc3986\Uri;

echo Uri::parse('https://example.com/search?q=php#top')
    ->withPath('/docs')
    ->withQuery('page=2')
    ->withFragment(null)
    ->toString();

That code produces https://example.com/docs?page=2, according to the deprecated package's migration notes. Joining relative paths uses resolve(), while query merging still needs application code such as parse_str() and http_build_query(). Projects on PHP 8.4 or older cannot use the native extension. Smith points those users toward the PHP League's URI library or a smaller combination of existing PHP functions.

The PHP team's URI API proposal explains why replacing the polyfill also requires a parser choice. It says legacy parse_url() does not conform to a URL standard and describes parsing disagreement as a source of subtle bugs and security problems. Teams replacing the polyfill should first decide whether their input is a generic RFC 3986 URI or a browser-style WHATWG URL. Picking a class by name without testing old edge cases can exchange one assumption for another.

Find it before replacing it

A direct Composer dependency is easy to spot in composer.json. A transitive one may sit several levels down, and a vendored copy such as WPML's will not appear in the dependency graph at all. Composer's documented why command identifies which installed package requires a named dependency:

composer why jakeasmith/http_build_url

If that command finds the package, the next step is to locate every http_build_url() call and record which flags it uses before choosing a substitute. Tests should include the trailing-slash case from issue 25 and real URLs from the application. A source search is still needed when an application bundles libraries outside Composer. Removing the dependency from a lockfile proves little if a copied PHP file remains in a plugin or vendor directory.

The figures to watch now are downstream releases and Packagist's 107 dependents. Continued downloads may come from fresh deployments of old lockfiles or automated builds, so the counter cannot measure successful migrations on its own. Crossing 20 million will say little. The useful number is how quickly those 107 dependents disappear, and whether products that copied the polyfill publish their own exit plans.

We reviewed this

  1. browser — our honest review
  2. marked — our honest review
  3. migrate — our honest review

Sources

  1. My temporary PHP fix from 2014 has nearly 20M installs. Today I'm deprecating it.
  2. jakeasmith/http_build_url on Packagist
  3. http_build_url GitHub repository and migration notes
  4. PHP 8.5 release announcement
  5. PHP RFC: Add RFC 3986 and WHATWG URL compliant API
  6. Composer command-line documentation
  7. Composer schema: abandoned packages
  8. URLs with trailing slashes lose all their a's when joined