Pourquoi ces nouveautés sont importantes ?
Depuis sa première version, PHP a régulièrement évolué pour offrir aux développeurs davantage de productivité, une meilleure robustesse et une lisibilité accrue de leurs applications. Avec PHP 8, deux ajouts majeurs changent particulièrement la donne :
-
Les Types Union (
|
) : Cette fonctionnalité vous permet d’indiquer clairement qu’une variable, un paramètre ou une fonction peut manipuler plusieurs types. Plus besoin de docblocks ambigus : le moteur PHP valide désormais que la valeur renvoyée correspond à un des types autorisés (ex.int|string
,User|null
, etc.). -
L’expression
match
: Successeur modernisé duswitch
,match
permet d’écrire des branches de logique plus concises et plus robustes grâce à une comparaison stricte (équivalent de===
), l’absence de fall-through involontaire et la possibilité de retourner directement une valeur.
Dans cet article, je vais découvrir comment et pourquoi tirer parti de ces nouveautés
dans vos projets PHP 8, que ce soit pour renforcer la sécurité de type, améliorer la
lisibilité de vos conditions ou remplacer des switch
longs et répétitifs.
1. Les Types Union en PHP 8
1.1 Qu’est-ce qu’un type union ?
Avant PHP 8, lorsqu’une fonction pouvait retourner différents types, il n’existait pas de moyen natif de l’indiquer clairement. On se contentait bien souvent de docblocks ou de retours non typés. Désormais, grâce aux Types Union, vous pouvez spécifier plusieurs types possibles pour vos paramètres ou vos retours de fonction.
function parseNumber(string $input): int|float {
if (str_contains($input, '.')) {
return (float) $input;
}
return (int) $input;
}
Dans l’exemple ci-dessus, la fonction parseNumber
peut retourner soit un
int
, soit un float
, en fonction de la présence ou non d’un point
décimal dans la chaîne.
1.2 Pourquoi utiliser les types union ?
- Lisibilité du code : Les Types Union rendent explicite les différents types gérés, ce qui facilite la compréhension et la maintenance du code.
- Sécurité de type : Le moteur PHP valide que la valeur renvoyée correspond à l’un des types autorisés, limitant les comportements imprévus.
- API plus claire : Lorsqu’on publie une bibliothèque ou lorsqu’on travaille en équipe, spécifier clairement les types attendus et retournés évite les mauvaises utilisations et facilite la documentation.
1.3 Combiner plusieurs types (objets, null, erreurs, etc.)
Il arrive qu’une fonction puisse retourner, selon le contexte, soit un objet précis,
soit un objet représentant une erreur, ou encore null
. PHP 8 rend cela
très facile :
1000) {
return null;
}
// Sinon, on retourne un User
return new User($id, "John Doe");
}
// Utilisation
$result = getUser(42);
if ($result instanceof User) {
echo "Utilisateur trouvé : " . $result->name;
} elseif ($result instanceof ErrorObject) {
echo "Erreur : " . $result->message;
} else {
echo "Aucun utilisateur trouvé";
}
Dans ce scénario, la fonction getUser
peut renvoyer trois types :
User
, ErrorObject
ou null
. Grâce aux Types Union,
la signature de la fonction le rend parfaitement explicite.
2. L’Expression match
en PHP 8
2.1 Présentation
L’expression match
est une alternative moderne au traditionnel
switch
. Contrairement à switch
:
-
La comparaison est stricte : équivalent de
===
, au lieu de==
. - Elle retourne une valeur : plus besoin de stocker dans une variable manuellement à chaque branche.
-
Pas de risque de "fall-through" : les branches n’ont plus besoin de
se terminer par un
break
.
2.2 Syntaxe de base
Voici un exemple de base illustrant la différence entre switch
et
match
.
// Ancien code avec switch
$color = 'red';
switch ($color) {
case 'red':
$hex = '#FF0000';
break;
case 'green':
$hex = '#008000';
break;
case 'blue':
$hex = '#0000FF';
break;
default:
$hex = '#000000';
}
// Nouveau code avec match
$color = 'red';
$hex = match ($color) {
'red' => '#FF0000',
'green' => '#008000',
'blue' => '#0000FF',
default => '#000000',
};
Avec match
, plus besoin de break
. Chaque branche retourne
directement la valeur souhaitée.
2.3 Combiner match
et appels de fonctions
Il est tout à fait possible d’exécuter du code plus complexe ou d’appeler des fonctions au sein des branches :
function handleColor(string $color): string {
return match ($color) {
'red' => strtoupper('#ff0000'),
'green' => strtoupper('#008000'),
'blue' => strtoupper('#0000ff'),
default => defaultColorValue(),
};
}
function defaultColorValue(): string {
// Imaginons un mécanisme plus complexe ici
return '#000000';
}
echo handleColor('red'); // #FF0000
echo handleColor('purple'); // #000000 (valeur par défaut)
2.4 Gérer des conditions plus complexes
Bien que match
compare avant tout des valeurs par égalité stricte, on peut
l’utiliser avec des conditions plus élaborées. L’astuce consiste à faire le
match sur la valeur true, puis à placer la logique dans
chaque branche :
function matchComplex(int $number): string {
return match (true) {
$number < 0 => "Nombre négatif",
$number === 0 => "Zéro",
$number > 100 => "Très grand nombre",
default => "Nombre positif",
};
}
echo matchComplex(-5); // Nombre négatif
echo matchComplex(0); // Zéro
echo matchComplex(150); // Très grand nombre
echo matchComplex(42); // Nombre positif
Ce pattern est parfois controversé (car il détourne un peu l’idée initiale de
match
), mais il demeure pratique lorsqu’on souhaite gérer des branches
conditionnelles plus complexes tout en bénéficiant de la syntaxe plus concise de
match
.
3. Bonnes pratiques et scénarios d’utilisation
-
Types union et
null
: Évitez d’abuser denull
comme valeur de retour. Si un échec est fréquent, vous pourriez préférer lancer des exceptions ou renvoyer un objet dédié. Cependant, indiquer qu’une fonction peut retournernull
dans un type union reste pertinent dans certains cas. -
Lisibilité du
match
: Pour maintenir un code propre, évitez de mettre trop de logique dans chaque branche. Si la structure devient complexe, extrayez la logique dans des fonctions dédiées. -
Refactorisation :
Dans le cas de projets legacy, il est possible de migrer progressivement les
switch
simples versmatch
. Veillez à vérifier les cas où un fall-through volontaire aurait pu être utilisé par le passé. - Documentation : Avec les Types Union, mettez à jour vos docblocks et la documentation du projet (README, wiki, etc.) pour signaler clairement les retours possibles.
-
Combiner
match
et retours type union : Il est possible de renvoyer différents types selon les branches d’unmatch
:function processInput(int $value): string|int { return match (true) { $value < 10 => "Trop petit", $value === 10 => $value, // Renvoie un int default => "Valeur OK", // Renvoie un string }; }
4. Exemple complet : Types Union + match
Pour terminer, voici un exemple concret mêlant Types Union et
match
dans un contexte e-commerce où différents statuts de commande sont traités :
new Command($orderId, 99.99),
'cancelled' => new CancelledCommand($orderId, "Annulation demandée par le client."),
'error' => new ErrorObject("Erreur interne lors du traitement de la commande."),
default => new ErrorObject("Status inconnu: " . $status),
};
}
// Exemple d'utilisation
$result = processOrderStatus(12345, 'cancelled');
if ($result instanceof Command) {
echo "Commande " . $result->orderId . " traitée. Montant: " . $result->amount;
} elseif ($result instanceof CancelledCommand) {
echo "Commande " . $result->orderId . " annulée. Raison: " . $result->reason;
} else {
// Ici, $result est forcément un ErrorObject
echo "Erreur: " . $result->message;
}
Cet exemple montre qu’en combinant Types Union et l’expression match
, il est possible de rendre
la logique de traitement de différents statuts de commande claire et explicite.