PHP 7.0 : quelques rappels du passé récent

9 septembre 2016php, php-7.0, php-7.1

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


D’autres articles à propos de PHP 7.1 vont arriver la semaine prochaine, mais, pour terminer celle-ci, voici un bref rappel de quelques nouveautés sympa de PHP 7.0.
Après tout, vous retrouverez ces améliorations dans PHP 7.1, et vu que nombre d’entre nous n’ont pas encore quitté PHP 5, cela vous fera des arguments supplémentaires pour passer à PHP 7.x ;-)

Quelques nouveaux opérateurs

Pour commencer, PHP 7.0 a introduit deux nouveaux opérateurs, encore une fois dans l’optique de simplifier certaines écritures.

Null Coalesce Operator

Le premier, ??, vise à faire disparaitre ce type de construction :

$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

Elle peut désormais s’écrire comme ceci :

$username = $_GET['user'] ?? 'nobody';

Cette écriture donne le même résultat que la précédente et ne provoquera pas de notice si la variable utilisée en premier opérande n’est pas définie. La seule différence, souvent intéressante, est que cet opérande n’est plus évalué qu’une seule fois.

isset() renvoyant false quand une donnée est null, cet opérateur se comporte de la même manière et peut donc être utilisé sur des cas plus complexes que des variables, comme des appels de méthodes. J’ajouterais qu’il est évalué paresseusement :

$model = Model::get($id) ?? $default_model;

// Avec court-circuit
function foo() {
    echo "executed!", PHP_EOL;
}
var_dump(true ?? foo());

‣ La RFC : Null Coalesce Operator


Spaceship Operator

Le second opérateur ajouté avec PHP 7.0 est un opérateur de comparaison, souvent appelé opérateur « spaceship » à cause de sa forme :

function order_func($a, $b) {
    return $a <=> $b;
}

Cet opérateur retourne -1 si son opérande de gauche est inférieur à celui de droite, 1 s’il est supérieur et 0 si les deux sont égaux. Il est donc fort utile dans le cas d’écriture de fonctions de rappels pour des tris, par exemple pour passer à usort().

Il fonctionne également lorsqu’on souhaite comparer des tableaux :

function order_func($a, $b) {
    return [$a->x, $a->y, $a->foo]
        <=> [$b->x, $b->y, $b->foo];
}

‣ La RFC : Combined Comparison (Spaceship) Operator


Unicode Codepoint Escape Syntax

Si vous aimez placer des caractères Unicode dans vos chaines de caractères, cette nouveauté est pour vous : PHP 7.0 permet d’utiliser la syntaxe \u{xxxx} pour spécifier un caractère à partir de son code :

$ php -r 'echo "I \u{2665} PHP!\n";'
I ♥ PHP!

Ou, pour un exemple avec d’autres caractères sympathiques :

$ php -r 'echo "\u{1F408} \u{1F431} \u{1F638} \u{1F639} \u{1F63A} \u{1F63B} \u{1F63C} \u{1F63D} \u{1F63E} \u{1F63F} \u{1F640} - so cute!!!\n";'
🐈 🐱 😸 😹 😺 😻 😼 😽 😾 😿 🙀 - so cute!!!

(Saviez-vous qu’il existait autant de caractères « chat » en Unicode ?)

‣ La RFC : Unicode Codepoint Escape Syntax


Déclarations de types scalaires

C’est sans doute la nouveauté de PHP 7 : il est désormais possible de spécifier des déclarations de types (ce qui s’appelait précédemment « type-hints »), pour des scalaires ; et plus uniquement pour les objets / tableaux / callables.

Déclarations de types scalaires pour les paramètres

Les différents types scalaires de PHP peuvent être utilisés pour les paramètres : int, float, string et bool.

function add(int $a, int $b) {
    return $a + $b;
}

PHP conserve sa logique de langage au typage souple et des conversions seront automatiquement appliquée lorsqu’elles sont possibles. Lorsqu’elles ne sont pas possibles, une exception de type TypeError sera levée :

try {
    var_dump( add(10, 'abc') );
}
catch (TypeError $e) {
    var_dump( $e->getMessage() );
}

Ici, nous obtiendrions ceci :

string(148) "Argument 2 passed to add()
    must be of the type integer, string given,
    called in .../test-01.php on line 12"

‣ La RFC : Scalar Type Declarations


Déclaration du type de retour

