0073: PHP version and extensions from Composer

On this page

Status: accepted

Context

PHP projects say what they need in composer.json: the PHP versions in require.php, and the extensions as ext-* packages in composer.json and composer.lock. Short Leash ignored both. A site ran on Short Leash's default PHP unless someone picked a version for it, and a missing extension showed up only as an error from the app. Short Leash's PHP builds don't all carry the same extensions: a version installed before Short Leash built its own (0017) can be a static-php-cli build (0006) with fewer, and some extensions don't exist on one system or left PHP in a later version.

Decision

The PHP version. Engine::php_choice picks a site's version in this order:

  1. The version picked for the site (its pin) wins.
  2. Otherwise Short Leash's default, when composer.json has no readable require.php or require.php allows the default.
  3. Otherwise the newest installed, released version that require.php allows (runtimes::php::version_from_composer).
  4. Otherwise the default.
  • A version from the project's leash.toml or leash.local.toml comes between 1 and 2 (0078).
  • composer.json moves a site only when the default doesn't fit it, so installing a newer PHP or upgrading Short Leash doesn't move every ^8.2 site off the version it ran on. We chose this over "the newest installed version that fits", which would.
  • Only installed versions are candidates. Released ones (runtimes::php::SUPPORTED) come first; an installed preview is picked only when no installed released version fits (0083). composer.json never makes Short Leash download anything.
  • The site's Needs a look covers the three ways the version can miss require.php. A pinned site whose pin doesn't fit keeps its pin, and the check says so and names the newest version Short Leash offers that fits, to pick in the site's settings. An unpinned site that no installed version fits stays on the default, and the check names the version to install on the Runtimes screen. When no version Short Leash offers fits, the check says Short Leash doesn't offer one.
  • Constraints are read as Composer reads them, with Short Leash's own small parser and no new dependency: || alternatives, , or spaces for conditions that all hold, hyphen ranges, ^, ~, wildcards and comparison operators. A bare version means exactly that version. A missing or broken composer.json, or a constraint that can't be read ("latest"), has no opinion: the site keeps the default and no check is raised.
  • The choice runs on every PHP request, so the composer pick is cached per site folder (composer_php), reused while it's younger than defaults::COMPOSER_PHP_CACHE and composer.json's modified time hasn't changed. The cache is cleared when Short Leash's settings change, such as the default PHP, and when a PHP version is installed.
  • The site's Overview says where the version comes from: picked for this site, from leash.local.toml or leash.toml (0078), from composer.json, or Short Leash's default.

The extensions. sites::extensions::required reads the ext-* names a project needs from composer.json (require, require-dev) and composer.lock (the require of each package in packages and packages-dev, and the lock's top-level platform and platform-dev). It drops any name that composer.json's config.platform, provide or replace, the lock's platform-overrides, or a locked package's provide or replace covers, so a polyfill that provides ext-mbstring counts as having it.

  • They're checked against what the installed PHP loads: php -m with Short Leash's ini files (PHPRC, PHP_INI_SCAN_DIR), so extensions you load from your own conf.d count. The result is cached per version. When that version isn't installed, or the run fails, Short Leash falls back to the list in code (runtimes::php::available_extensions).
  • Each missing extension gets one fix (ExtensionFix):
    • turn it on (Xdebug, when it's off for that version);
    • it doesn't exist for PHP on this system (such as pcntl on Windows);
    • update this PHP version on the Runtimes screen, when the installed build lacks it but Short Leash's current build of the same version has it (an install from before Short Leash built its own, which an update or Short Leash's rebuild check replaces);
    • it isn't in Short Leash's build of this version: then, and only then, Short Leash looks for another version, naming the newest released one other than the site's own that has it (by what an installed one loads, else the list in code), or saying none does yet.
  • The same report appears in the site's Needs a look, at GET /v1/sites/{name}/extensions and in the MCP tool check_extensions, which chat may use as a read (0062). None of them has a fix button: Short Leash doesn't change the project or install extensions for it.
  • Octane sites on FrankenPHP skip the check, because FrankenPHP runs the PHP it was built with.

Consequences

  • Each PHP request costs one stat of composer.json for the cached lookup. A change to require.php applies on the next request; processes Short Leash runs for the site pick it up when they restart.
  • A site can change PHP version without anyone touching Short Leash, when its composer.json changes. The Overview's source wording says why.
  • Constraints behave exactly as they do for composer install, surprises included: <=8.3 means <=8.3.0, so it excludes 8.3.x.
  • php -m starts PHP once per version, and only for the Overview and the extension check, never on the request path.
  • The list in code has to follow ShortLeash/runtimes' scripts/php-extensions.txt; its doc comment says so, and a test checks it against a real build where one is installed. Its Windows half grows with Short Leash's Windows extensions.
  • Short Leash doesn't install a PHP version or an extension because composer.json asks for one. The person decides, from what Needs a look says.