PHP 7.1 : les évolutions du typage

8 septembre 2016php, php-7.1
 Cet article a été rédigé il y a plusieurs années et peut ne plus être tout à fait à jour…

This post is also available in English.
Ceci est le quatrième article d’une série à propos de PHP 7.1.


Un des changements importants apportés l’an dernier par PHP 7.0 portait sur le typage, avec l’introduction des déclarations de types scalaires pour les paramètres de fonctions/méthodes et pour leur valeur de retour.

PHP 7.1 enrichit ces déclarations de types, en ajoutant plusieurs points qui manquaient dans la version précédente du langage.


Nullable Types

Le mécanisme de déclarations de types de PHP 7.0 souffrait d’une limitation : nous n’avions pas la possibilité de déclarer un paramètre comme pouvant être null, sans le rendre optionnel en même temps.

Par exemple, avec la déclaration de fonction suivante, le second appel était invalide, puisque null n’est pas un entier :

function fonc01(int $a) {
    var_dump($a);
}

fonc01(100); // int(100)
fonc01(null); // TypeError: Argument 1 passed to fonc01() must be of the type integer, null given

Pouvoir passer null, généralement pour indiquer « voici une valeur non spécifiée », est un besoin exprimé de nombreuses fois lors du développement de PHP 7.0 et après sa sortie.

Pour répondre à cette demande, PHP 7.1 introduit les types nullables : en positionnant un ? au début du nom d’un type, comme ?int, on indique que la valeur null est acceptée :

function fonc01(?int $a) {
    var_dump($a);
}

fonc01(100); // int(100)
fonc01(null); // NULL

Notez qu’utiliser un type nullable ne signifie pas que le paramètre est optionnel ni qu’il vaut null par défaut : une Error sera toujours levée si un paramètre attendu n’a pas été passé lors de l’appel de la fonction :

fonc01();
// Uncaught Error: Too few arguments to function fonc01(), 0 passed

Cette notion de types nullables s’étend bien sûr à la valeur de retour :

function fonc02(?int $a, ?int $b) : ?int {
    if ($a === null || $b === null) {
        return null;
    }
    return $a + $b;
}

var_dump( fonc02(10, 20) ); // int(30)
var_dump( fonc02(10, null) ); // NULL

‣ La RFC : Nullable Types


Void Return Type

En PHP, une fonction peut retourner une valeur explicitement, à l’aide du mot-clé return suivi de celle-ci, ou ne pas en retourner – soit en utilisant le mot-clé return sans valeur, soit en ne le positionnant tout simplement pas :

function returns_a_value()
{
    // Cette fonction retourne une valeur
    return 42;
}

function returns_nothing()
{
    // Cette fonction retourne null
    return;
}

function does_not_return()
{
    // Cette fonction retourne elle aussi null
}

Les déclarations de types ajoutées à PHP 7.0 permettent de spécifier de quel type doit être une valeur retournée, mais ne permettaient pas d’indiquer qu’une fonction ou méthode ne devait rien retourner.

Pour combler ce manque, PHP 7.1 introduit un nouveau pseudo-type, utilisable comme type de retour d’une fonction ou méthode : void. Une fonction qui utilise ce pseudo-type ne pourra pas retourner de valeur. Les deux écritures suivantes sont donc valides :

function returns_nothing() : void
{
    // Cette fonction retourne null
    return;
}

function does_not_return() : void
{
    // Cette fonction retourne elle aussi null
}

Les deux fonctions reproduites ci-dessus ne retournent pas de valeur et respectent donc la déclaration de type de retour void. Par contre, la fonction suivante n’est pas valide :

function returns_a_value() : void
{
    return 42;
}

Ici, nous essayons de retourner une valeur. C’est en désaccord avec la déclaration de type de retour void et nous prendrons donc une erreur fatale :

Fatal error: A void function must not return a value in .../demo-03.php on line 16

Notez que cette erreur se produit à la compilation du script et non à son exécution : PHP détecte, dès la compilation du code, que le mot-clé return a été utilisé avec une valeur.

‣ La RFC : Void Return Type


Iterable

Le dernier ajout au mécanisme de déclarations de types de PHP est un nouveau pseudo-type, nommé iterable. Il regroupe tout ce sur quoi on peut itérer : tableaux et données traversables, comme les itérateurs (et les générateurs, donc).

Ce mot-clé se positionne comme n’importe quelle autre déclaration de type :

function fonc01(iterable $data) {
    foreach ($data as $key => $val) {
        var_dump($val);
        // ...
    }
}

Cette déclaration garantit que nous pourrons boucler sur la donnée reçue, la parcourir. Par exemple, on peut itérer sur le contenu d’un tableau :

fonc01([10, 20, 30]);

Dans ce cas précis, nous disposions déjà de la déclaration de type array. Mais elle ne permettait pas, contrairement à iterable, de passer une instance d’objet qui implémente l’interface Iterator (qui étend elle-même Traversable) :

fonc01(new SplFixedArray(5));

Bien sûr, un générateur étant un itérateur, on peut en utiliser un ici aussi :

function mon_generateur() {
    yield 100;
    yield 200;
    yield 300;
}

fonc01(mon_generateur());

Par contre, et c’est fort logique, la déclaration de type iterable rejettera une donnée comme une chaine de caractères, sur laquelle il n’est pas possible de boucler :

fonc01("plop");
// TypeError: Argument 1 passed to fonc01() must be iterable, string given

Avec ce nouveau pseudo-type, PHP 7.1 répond à un besoin qui était fréquemment exprimé : pouvoir spécifier qu’un jeu de données itérable est attendu, en acceptant à la fois un tableau ou un objet implémentant Traversable.

‣ La RFC : Iterable