Your team runs PHP 8.5 locally but production runs 8.3; how do composer.json's php and ext-* requirements and config.platform keep composer update from locking incompatible packages?
answer
- virtual platform packages, no code
- composer show --platform
- config.platform fakes the version
- "ext-foo": false hides an extension
- check-platform-reqs ignores config.platform
basics
~20 sphp and ext-* in require are platform packages: Composer installs nothing, only checks the running PHP. Setting config.platform.php to production's version, such as 8.3.12, makes composer update resolve as if on that PHP, so nothing needing 8.4 gets locked.
solid answer
~40 sComposer exposes the environment as **virtual platform packages**: `php`, `ext-*` for extensions, `lib-*` for system libraries; `composer show --platform` lists them. Requiring `"php": "^8.3"` or `"ext-intl": "*"` installs nothing; it makes Composer refuse to resolve when the platform does not match, and it tells consumers what your code needs. The trap is that `composer update` resolves against the PHP **running Composer**: on 8.5 it may lock a release that requires 8.4, and production on 8.3 then fails. Setting `"config": {"platform": {"php": "8.3.12"}}` makes resolution pretend to be production. Use the full patch version, because `"8.3"` means 8.3.0 exactly. `"ext-foo": false` hides a local-only extension. Since the fake also hides real gaps, run `composer check-platform-reqs`, which ignores `config.platform`, on the production host.
code
bash · 5 linescomposer show --platform
composer config platform.php 8.3.12
composer update
# on the production image, after install:
composer check-platform-reqs --no-devgo deeper
Know that php and ext-* entries in require are checks against the machine, not packages Composer downloads.
Explain that update resolves against the PHP running Composer, and how config.platform.php changes that for the whole resolution.
Set config.platform to production's full PHP version, hide local-only extensions with false, and verify with check-platform-reqs on the real image.
Own the platform baseline: one lowest-supported PHP per application, bumped deliberately together with the lock file when production upgrades.
## Platform packages: requirements that install nothing Composer turns facts about the environment into **virtual packages** that live in a built-in "platform repository". They can appear in `require`, `require-dev`, `conflict`, `provide` and `replace` like any other package, but no code is ever downloaded for them: - `php` (plus subtypes such as `php-64bit`), with the version of the PHP binary running Composer; - `ext-<name>` for each loaded extension, such as `ext-mbstring`, `ext-intl`, `ext-pdo_pgsql`; - `lib-<name>` for system libraries an extension links against, such as `lib-curl`; - `composer`, `composer-plugin-api` and `composer-runtime-api` for Composer itself. `composer show --platform` (or `show -p`) lists what the current machine offers. ```json { "require": { "php": "^8.3", "ext-intl": "*", "ext-mbstring": "*" } } ``` Extensions bundled with PHP carry PHP's own version, so `*` is the normal constraint; when an extension's version equals the running PHP version, `composer require ext-intl` writes `*` for exactly that reason. Listing extensions matters because Composer would otherwise install happily on a host that lacks one, and the code would fail only when it first calls the missing function. ## The mismatch this creates Resolution happens where **`composer update` runs**. If that is a developer laptop on PHP 8.5, the platform repository says `php 8.5.x`, and the solver may pick the newest release of a library that requires `php >=8.4`. The lock file records it. Production on 8.3 then refuses to install that lock, or, if the check is bypassed, crashes on syntax or functions it does not have. ## `config.platform`: resolve as if you were production `config` is root-only, and its `platform` key **overrides** platform package versions for dependency resolution: ```json { "config": { "platform": { "php": "8.3.12", "ext-redis": "6.1.0", "ext-xdebug": false } } } ``` - A string replaces the detected version. With `php` set to `8.3.12`, no package that needs more than 8.3.12 can be selected, whatever PHP runs Composer. - **Write the full version.** `"8.3"` means 8.3.0, so packages declaring `>=8.3.1` are excluded. - A string can also add an extension that is missing locally, so resolution can proceed on a laptop without it. - `false` **hides** an extension that exists locally but not in production. `php` itself cannot be set to `false`; Composer rejects it. ## What the fake hides, and how to catch it The override cuts both ways: Composer now trusts the pretend version, so a laptop running an older PHP resolves "successfully" and fails at runtime. Two tools close the gap: 1. **`composer check-platform-reqs`** checks installed (or, with `--lock`, locked) packages against the **real** PHP and extensions, and ignores `config.platform` on purpose. Run it on the production image or host during deployment. 2. **`--ignore-platform-req=ext-foo`** skips one requirement for a single `install`, `update` or `require`; `--ignore-platform-reqs` skips them all. Both are escape hatches: they let a lock through that the target may not satisfy, so use them only when the target is known to have what is missing. | Tool | What it changes | Risk | |---|---|---| | `config.platform.php` | resolution uses the fake version | local PHP may be older than it claims | | `"ext-foo": false` | resolution hides an extension | none, if production truly lacks it | | `--ignore-platform-req=ext-foo` | one run skips one check | a real missing extension slips through | | `check-platform-reqs` | verifies the real platform | none; it only reports | ## Where the mismatch shows up Without the override, the failure appears late and far from its cause. The developer who ran `composer update` sees nothing wrong, because their PHP satisfies every requirement. The build or deploy on the older PHP then stops at install time with a problem report naming the package and its `php` requirement, or, if someone added `--ignore-platform-reqs` to get past that, the application fails at runtime on a function or syntax the older PHP lacks. With `config.platform.php` set, the same mistake surfaces on the developer's machine, during `composer update`, as an older release being chosen or a clear resolution error. ## Choosing the value Set `config.platform.php` to the **lowest** PHP version the application will run on in production, not the newest it supports. When production upgrades, bump the value and run an update in the same change. For a **library**, the requirement worth writing is `"php"` in `require` with the lowest version you test; `config.platform` in a library affects only the library's own development checkout, because consumers never read its `config`.
- Why is config.platform.php set to "8.3" subtly wrong?Composer reads `"8.3"` as 8.3.0. Any package that declares a minimum such as `>=8.3.1` is then treated as incompatible, so the solver may choose older releases or fail, even though production actually runs a later 8.3 patch. Use the full version production runs.
- A library sets config.platform.php in its composer.json. Does that protect the projects that install it?No. `config` is root-only, so consumers never read it. What a consumer's solver sees is the library's `require` entry for `php`. A library should state its supported range there, for example `^8.2`, and may use `config.platform` only to keep its own development lock file honest.
saying these in an interview costs you the question
- Requiring ext-intl in composer.json makes Composer download and install the extension
- check-platform-reqs honours config.platform, so it proves production is fine
- config.platform.php set to 8.3 covers every 8.3 patch release
- --ignore-platform-reqs is a safe default for CI builds
- A library's config.platform setting constrains the projects that install it