PHP 7 permet également de déclarer le type de retour d’une fonction ou d’une méthode, qu’il soit scalaire ou d’un des types déjà reconnus sous PHP 5 pour les paramètres :

function add(int $a, int $b) : int {
    return $a + $b;
}

‣ La RFC : Return Type Declarations


Typage strict

Si l’approche souple du typage ne vous satisfait pas ou que vous êtes certains des types de données manipulés par votre code, vous pouvez activer un mode de typage strict, pour le fichier courant :

<?php
declare(strict_types=1);

function add(int $a, int $b) {
    return $a + $b;
}

add(10, '20');

Ici, puisque '20' n’est pas un entier et que nous sommes en mode de typage strict, une TypeError sera levée :

Fatal error: Uncaught TypeError:
    Argument 2 passed to add()
    must be of the type integer, string given

Pour la distinction entre typage souple et typage strict :

  • Pour les paramètres, c’est au choix de l’appelant : c’est lui qui sait s’il manipule des données en types souples ou stricts.
  • Pour la valeur de retour, c’est au choix de l’appelé, qui sait si la fonction qu’il a écrite utilise des types souples ou stricts.

Dans tous les cas, à l’intérieur de la fonction, les données sont des types indiqués : le paramétrage souple ou strict détermine uniquement si les conversions sont permises.


Group use declarations

Avec l’utilisation quasi systématique des espaces de noms, il n’est pas rare de voir des fichiers commençant par une incroyable quantité de lignes de ce type :

use Mon\Espace\De\Nom\ClasseAa;
use Mon\Espace\De\Nom\ClasseBb;
use Mon\Espace\De\Nom\ClasseCc;
use Mon\Espace\De\Nom\Enfant\AutreClasse as ClassDd;

PHP 7 permet de regrouper ces directives use :

use Mon\Espace\De\Nom\ {
    ClasseAa, ClasseBb, ClasseCc,
    Enfant\AutreClasse as ClassDd
};

Cette nouvelle syntaxe peut également être utilisée pour les constantes et fonctions :

use Mon\Nom\ {
    function fonc01,
    const CONST01,
    Class01
};

‣ La RFC : Group Use Declarations


Des améliorations sur la gestion d’erreurs

PHP 7.0 apporte deux évolutions autour de la gestion d’erreurs.

Erreurs fatales internes → exceptions

Avec PHP 7.0, une partie des erreurs Fatales et internes à / levées par PHP sont transformées en Error, qui peuvent être catchées. Par exemple :

$obj = null;
try {
    // Ooops !
    $obj->methode();
}
catch (Error $e) {
    var_dump($e>getMessage());
}

La portion de code ci-dessus, qui aurait entrainé en PHP 5 une Fatal Error mettant fin à l’exécution du script, mène désormais à la sortie suivante, où nous sommes passés dans le bloc catch :

string(43) "Call to a member function methode() on null"

Ce même principe se retrouve pour d’autres erreurs, comme en cas de code PHP invalide inclus depuis un autre contexte d’analyse :

$php = <<<'PHP'
$a = 10        // -- Parse error ! -- //
printf("\$a = %d\n", $a);
PHP;

try {
    eval($php);
}
catch (ParseError $e) {
    var_dump($e->getMessage());
}

J’insiste : cette modification, qui va fortement aider ceux qui font tourner des programmes PHP pendant longtemps, ne s’applique pour l’instant qu’à certaines erreurs fatales, et uniquement levées depuis le moteur de PHP.

‣ La RFC : Exceptions in the engine (for PHP 7)


Hiérarchie d’erreurs / exceptions

Pour accompagner ce changement, la hiérarchie des classes d’exceptions a été revue, avec l’introduction d’une interface Throwable commune à tous les types d’exceptions.

Les exceptions usuelles continuent d’hériter de Exception, alors que les erreurs héritent quant à elles de Error, qui est une classe-sœur de Exception :

interface Throwable
    Exception implements Throwable
        // Toutes les exceptions usuelles
    Error implements Throwable
        AssertionError extends Error
        ParseError extends Error
        TypeError extends Error

De la sorte, le code qui attrapait une Exception ne se retrouve pas subitement, à partir de PHP 7.0, à également attraper les Error, assurant que le comportement de votre code ne changera pas en passant de PHP 5 à PHP 7.

‣ La RFC : Throwable Interface


Classes anonymes

