PHP 8.4 apporte deux nouveautés qui changent la façon d'écrire des classes : les property hooks, qui attachent du code à la lecture ou à l'écriture d'une propriété, et la visibilité asymétrique, qui permet de lire une propriété publiquement tout en limitant qui peut la modifier.
Les property hooks
Un hook set valide ou transforme la valeur avant de la stocker ; un hook get peut calculer une propriété « virtuelle » sans stockage :
<?php
final class Utilisateur
{
public string $email {
set (string $valeur) {
if (!filter_var($valeur, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Adresse e-mail invalide : $valeur");
}
$this->email = strtolower($valeur);
}
}
// Propriété virtuelle : calculée à chaque lecture, jamais stockée.
public string $nomComplet {
get => "{$this->prenom} {$this->nom}";
}
public function __construct(
public string $prenom,
public string $nom,
string $email,
) {
$this->email = $email;
}
}
$u = new Utilisateur('Ada', 'Lovelace', 'Ada@Example.org');
echo $u->email; // ada@example.org
echo $u->nomComplet; // Ada Lovelace
Le code appelant ne change pas : on continue d'écrire $u->email = '…'. On peut donc ajouter une validation à une propriété publique existante sans casser son API.
La visibilité asymétrique
Cas classique : un solde lisible par tout le monde mais modifiable uniquement par la classe elle-même. Avant, il fallait une propriété privée et un getter. Le diff parle de lui-même :
final class Compte
{
- private int $solde = 0;
-
- public function getSolde(): int
- {
- return $this->solde;
- }
+ public private(set) int $solde = 0;
public function crediter(int $montant): void
{
$this->solde += $montant;
}
}
Une écriture depuis l'extérieur lève désormais une Error : « Cannot modify private(set) property Compte::$solde from global scope ».
Bonus : de nouvelles fonctions pour les tableaux
array_find(), array_find_key(), array_any() et array_all() évitent enfin les boucles foreach écrites pour trouver un élément :
$commandes = [
['id' => 1, 'montant' => 45, 'payee' => true],
['id' => 2, 'montant' => 180, 'payee' => false],
['id' => 3, 'montant' => 320, 'payee' => true],
];
$grosse = array_find($commandes, fn (array $c) => $c['montant'] > 100); // commande n° 2
$impayee = array_any($commandes, fn (array $c) => !$c['payee']); // true
$toutesPayees = array_all($commandes, fn (array $c) => $c['payee']); // false
// Et plus besoin de parenthèses autour de « new » pour chaîner un appel :
$date = new DateTimeImmutable()->format('d/m/Y');
Pour migrer un projet existant, Rector propose un jeu de règles PHP 8.4 qui fait une bonne partie du travail.