PHP 8.2 est sorti le 8 décembre. Les classes readonly font les gros titres. La dépréciation des propriétés dynamiques, elle, demande votre attention concrète.
Les propriétés dynamiques dépréciées
PHP a toujours permis d’ajouter des propriétés à des objets sans les déclarer dans la classe :
class User {}
$user = new User();
$user->name = 'Alice'; // aucune déclaration, aucune erreur... jusqu'ici
En 8.2, ça déclenche un avertissement de dépréciation. En PHP 9.0, ce sera une erreur fatale. Le délai de grâce existe, mais le compteur tourne.
La logique est solide : les propriétés dynamiques sont une source classique de typos qui passent silencieusement (écrivez $user->nmae et PHP crée simplement une nouvelle propriété au lieu de se plaindre). Des déclarations explicites rendent le contrat de la classe lisible et donnent aux outils matière à travailler.
La migration est essentiellement mécanique : déclarez les propriétés, ou posez #[AllowDynamicProperties] sur les classes legacy que vous ne pouvez pas encore toucher.
Les classes readonly
8.1 avait ajouté readonly sur les propriétés individuelles. 8.2 l’ajoute sur la déclaration de classe elle-même :
readonly class Point {
public function __construct(
public float $x,
public float $y,
public float $z,
) {}
}
Toutes les propriétés promues et déclarées explicitement deviennent readonly automatiquement. Les value objects (coordonnées, montants monétaires, identifiants) sont la cible évidente. La syntaxe est propre et l’intention se lit clairement.
Une contrainte : les classes readonly ne peuvent pas avoir de propriétés non typées, ce qui était déjà une mauvaise idée avec readonly de toute façon.
Les types DNF
Les types en Forme Normale Disjonctive permettent de combiner types union et types intersection :
function process(Countable&Iterator|null $collection): void { ... }
(Countable&Iterator)|null : un objet qui implémente les deux interfaces, ou null. Cela couvre des expressions de type que les unions de 8.0 et les intersections de 8.1 approchaient chacune sans pouvoir les représenter ensemble.
L’extension Random
Une extension Random dédiée remplace les fonctions éparpillées rand(), mt_rand(), random_int() par une API orientée objet :
$rng = new Random\Randomizer();
$rng->getInt(1, 100);
$rng->shuffleArray($items);
Les moteurs sont interchangeables : Mersenne Twister, PCG64, Xoshiro256StarStar, ou CryptoSafeEngine pour les contextes sensibles à la sécurité. Même code, moteur déterministe avec seed dans les tests, moteur cryptographique en production.
8.2 est une version de consolidation. La dépréciation des propriétés dynamiques est la seule décision à prendre maintenant.
null, false, et true comme types autonomes
PHP avait les types nullable depuis 7.1 et les types union depuis 8.0, mais null comme déclaration de type autonome n’était pas valide. 8.2 corrige ça :
function alwaysNull(): null {
return null;
}
function disabled(): false {
return false;
}
function enabled(): true {
return true;
}
false et true comme types autonomes sont utiles quand on veut être précis sur ce qu’une fonction peut réellement retourner. C’est étroit mais juste : une fonction qui retourne false en cas d’échec et une chaîne en cas de succès devrait déclarer string|false, et maintenant les deux côtés de cette union sont de vrais types.
Les constantes dans les traits
Les traits pouvaient contenir des propriétés et des méthodes. Les constantes étaient le trou dans la raquette. 8.2 le comble :
trait Timestamps {
public const DATE_FORMAT = 'Y-m-d H:i:s';
public function formatCreatedAt(): string {
return $this->createdAt->format(self::DATE_FORMAT);
}
}
class Article {
use Timestamps;
}
echo Article::DATE_FORMAT; // 'Y-m-d H:i:s'
La constante appartient à la classe qui utilise le trait, pas au trait lui-même, donc on ne peut pas accéder à Timestamps::DATE_FORMAT directement. Comportement de scope attendu, cohérent avec le fonctionnement des méthodes de trait.
#[SensitiveParameter]
Les stack traces ont toujours été un risque : les arguments de fonction sont loggés verbatim, ce qui veut dire que les mots de passe et les tokens se retrouvent dans les logs d’erreur et les dashboards de monitoring. 8.2 ajoute un attribut pour stopper ça :
function authenticate(
string $user,
#[\SensitiveParameter] string $password,
): bool {
// si ça lève une exception, la stack trace affiche :
// authenticate('alice', Object(SensitiveParameterValue))
return hash('sha256', $password) === getStoredHash($user);
}
La valeur du paramètre dans la trace est remplacée par un objet SensitiveParameterValue. Un attribut, zéro excuse pour ne pas l’ajouter sur chaque fonction qui touche à des credentials.
Les syntaxes d’interpolation de chaînes dépréciées
Deux façons d’interpoler des expressions dans des chaînes sont dépréciées en 8.2 :
$name = 'world';
// Celles-ci sont dépréciées :
echo "Hello ${name}"; // utiliser "$name" ou "{$name}"
echo "Hello ${getName()}"; // utiliser "{$this->getName()}"
Les formes ${...} créaient une ambiguïté entre les variables variables et les expressions. La syntaxe plus propre {$...} a toujours été là et fait la même chose. C’est essentiellement un travail de recherche-remplacement sur les bases de code qui ont adopté les formes dépréciées par habitude.
utf8_encode() et utf8_decode() dépréciées
Ces deux fonctions sont dépréciées en 8.2 et disparaissent en 9.0. Leur comportement a toujours été plus étroit que ce que les noms suggéraient : utf8_encode() convertit ISO-8859-1 en UTF-8, pas “n’importe quel encodage en UTF-8”.
// Déprécié en 8.2 :
$utf8 = utf8_encode($latin1String);
// Utiliser à la place :
$utf8 = mb_convert_encoding($latin1String, 'UTF-8', 'ISO-8859-1');
mb_convert_encoding() ou iconv() gèrent le cas général. Si vous traitez vraiment des entrées en Latin-1, le remplacement est direct.
Les fonctions de chaîne indépendantes de la locale
Plusieurs fonctions de chaîne variaient silencieusement leur comportement selon la locale système, produisant des résultats différents en production par rapport à un container de dev. En 8.2, elles sont indépendantes de la locale et ne gèrent que l’ASCII :
// strtolower, strtoupper, stristr, stripos, strripos,
// lcfirst, ucfirst, ucwords, str_ireplace font maintenant une conversion
// de casse ASCII uniquement.
// Pour un comportement sensible à la locale, utiliser les équivalents mb_* :
$lowered = mb_strtolower($text, 'UTF-8');
C’est un correctif de cohérence. Si votre code reposait sur le comportement sensible à la locale de ces fonctions, il était déjà cassé sur des systèmes avec des configurations de locale différentes. 8.2 rend le comportement déterministe partout, ce qui est ce qu’on voulait vraiment.
str_split() sur une chaîne vide
Un changement de comportement discret à noter :
// PHP 8.1 : str_split('') === ['']
// PHP 8.2 : str_split('') === []
Le nouveau comportement a plus de sens : découper rien ne produit rien. Si vous vérifiez count(str_split($input)), une entrée vide ne produit plus un count de 1.