Une autre nouveauté de PHP 7.0 est la possibilité de travailler avec des classes anonymes. Cela vient étendre la notion de fonctions anonymes qui existait depuis PHP 5.3, en ajoutant tout ce qui est objet : héritage, implémentation d’interfaces…

Par exemple, instancions un objet anonyme et appelons une de ses méthodes :

$obj = new class ("Monde") {
    protected $qui;

    public function __construct($qui) {
        $this->qui = $qui;
    }

    public function hello() {
        printf("Hello, %s !\n", $this->qui);
    }
};

var_dump($obj);
$obj->hello();

La sortie obtenue ici sera la suivante ; on remarque notamment l’utilisation de "Monde" comme paramètre passé au constructeur et la conservation d’état entre les deux méthodes, via l’attribut $qui.

object(class@anonymous)#1 (1) {
  ["qui":protected]=>
  string(5) "Monde"
}
Hello, Monde !

Un cas d’usage assez classique pourrait être la mise en place d’un callback gérant deux cas, comme une méthode success() et une méthode failure(), en passant un seul objet implémentant l’interface correspondante — et plus deux fonctions distinctes.

‣ La RFC : Anonymous Classes


Des évolutions sur les générateurs

Les générateurs sont une évolution encore assez jeune de PHP, puisqu’ils ont été ajoutés pour PHP 5.5 et que la communauté et les développeurs commencent seulement à se pencher sur leur utilisation un peu plus avancée.

‣ Si vous voulez en savoir plus à propos des générateurs, voici une série d’articles sur le sujet, écrits par plusieurs membres de la communauté il y a quelques mois : semaine des générateurs : le lancement !


Avec PHP 7.0, les générateurs ont gagné deux améliorations.

Generators et return

Il n’était précédemment pas possible de retourner une valeur depuis un générateur. PHP 7.0 le permet désormais :

function plop() {
    yield 100;
    yield 200;
    return 42;
}

foreach (($generator = plop()) as $val) {
    var_dump($val);
}

var_dump( $generator->getReturn() );

Cette portion de code donnerait la sortie suivante, avec les valeurs obtenues lors de l’itération, puis la valeur de retour :

int(100)
int(200)
int(42)

Quel intérêt ? On peut par exemple imaginer une fonction qui génèrerait des valeurs intermédiaires, pour finalement retourner un total calculé à partir de celles-ci.

‣ La RFC : Generator Return Expressions


Generator Delegation

Avec PHP 7.0, le mot-clef yield a été enrichi et il est désormais possible d’utiliser yield from pour générer des valeurs depuis un traversable (avec yield, on aurait généré une seule chose : le traversable en lui-même) :

function test() {
    yield from [10, 20, 30];
}

foreach (test() as $val) {
    var_dump($val);
}

Ici, yield from nous permet de générer successivement les trois valeurs du tableau, menant à la sortie suivante :

int(10)
int(20)
int(30)

Un générateur étant traversable, on peut également utiliser yield from pour générer des valeurs depuis d’autres « sous-générateurs », d’où le terme de délégation :

function sub_generator_1() {
    yield 10;
    yield 20;
}
function sub_generator_2() {
    yield from [ 'aa', 'bb', ];
}
function delegating_generator() {
    yield from sub_generator_1();
    yield from sub_generator_2();
}

foreach (delegating_generator() as $val) {
    var_dump($val);
}

Ici, la sortie sera la suivante :

int(10)
int(20)
string(2) "aa"
string(2) "bb"

Ceci est particulièrement intéressant lorsqu’on souhaite agréger des données provenant de plusieurs sources — par exemple, pour charger des informations depuis une base de données (via un premier générateur) et via un fichier CSV (via un second générateur).

‣ La RFC : Generator Delegation


Et encore ?

Bien sûr, je n’ai pas listé ici toutes les nouveautés de PHP 7.0 et me suis limité à celles qui avaient le plus attiré mon attention. Je vous laisse jouer avec, ce sera plus enrichissant ;-)

Et à la semaine prochaine pour de nouveaux articles à propos de PHP 7.1 !


Vous avez apprécié cet article ? Faites-le savoir !

Ce blog a récemment été migré vers un générateur de sites statiques et je n'ai pas encore eu le temps de remettre un mécanisme de commentaires en place.

Avec un peu de chance, je parviendrai à m'en occuper d'ici quelques semaines ;-)