La migration PHP 8.1 vers 8.3 semble anodine sur le papier — deux versions mineures, pas de changement majeur annoncé. En réalité, c'est un champ de mines de dépréciations devenues erreurs, de changements comportementaux subtils et de fonctions supprimées. Voici les incompatibilités que les changelogs officiels ne mettent pas assez en avant.
Les dépréciations devenues erreurs fatales
Propriétés dynamiques (PHP 8.2). C'est le breaking change le plus impactant. En PHP 8.1, assigner une propriété non déclarée générait un E_DEPRECATED. En PHP 8.2+, c'est une ErrorException. Des milliers de projets legacy utilisent des propriétés dynamiques sans le savoir — notamment via des DTO hérités, des tests avec des mocks, ou des classes de configuration.
<?php
// PHP 8.1 : E_DEPRECATED
// PHP 8.2+ : ErrorException
class Config {
public string $name = 'app';
}
$config = new Config();
$config->debug = true; // ERREUR en 8.2+
// Solution 1 : Déclarer la propriété
class Config {
public string $name = 'app';
public bool $debug = false; // Déclarer explicitement
}
// Solution 2 : #[AllowDynamicProperties] (temporaire)
#[\AllowDynamicProperties]
class Config {
public string $name = 'app';
}Interpolation ${} dans les chaînes (PHP 8.2). La syntaxe "${var}" et "${expr}" est dépréciée en 8.2 et génère une erreur en 8.3. Seule la syntaxe "{$var}" reste valide. Ce changement casse silencieusement des templates, des logs et des requêtes SQL construites avec des doubles guillemets.
<?php
$table = 'users';
// ERREUR en PHP 8.3
$sql = "SELECT * FROM ${table}"; // Deprecated
// Correct
$sql = "SELECT * FROM {$table}"; // OK
$sql = "SELECT * FROM " . $table; // OK aussiSuppression de utf8_encode() et utf8_decode() (PHP 8.2). Ces fonctions sont supprimées en PHP 8.2. Elles étaient mal nommées (elles convertissaient ISO-8859-1, pas UTF-8 arbitraire) et utilisées partout dans les projets legacy pour traiter les caractères spéciaux.
<?php
// PHP 8.1 : fonctionne
$encoded = utf8_encode($string);
// PHP 8.2+ : Fatal Error
// Remplacement :
$encoded = mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1');Changements comportementaux subtils
Readonly properties et le clonage (PHP 8.2). PHP 8.2 introduit la possibilité de réinitialiser les propriétés readonly pendant le clonage via __clone(). Si ton code dépendait du comportement de PHP 8.1 où le clonage ne réinitialisait pas les propriétés readonly, le comportement change.
<?php
class Order {
public function __construct(
public readonly string $id,
public readonly string $status = 'draft',
) {}
public function __clone(): void {
// PHP 8.2+ : on peut réinitialiser readonly dans __clone
$this->status = 'cloned';
}
}Sérialisation des enums. Les enums PHP 8.1 utilisent serialize() et json_encode() différemment selon les versions mineures. En 8.1, json_encode() sur un enum backed retourne sa valeur. En 8.3, le comportement est standardisé mais peut diverger si tu avais implémenté JsonSerializable manuellement sur tes enums.
Migration Symfony en vue ?
Estimer ma migration →Fibers et edge cases. Les Fibers, introduites en PHP 8.1, ont vu des corrections de bugs en 8.2 et 8.3 qui changent le comportement dans les cas limites — notamment l'ordre d'exécution des destructeurs et le comportement des exceptions non catchées dans une Fiber suspendue. Si tu utilises Messenger async ou ReactPHP, teste soigneusement.
Les nouvelles fonctionnalités à adopter immédiatement
La migration n'est pas que des breaking changes. PHP 8.2 et 8.3 apportent des fonctionnalités qui améliorent la qualité du code :
- Typed class constants (PHP 8.3) :
const string VERSION = '1.0';— enfin du typage sur les constantes - json_validate() (PHP 8.3) : valide du JSON sans le décoder — plus performant que
json_decode()suivi d'un check d'erreur - #[Override] attribute (PHP 8.3) : marque explicitement qu'une méthode surcharge un parent — erreur de compilation si le parent change sa signature
- Readonly classes (PHP 8.2) :
readonly class DTO {}— toutes les propriétés sont automatiquement readonly - DNF types (PHP 8.2) :
(A&B)|null— combinaison d'intersection et d'union de types
<?php
// PHP 8.3 : typed class constants
class ApiClient {
const string BASE_URL = 'https://api.example.com';
const int TIMEOUT = 30;
}
// PHP 8.3 : #[Override]
class AdminController extends BaseController {
#[\Override]
public function index(): Response
{
// Si BaseController::index() est renommé,
// PHP lève une erreur à la compilation
}
}
// PHP 8.3 : json_validate()
if (json_validate($payload)) {
$data = json_decode($payload, true);
}Stratégie de migration en 4 étapes
Voici la méthodologie que nous appliquons sur les projets clients :
- Étape 1 : Scanner avec PHPCompatibility. L'outil détecte automatiquement les incompatibilités entre versions PHP. Lance-le via PHPCS avec le standard PHPCompatibility configuré pour PHP 8.3.
- Étape 2 : Corriger automatiquement avec Rector. Rector a des rulesets dédiés pour PHP 8.2 et 8.3. Il corrige automatiquement les propriétés dynamiques, l'interpolation de chaînes, et les appels de fonctions supprimées.
- Étape 3 : Rollout progressif. Monte d'abord en PHP 8.2, stabilise, puis passe en 8.3. Deux migrations de 2 semaines valent mieux qu'une migration big bang de 2 mois.
- Étape 4 : Adopter les nouvelles fonctionnalités. Une fois migré, utilise
#[Override], les typed constants etjson_validate()dans tout nouveau code.
# Étape 1 : Scanner les incompatibilités
composer require --dev phpcompatibility/php-compatibility
vendor/bin/phpcs --standard=PHPCompatibility \
--runtime-set testVersion 8.3 \
-p src/
# Étape 2 : Corriger avec Rector
composer require --dev rector/rector
# Configurer rector.php avec PHP_83 target
vendor/bin/rector process --dry-run
vendor/bin/rector processNe migre pas à l'aveugle
La migration PHP est un exercice technique mais aussi stratégique. Avant de toucher au code, il faut un état des lieux précis : quelles dépendances sont compatibles ? Quels bundles Symfony sont impactés ? Quel est le volume de code à adapter ? Bear Scan cartographie automatiquement ces informations et produit un rapport avec le chiffrage de l'effort — pour que tu saches exactement ce qui t'attend avant de créer ta branche de migration.
Migration Symfony en vue ?
Bear Upgrade migre votre codebase 3-5× moins cher qu'une ESN.