PHP 8.2 reaches end of life on 31 December 2026, and PHP 8.4 is the natural next stop: it has security support until 31 December 2028 and is the default PHP in Debian 13. Going from 8.2 to 8.4 crosses two sets of changes, those of 8.3 and those of 8.4, so it pays to work through them in order.
This checklist is built from the two official migration guides and adds the testing and server steps around them.
1. Prepare
- Inventory each application: framework or CMS version, PHP extensions used (
php8.2 -m), cron jobs and queue workers that run PHP. - Check the framework first. Upgrade to a framework or CMS release that supports PHP 8.4 before touching PHP itself.
- Check Composer constraints. This lists the packages whose requirements block PHP 8.4:
composer prohibits php 8.4 - Build a staging copy with the same data shape as production. Never do the first run on the live server.
2. Changes from 8.2 to 8.3
Backward-incompatible changes worth checking:
- The Date extension now throws its own
DateErrorandDateExceptionsubclasses instead of warnings and generic exceptions in many cases. Reviewtry/catchblocks around date parsing. range()is stricter: it throws on objects, resources or arrays as bounds, on a step of 0, on a negative step for increasing ranges, and on infinite or NAN arguments.- Programs close to overflowing the call stack may now throw an
Error, controlled by the newzend.max_allowed_stack_sizesetting. - Assigning a negative index
$nto an empty array now makes the next index$n+1instead of 0. - The
opcache.consistency_checksdirective was removed.
Deprecations introduced in 8.3:
- Calling
get_class()orget_parent_class()without arguments. - Using
++on empty or non-alphanumeric strings (and, softly, on non-numeric strings); usestr_increment(). assert_options(), theASSERT_*constants and theassert.*INI settings.ldap_connect()with separate host and port, a negative width inmb_strimwidth(), andReflectionProperty::setValue()with a single argument.
3. Changes from 8.3 to 8.4
Backward-incompatible changes worth checking:
- Extensions removed from core: IMAP, OCI8, PDO_OCI and PSpell moved to PECL. Check whether your platform still packages them (see step 5).
exit()anddie()now behave more like functions: they followstrict_typesand normal type coercion.- The
E_STRICTerror level was removed (and the constant deprecated). - Several resources became objects (DBA, ODBC, parts of SOAP); replace
is_resource()checks as the guide describes. - Many functions now throw
ValueErroron invalid arguments, includinground()with an invalid mode,str_getcsv()with multi-byte separators, and GD image functions with an invalid quality. - The bundled PCRE2 is 10.44, so
{,3}is now a quantifier rather than literal text. - JIT defaults changed to
opcache.jit=disablewithopcache.jit_buffer_size=64M. JIT is still off by default, but if you enabled it by setting only the buffer size, check that it is really on. - For MySQL 8.0.24 and later, mysqlnd reports server wait timeouts as error 4031 instead of 2006. Reconnect logic that matches on 2006 needs updating.
Deprecations introduced in 8.4, in rough order of how often they show up:
- Implicitly nullable parameters.
function f(Foo $x = null)must becomefunction f(?Foo $x = null). This is usually the largest batch of messages. trigger_error()withE_USER_ERROR; throw an exception or callexit()instead.- Relying on the default
escapeparameter offputcsv(),fgetcsv()andstr_getcsv(); pass it explicitly. mysqli_ping(),mysqli_kill()andmysqli_refresh();lcg_value();session_set_save_handler()with more than two arguments; changingsession.sid_lengthorsession.sid_bits_per_character.- Raising zero to a negative power, and naming a class
_.
4. Test
- Lint with 8.4. Since PHP 8.3,
php -laccepts several files at once. It finds syntax errors, not runtime problems.find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n 200 php8.4 -l | grep -v '^No syntax errors' - Static analysis. PHPStan can analyse against a target version with its
phpVersionparameter (for 8.4,phpVersion: 80400). The PHPCompatibility standard for PHP_CodeSniffer checks code against atestVersion, for example--runtime-set testVersion 8.4; how far its checks reach depends on the version you install. - Automated fixes. Rector can apply many upgrade changes, such as making implicit nullable types explicit. Configure its PHP sets with
->withPhpSets()inrector.php, preview withvendor/bin/rector process --dry-run, and review the diff like any other change. - Run the test suite and click through the application on 8.4 with deprecations visible:
error_reporting = E_ALL,log_errors = On,display_errors = Off. Read the log afterwards. - Check the platform: on the target server,
composer check-platform-reqsconfirms that the real PHP version and extensions satisfy the installed packages.
5. Get PHP 8.4 on the server
- Debian 13: PHP 8.4 is the distribution default; install
php8.4-fpmand the extension packages you need. - Debian 12: ships 8.2. Either move to Debian 13 or use a third-party repository for 8.4, pinned so it does not replace other packages unexpectedly.
- Ubuntu 24.04 LTS: ships 8.3. PHP 8.4 requires a third-party repository.
- Ubuntu 26.04 LTS: ships 8.5, not 8.4. On that release, target 8.5.
Watch the removed extensions: Debian 13 has no IMAP extension package for PHP 8.4 in its main suites, so applications that call imap_* functions (webmail clients, helpdesk mail fetchers) need another solution before you move. php8.4-pspell is packaged in Debian 13 from the PECL extension.
sudo apt install php8.4-fpm php8.4-cli php8.4-mysql php8.4-xml php8.4-mbstring php8.4-curl php8.4-intl php8.4-zip php8.4-gd
php8.2 -m > /tmp/mods-82.txt; php8.4 -m > /tmp/mods-84.txt; diff /tmp/mods-82.txt /tmp/mods-84.txtCopy only your own settings: pool files in /etc/php/8.4/fpm/pool.d/ and small overrides in conf.d/. Do not copy a whole 8.2 php.ini.
6. Switch and keep a way back
# Nginx
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
sudo nginx -t && sudo systemctl reload nginx
# Apache with PHP-FPM
sudo a2disconf php8.2-fpm && sudo a2enconf php8.4-fpm
sudo apachectl configtest && sudo systemctl reload apache2
# CLI used by cron and workers
php -v
sudo update-alternatives --config phpLeave php8.2-fpm running until the new version has handled real traffic for a while; rolling back is then a one-line change and a reload. Restart queue workers and schedulers so they pick up the new binary. When everything is stable, remove the 8.2 packages.
What you should do
- Upgrade the framework and clear blockers reported by
composer prohibits php 8.4. - Fix implicit nullable parameters and the other 8.3 and 8.4 deprecations, using Rector and static analysis to speed it up.
- Confirm that every extension you need, especially IMAP, is available for 8.4 on your platform.
- Test on a staging copy with full error logging, then switch the FPM socket with the old version kept ready.
- Finish before 31 December 2026, when PHP 8.2 stops receiving upstream security fixes.
Sources
- php.net/manual/en/migration83.incompatible.php
- php.net/manual/en/migration83.deprecated.php
- php.net/manual/en/migration83.other-changes.php
- php.net/manual/en/migration84.incompatible.php
- php.net/manual/en/migration84.deprecated.php
- php.net/manual/en/migration84.removed-extensions.php
- php.net/supported-versions.php
- php.net/manual/en/features.commandline.options.php
- getcomposer.org/doc/03-cli.md#prohibits-why-not
- getcomposer.org/doc/03-cli.md#check-platform-reqs
- phpstan.org/config-reference#phpversion
- github.com/PHPCompatibility/PHPCompatibility
- getrector.com/documentation
- getrector.com/documentation/set-lists
- packages.debian.org/trixie/php
- packages.debian.org/trixie/php8.4-pspell
- qa.debian.org/madison.php?package=php-imap&text=on
- people.canonical.com/~ubuntu-archive/madison.cgi?package=php&text=on
- salsa.debian.org/php-team/php/-/blob/debian/main/8.4/debian/php-fpm.conf
- salsa.debian.org/php-team/php/-/blob/debian/main/8.4/debian/php-cli.postinst.extra
Comments
0 total · 0 threads