A plugin written against PHP 5.6, still in production, about 14k lines. The goal was to get it running on 8.3 with the smallest possible diff, because a rewrite was not going to be approved and would not have been a good idea anyway.
Doing it the other way round means debugging runtime errors one page load at a time, which on a plugin this size is weeks.
The single biggest category. Since 8.1 this is a deprecation, and with warnings
promoted it is a wall of noise. Most of it was trim($maybe_null) where
the value comes out of a meta field that has never been set. A null coalescing
default at the call site fixed nearly all of them.
function fmt($sep = ',', $items) {} // deprecated
function fmt(?string $sep, array $items) {} // fine
Assigning to a property that was never declared is deprecated as of 8.2. In a 2014-era codebase this is everywhere. Declaring the properties is the right fix; the attribute that suppresses the deprecation is for buying time, not for keeping.
$s{0} is gone. Mechanical replacement with $s[0].
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level=1
Start at level 0 or 1, not at the maximum. The goal is a clean run at a low level that you then raise one step at a time, not a list of 4,000 problems you will never read. Raising the level after each green run turned a hopeless list into about a week of small commits.
Sorting behaviour changed. usort is stable as of 8.0, which is an
improvement, but code that relied on the old unstable order to produce a particular
tie-break now produces a different one. Nothing errors. The output is just quietly
different. Worth diffing a report before and after rather than trusting that a clean
static analysis means a clean upgrade.