What adding PHP 8.5 support actually involves
Rarely the language changes. It is the toolchain that blocks you first, the test suite that makes deprecations fire at all, and two version-range traps that produce no error on the machine where you introduce them.
Adding a new PHP version to a project is rarely about the language changes. It is about everything around the language that has an opinion on which versions exist.
The toolchain blocks you first
Before a single line of framework code was parsed on 8.5, the install failed. Dev dependencies (PHPUnit, static analysis, the lint tooling) had constraints that simply excluded it.
That is the actual first task: widen the dev requirements until composer install succeeds on the new version. Only then can you find out whether your
own code has a problem.
It is worth doing in that order rather than skipping to php -v and assuming
things work. A CI matrix that cannot install is not testing anything.
Then the deprecations
With the toolchain out of the way, the real work is small and mechanical. Deprecations in a release like 8.5 are mostly narrow: implicit conversions that used to be tolerated, edge cases in parameter handling, functions that have been discouraged for years finally being flagged.
The value of a test suite here is not that it catches clever bugs. It is that it exercises enough of the codebase for the deprecation notices to actually fire. Code that is never called never warns.
Run every version, every push
strategy:
matrix:
php: ['8.3', '8.4', '8.5']
Three versions on every push. A change that works on 8.3 and breaks on 8.5 fails before it merges, rather than after someone upgrades and cannot work out what changed.
The cost is three times the CI minutes. The alternative is discovering incompatibility from a bug report, which costs considerably more.
Version ranges are a promise
Two related things caught us out, both worth repeating because neither produces an error on the machine where you introduce them.
A manifest saying ^8.3 should install on 8.3. Composer resolves against
the PHP running the resolution, not the version in your require block. Run
composer update on 8.4 and you can lock packages that need 8.4.1 while
declaring 8.3 support. config.platform.php pins what Composer pretends to be.
A dependency's declared range is a real constraint. phpMyAdmin 5.2.3 says
">=7.2,<8.4". Serving it on 8.5 because 8.5 is installed produces deprecation
spew or a blank page: a failure that reads as a broken install rather than a
version mismatch. Libxa Desktop reads that range and picks the newest installed
version inside it.
Both are the same lesson from different directions: a version range is information someone published deliberately, and ignoring it produces failures that do not look like version problems.
Supporting old versions too
Libxa Desktop installs PHP 8.0 through 8.5, including three lines that no longer receive security fixes. They are labelled End of life in the UI, and they are still offered.
A legacy project that only runs on 8.1 is exactly the reason to have several versions side by side. Refusing to install one does not modernise the project; it just means the developer goes and finds a different tool. Marking it clearly and letting them get on with their work is the more useful position.