The three real options when you are on 8.1
Dockerfile
FROM debian:bookworm-slim AS build
RUN apt-get update && apt-get install -y --no-install-recommends \
git ca-certificates build-essential autoconf bison re2c pkg-config \
libxml2-dev libsqlite3-dev libssl-dev zlib1g-dev libonig-dev \
libcurl4-openssl-dev libicu-dev libpq-dev
RUN git clone --depth 1 --branch PHP-8.1-CVE-2026-109618-109619 \
https://github.com/php/php-src.git /src
WORKDIR /src
RUN ./buildconf --force \
&& ./configure --prefix=/opt/php \
--enable-fpm --enable-opcache --enable-mbstring --enable-intl \
--with-openssl --with-zlib --with-curl --with-pdo-pgsql \
&& make -j"$(nproc)" \
&& make install
Aiming at the right version
- 8.1: security support already over (end of 2025)
- 8.2: until December 31, 2026
- 8.3: until December 31, 2027
- 8.4: until December 31, 2028
The tooling that does most of the work
Json
{
"require": {
"php": ">=8.3"
},
"config": {
"platform": {
"php": "8.3.36"
}
}
}
Php
<?php
// rector.php
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
->withPhpSets(php83: true)
->withSkip([__DIR__ . '/src/Legacy']);
Neon
# phpstan.neon
parameters:
level: 6
phpVersion: 80300
paths:
- src
- tests
Yaml
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.3']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/phpunit
- dynamic properties, deprecated in 8.2 (#[\AllowDynamicProperties] as a temporary escape hatch, a real property as the fix);
- "${var}" string interpolation, deprecated in 8.2;
- utf8_encode() / utf8_decode(), deprecated in 8.2;
- implicitly nullable parameters (function f(Foo $x = null)), deprecated in 8.4. Rector fixes them with ?Foo.
The traps
- Compiling your own branches is tenable for a week, not for a year. It's a bridge to get through the incident, not a strategy.
- Running Rector on the whole codebase in one PR makes review impossible. One set per PR, one directory at a time on the legacy, and the CI on both versions in between.
- Deprecations don't break anything until the day they become errors. If E_DEPRECATED is filtered out of the production logs, you find out at the next major version. Keep them visible in CI with PHPUnit's failOnDeprecation="true".