Upgrades to Newer PHP and Libraries
The upgrading section decides on upgrading code to a newer PHP and to new library versions: what a
newer PHP can write better, what it deprecated and what to replace it with, and what the libraries your project uses have renamed
or removed.
The decisions of the section are grouped by what they are about: syntax covers new syntax (?->,
match, the pipe operator, promoted properties, readonly), functions new functions
(str_contains(), array_any(), clamp()), classes attributes and interfaces that
PHP added (#[\Override], Stringable), phpdoc annotations that an attribute or a keyword
replaced, php what PHP deprecated, api replacement maps for the API of libraries and of your own code,
and declarations uses of what your own code marked @deprecated. The value required turns on
the newer form (“write the newer form wherever you find the older one”), and the value forbidden turns on
replacing a deprecated form (“the deprecated form must not be here”).
upgrading:
functions:
substringFunctions: required
syntax:
nullsafe: required
pipe: required
function describe(?User $user, string $url): string
{
if (strpos($url, 'https://') === 0) { // The `strpos()` comparison must be written with `str_starts_with()`.
$scheme = 'secure';
}
$name = $user === null ? null : $user->getName(); // The ternary testing for null must be written with `?->`.
return trim(strtolower(strip_tags($name))); // The nested calls must be written with the pipe operator.
}
function describe(?User $user, string $url): string
{
if (str_starts_with($url, 'https://')) {
$scheme = 'secure';
}
$name = $user?->getName();
return $name |> strip_tags(...) |> strtolower(...) |> trim(...);
}
Each decision that writes newer PHP code knows which PHP version introduced it, and it applies only when the target PHP version of the project allows it. The
pipe operator from the example gets into the code only when the composer.json of the project allows nothing older
than PHP 8.5; until then, dresscode config prints no effect: php next to the decision. Deprecations, on
the other hand, are always reported, because code written for an older PHP will one day run on a version that has
deprecated it.
You do not have to write the decisions of the section one by one. The modernizations set turns on everything that
writes a newer form, and the deprecations set everything that fixes deprecated code. You add both on top of the
standard in the use key, and as DressCode
gains new decisions, the sets bring them in without any change to your configuration. Upgrading to a Newer PHP shows what upgrading to a newer PHP looks like in
practice, and Code Upgrade shows how to upgrade libraries.
PHP Migration Guides
DressCode carries what PHP deprecated, and how the same thing is written today, as the data of the package php.
This data has the same format as the upgrade data of libraries in the upgrade packages: a deprecated function or constant is
reported, utf8_encode() is rewritten to mb_convert_encoding(), and a call that does nothing on the
target version, such as curl_close(), disappears. A single requirement, upgrading.php.deprecatedApi,
turns on the whole guide, and everything the data finds is reported under it, so this is also the name you use in
--only, severity, fixRisky or in a dresscode:ignore comment. To exclude a
single item of the data, set it to keep in the map that the item belongs to:
upgrading:
php:
deprecatedApi: forbidden
api:
replacedCalls:
'curl_close($handle)': keep
No map can name an argument value that PHP deprecated, such as E_USER_ERROR passed to trigger_error()
or an integer passed to a ctype_*() function. A separate decision, upgrading.php.deprecatedArgument,
reports such a value. The nette standard and the deprecations set turn on both decisions.
API Replacement Maps
The keys under upgrading.api are replacement maps: a class for a class, a member for a member, a call for a
template, a forbidden function with advice on what to write instead. A project writes them when it renames its own code, and
upgrade packages carry them for whole ecosystems. The decision upgrading.packages: yes, which both sets,
deprecations and modernizations, turn on, adds the package data as a layer below the maps of the
project. The value keep takes an item the project does not want out of the maps
(Acme\Mail\SmtpTransport: keep), and an exclamation mark after a map key (replacedClasses!:) replaces
the maps of lower layers and the package data with what the project writes. The page Replacement Maps describes the grammar of the maps.
upgrading.functions.array_is_list
array_is_list() for a hand-written test of a list.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.1 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.php.nullArrayKey
[null => 1] is ['' => 1], which it becomes.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.syntax.spreadForArrayMerge
[...$a, ...$b] for array_merge($a, $b).
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.1 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.syntax.dynamicClassConstant
X::{$name} for constant(X::class . '::' . $name).
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.3 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.php.constructorReturnValue
A return with a value in a constructor or destructor, deprecated by PHP 8.6.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.php.sleepAndWakeup
__sleep() and __wakeup(), which PHP 8.5 soft-deprecated.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep · covers modern_serialization_methods
upgrading.classes.Override
#[\Override] on a member overriding an inherited one.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.3 · needs types · standards: perCs keep, psr12
keep, nette keep, symfony keep, laravel keep
upgrading.syntax.promotedProperties
A constructor parameter only assigned to a property.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep ·
covers SlevomatCodingStandard.Classes.RequireConstructorPropertyPromotion
upgrading.syntax.readonlyClasses
A class whose every property is readonly.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.2 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.phpdoc.readonly
readonly for @readonly.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.1 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep ·
covers phpdoc_readonly_class_comment_to_keyword
upgrading.classes.Stringable
A class with __toString() declares it.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.functions.arraySearchFunctions
array_any(), array_all(), array_find() and array_find_key() for a
foreach or an array_filter() that only asks what they answer.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.4 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.syntax.match
match for a switch whose every case returns or assigns one value.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.php.returnInFinally
A return inside finally, which PHP 8.6 deprecated.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.php.caseSemicolon
case 1; and default; are case 1: and default:.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs forbidden, psr12 forbidden,
nette forbidden, symfony forbidden, laravel forbidden · covers
switch_case_semicolon_to_colon, PSR2.ControlStructures.SwitchDeclaration
upgrading.syntax.throwExpression
$a ?? throw new E() for an if throwing where the assigned value is null.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.syntax.cloneWith
clone($o, [...]) for a clone followed by assignments to the copy.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.5 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.php.backticks
The backtick operator PHP 8.5 deprecated, written as a shell_exec() call.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony forbidden, laravel keep ·
covers backtick_to_shell_exec
upgrading.syntax.nullsafe
$a?->b for $a !== null ? $a->b : null and $a === null ? null : $a->b.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep ·
covers SlevomatCodingStandard.ControlStructures.RequireNullSafeObjectOperator
upgrading.syntax.pipe
Calls nested one in another, each handing its result to the next, written with the pipe operator of PHP 8.5 in the order they
run: $x |> a(...) |> b(...) for b(a($x)).
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.5 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.syntax.pipeMinCalls
The calls a nest has at least for the pipe operator to be written.
- a number from 2
Default 3 · PHP 8.5 · standards: perCs 3, psr12 3, nette
3, symfony 3, laravel 3
upgrading.functions.arrayFirstLast
array_first(), array_last() for reset(), end() read as an item.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.5 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.syntax.firstClassCallables
foo(...) for Closure::fromCallable(), a forwarding closure and 'self::foo'; from PHP
8.6 a partial application.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.1 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.functions.clamp
clamp($x, $lo, $hi) for max($lo, min($x, $hi)).
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.6 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.php.csvEscapeArgument
The escape argument of fgetcsv() and kin written out, since PHP 8.4 deprecated relying on its
default.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.functions.get_debug_type
get_debug_type($x) for is_object($x) ? get_class($x) : gettype($x).
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.functions.json_validate
json_validate($s) for json_decode($s) !== null && json_last_error() === JSON_ERROR_NONE.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.3 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.syntax.newInInitializer
Clock $clock = new SystemClock for a null default the body swaps for the object.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.1 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.functions.substringFunctions
str_contains(), str_starts_with(), str_ends_with() for strpos() !== false,
substr() ===.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep · covers modernize_strpos
upgrading.functions.roundingMode
RoundingMode::HalfAwayFromZero for PHP_ROUND_HALF_UP.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.4 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.classes.SensitiveParameter
#[\SensitiveParameter] of PHP 8.2 on a parameter holding a secret, which keeps its value out of stack traces.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.2 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.classes.SensitiveParameterNames
The names of the parameters holding a secret, whatever their letter case and underscores.
- a list of names
Default [password, passphrase, secret, apiKey, privateKey] · PHP 8.2 · standards: perCs
[password, passphrase, secret, apiKey, privateKey], psr12
[password, passphrase, secret, apiKey, privateKey], nette
[password, passphrase, secret, apiKey, privateKey], symfony
[password, passphrase, secret, apiKey, privateKey],
laravel [password, passphrase, secret, apiKey, privateKey]
upgrading.php.dollarBraceInterpolation
${name} is {$name}.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony forbidden, laravel keep ·
covers simple_to_complex_string_variable
upgrading.syntax.octalPrefix
0o17 for 017.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.1 · standards: perCs keep, psr12 keep,
nette required, symfony keep, laravel keep · covers octal_notation
upgrading:
syntax:
octalPrefix: required
$mode = 0755; // The octal number `0755` must be written `0o755`.
$zero = 0;
$flags = 0x1F;
$mode = 0o755;
$zero = 0;
$flags = 0x1F;
upgrading.php.reservedNames
Names PHP 8.6 reserved, in declarations and imports.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.phpdoc.deprecated
#[\Deprecated] for @deprecated.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.4 · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.php.implicitNullable
A parameter type made nullable by its default null alone, int $x = null, deprecated by PHP 8.4 and
written ?int $x = null.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony forbidden, laravel forbidden · covers
nullable_type_declaration_for_default_null_value, SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue
upgrading.packages
Whether the code follows the upgrading files of the installed packages, their maps laid under those the project writes and the rules of the plugin of a package turned on where its files say.
yesorno
Default no · standards: perCs no, psr12 no, nette
no, symfony no, laravel no
upgrading.api.replacedClasses
A class written instead of another one, both fully qualified (Acme\Old\Mailer: Acme\Mail\Mailer).
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.api.replacedFunctions
A function written instead of another one (acme_send: Acme\Mail\send).
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.php.deprecatedApi
A function, a constant or a member PHP deprecated, written as what replaces it as the upgrading data of PHP say,
mb_convert_encoding() for utf8_encode(), or reported.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep ·
covers Generic.PHP.DeprecatedFunctions
upgrading.api.replacedMembers
A constant, a method or a property written instead of another one of the class
(Acme\Mail\Mailer::send(): sendMessage()).
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · needs types · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.api.replacedCalls
A call written as the template says (Acme\Mail\Mailer::send($to, $body): send(new Message($to, $body))).
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.api.forbiddenClasses
A class that may not be used, with what to do instead (Acme\Legacy\Db: "use the repository").
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.api.forbiddenFunctions
A function that may not be called, with what to do instead.
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep · covers Generic.PHP.ForbiddenFunctions,
Generic.PHP.SAPIUsage, Squiz.PHP.DiscouragedFunctions
upgrading.api.forbiddenConstants
A global constant that may not be used, with what to do instead (E_STRICT: "it no longer does anything").
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.api.forbiddenMembers
A constant, a method or a property that may not be used, with what to do instead.
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · needs types · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.api.attributeForAnnotation
An attribute written for an annotation (@ORM\Entity: ORM\Entity).
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.api.attributeForMember
An attribute written for a member the class declares by convention.
- a map in the grammar of replacement maps, an entry is withdrawn with the
value
keep keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
keep, symfony keep, laravel keep
upgrading.declarations.deprecatedClass
A class whose declaration is @deprecated, replaced by the class the deprecation names, reported where it
names none.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · needs types · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.declarations.deprecatedMember
A constant, method or property whose declaration is @deprecated, replaced by the member the deprecation names,
reported where it names none.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · needs types · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
The replacement can be a member of the same class, and for a static call also a public static method of another class;
DressCode writes it the way the file can reach it and adds an import where needed. The replacement must accept the call the same
way as the original method; parameter names matter only where the call passes arguments by name. But the annotation is true only
for the installed version. Where the deprecated class or its replacement belongs to a package installed in a newer version than
the lowest version the composer.json of the project allows, DressCode rewrites nothing, because the replacement may
not exist in the older version, and it does not report the deprecated class or member of such a package either. To install the
lowest versions into vendor, run composer update --prefer-lowest. For a package that the project does
not require itself, or requires without a lower bound, the deprecated use is only reported. The fix is risky, like every replacement based on an annotation: the
annotation says what to write, not that it behaves the same, so the fix is made only with fixRisky or
--fix-risky. A method called on an object whose replacement is in another class is only reported, never
rewritten.
typeAnalysis: phpstan
upgrading:
declarations:
deprecatedMember: forbidden
use Acme\Text\Text;
$valid = Text::check($s); // Static method `Acme\Text\Text::check()` is deprecated: use Acme\Text\Encoding::isValid().
use Acme\Text\Encoding;
use Acme\Text\Text;
$valid = Encoding::isValid($s);
upgrading.declarations.deprecatedFunction
A function whose declaration is @deprecated, replaced by the function the deprecation names, reported where it
names none.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · needs types · standards: perCs keep, psr12 keep,
nette keep, symfony keep, laravel keep
upgrading.php.deprecatedArgument
An argument whose value PHP deprecated is reported, E_USER_ERROR given to trigger_error() or an
integer to a ctype_*() function.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep