Soyons honnêtes, les valeurs nulles, ça craint. Elles ont été appelées l'erreur d'un milliard de dollars, mais les programmeurs de la plupart des langages doivent encore y faire face (à l'exception de ceux qui travaillent en Rust). (Sauf pour ceux qui travaillent en Rust.) Elles craignent particulièrement lorsque vous avez une variable qui peut être un objet ou une valeur nulle. S'il s'agit d'un objet, on peut supposer que l'on connaît son type et ce qu'il peut faire ; s'il s'agit de null, on ne peut pas faire grand-chose avec.
La manière standard de gérer cette situation est de passer par une vérification conditionnelle, le modèle éponyme "if null" :
<?php $user = get_user($id) ; if (!is_null($user)) { $address = $user->getAddress() ; if (!is_null($address)) { $state = $address->state ; If (!is_null($state)) { // And so on. } }.
C'est, bien sûr, grossier et difficile à lire, mais nécessaire si vous voulez éviter la redoutable erreur Call to a member function on null (Appel à une fonction membre sur null)
.
Dans un monde idéal, nous pourrions structurer notre code de manière à ce que null soit impossible. Malheureusement, nous ne vivons pas dans un monde idéal, surtout lorsque nous devons gérer le code de quelqu'un d'autre. C'est pourquoi un certain nombre de langages disposent de ce que l'on appelle un "opérateur nullsafe". Et, depuis la version 8.0, PHP est l'un d'entre eux.
L'opérateur nullsafe est en fait un modificateur sur l'accès aux objets, que ce soit les propriétés ou les appels de méthodes. Si l'un ou l'autre est précédé d'un ?
, cela a le même effet que d'envelopper l'accès dans une vérification if (!is_null))
. L'exemple ci-dessus, par exemple, devient :
<?php $state = get_user($id)?->getAddress()?->state ;
Dans cet exemple, la valeur renvoyée par get_user()
est soit un objet User
, soit null. La valeur retournée par User::getAddress()
est soit un objet Address
, soit null. Si get_user()
renvoie null, la fonction ?
l'attrape et renvoie null, en ignorant tout le reste de la ligne. Il en va de même pour getAddress()
. A la fin, $state
est soit une valeur d'état valide provenant de la propriété state
de l'adresse, soit null.
L'opérateur nullsafe court-circuite le reste de la ligne la première fois qu'une valeur nulle est rencontrée. Cela signifie que même les appels dynamiques ou les appels erronés plus tard dans le processus ne se produisent pas. Voici un exemple :
<?php $bestSaleItem = $catalog?->getProducts(get_seasonal_type())?->mostPopular(10)[5] ;
Si $catalog
est nul, get_seasonable_type()
ne sera pas appelé du tout. La chaîne entière s'arrête après avoir détecté que $catalog
est null et met $bestSaleItem
à null. De même, si getProducts()
renvoie null
, alors mostPopular()
n'est jamais appelée. Cependant, cela ne s'applique pas aux tableaux. Si mostPopular()
est encore appelée et qu'elle renvoie null, vous obtiendrez toujours une erreur Trying to access array offset on value of type null (Essai d'accès au tableau offset sur une valeur de type null)
.
L'opérateur nullsafe a bien sûr quelques limites. Il ne fonctionne que pour la lecture de propriétés ou de méthodes, et non pour l'écriture de valeurs. Il ne traite pas non plus les références, car les références nécessitent des valeurs réelles. (Ceci est cohérent avec les autres langages qui ont un opérateur nullsafe).
Il ne fournit pas non plus d'indication sur l'étape de la chaîne qui a retourné null ; Si $bestSaleItem
est null, cela peut être parce que $catalog
était null, ou que getProducts()
a retourné null, ou que mostPopular()
a retourné null. Il n'y a aucun moyen de le savoir. Parfois, cela ne pose pas de problème. Mais si vous avez besoin de faire la différence, nullsafe ne vous aidera pas. Pour cela, vous avez vraiment besoin d'une monade Either, mais c'est un sujet pour une autre fois...
Néanmoins, nullsafe peut vous aider à réduire la quantité de code de type "boilerplate" flottant autour de vos chaînes de méthodes orientées objet. Nous devons remercier Ilija Tovilo pour le RFC nullsafe.
Restez dans les parages pour notre prochain article, où nous couvrirons une série de petites améliorations qui rendront PHP 8.0 plus agréable à vivre.