PHP 7.1 : créer une Closure à partir d'un appelable

13 septembre 2016 php, php-7.1

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


Historiquement, un callable a souvent été manipulé, en PHP, sous la forme d’une chaine de caractères. Par exemple, on utilise la fonction array_map() pour appeler le callable 'trim' sur tous les éléments d’un tableau.


Cette approche souffre d’un défaut majeur : la validité de l’appelable n’est déterminée que lors de son appel !

Par exemple, considérons la portion de code suivante :

function ma_fonction($param)
{
    printf("%s(%s)\n", __FUNCTION__, $param);
}

// Pas de problème détecté ici, malgré la faute de frappe
$nom = 'ma_fonctlon';

// Ici, de nombreuses lignes de code
// et encore plein d’autres

// C’est seulement ici qu’on a un problème :
// Warning: call_user_func() expects parameter 1 to be a valid callback, function 'ma_fonctlon' not found or invalid function name
call_user_func($nom, 42);

La faute de frappe dans le nom de la fonction de rappel n’est pas détectée à l’endroit où elle est commise… mais seulement plusieurs lignes plus loin, lorsque nous essayons de l’exécuter.

Sur un exemple réel, qui s’étalerait sur plusieurs dizaines ou centaines de fichiers, peut-être même par le biais de quelques fichiers de configuration, je vous laisse imaginer l’enfer en termes de débogage ! Peut-être même l’avez-vous déjà vécu ?


PHP 7.1 répond à cette problématique en introduisant une nouvelle méthode Closure::fromCallable(). Elle prend en paramètre un appelable, le valide, puis retourne une Closure le référençant. C’est cette Closure que vous manipulerez par la suite.

Nous pouvons donc réécrire l’exemple ci-dessus en tirant profit de cette fonctionnalité. Une exception instance de TypeError est levée si l’appelable spécifié n’est pas valide :

function ma_fonction() {
    var_dump(__FUNCTION__);
}

$closure = Closure::fromCallable('ma_fonction');
$closure();  // string(11) "ma_fonction"


$closure = Closure::fromCallable('plop');
// TypeError: Failed to create closure from callable: function 'plop' not found or invalid function name

Avantage : en cas d’erreur de frappe dans le nom de la fonction de rappel, elle est détectée immédiatement — sans avoir à attendre son exécution :

function ma_fonction($param)
{
    printf("%s(%s)\n", __FUNCTION__, $param);
}

// L’erreur est immédiatement détectée
// Fatal error: Uncaught TypeError: Failed to create closure from callable: function 'ma_fonctlon' not found or invalid function name
$callable = Closure::fromCallable('ma_fonctlon');

// Ici, de nombreuses lignes de code
// et encore plein d’autres

// Pas de problème ici : sauf erreur plus haut,
// $callable est forcément valide
call_user_func($callable, 42);


Le problème causé par l’absence de validation des callables lors de leur déclaration se posait également pour d’autres types d’appelables, comme ceux désignant une méthode d’un objet :

class MaClasse
{
    protected $data;

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

    public function maMethode()
    {
        printf("%s() -> %s\n", __METHOD__, $this->data);
    }
}


// Pas d’erreur levée par PHP ici, alors que c’est sur cette ligne
// qu’on a fait une typo !
$callable = [new MaClasse(42), 'maMehtode'];


// Pas non plus d’erreur ici
call_callable($callable);


function call_callable($callable)
{
    // C’est seulement ici qu’on a une erreur !
    // Fatal error: Uncaught Error: Call to undefined method MaClasse::maMehtode()
    $callable();
}

Heureusement, nous pouvons également, avec PHP 7.1, utiliser Closure::fromCallable() dans ce cas. Nous bénéficierons ici aussi de la validation de l’appelable lors de la création de la Closure, souvent bien avant son exécution :

class MaClasse
{
    protected $data;

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

    public function maMethode()
    {
        printf("%s() -> %s\n", __METHOD__, $this->data);
    }
}


// L’erreur est immédiatement détectée
// Fatal error: Uncaught TypeError: Failed to create closure from callable: class 'MaClasse' does not have a method 'maMehtode'
$callable = Closure::fromCallable([new MaClasse(42), 'maMehtode']);


// Pas de problème ici : $callable est forcément valide
call_callable($callable);


// Bonus : on peut type-declarer
function call_callable(callable $callable)
{
    $callable();
}

Bref, voici une nouvelle méthode qui va nous permettre de valider les appelables au plus tôt, facilitant ainsi la détection et la gestion d’erreur !


‣ La RFC : Closure from callable function


PHP 7.1: better syntax, a more consistent language

September 12, 2016 php, php-7.1, english

Cet article est aussi disponible en français.
This is the 6th post in a series about PHP 7.1.


A new version of PHP is always the right time to fix or enhance some syntax specific points, or to add some minor new features to the language. Here are some of the changes for PHP 7.1 that I noticed the most.

Allow specifying keys in list()

The first one is about the list keyword and extracting data from an array: list() now accepts the keys of the items we want to extract.

For example, to only get the items at 'glop' and 'who' keys from an array containing three entries, we can now use the following syntax:

$array = [
    'glop' => "Hello",
    'plop' => 123456,
    'who' => "World",
];

list (
    'glop' => $a,
    'who' => $b
) = $array;

var_dump($a, $b);
// string(5) "Hello"
// string(5) "World"

The approach is safer and easier to read than the one we were using before, only based on the position of each item in the array.

‣ The RFC: Allow specifying keys in list()


Square bracket syntax for array destructuring assignment

Since PHP 5.4, we can work with a short syntax to build arrays: [10, 20, 30]. Starting with PHP 7.1, we can also use this short syntax to deconstruct arrays, without using the list keyword:

$array = [10, 20, 30];
[$a, $b, $c] = $array;
var_dump($a, $b, $c);
// int(10)
// int(20)
// int(30)

This is, of course, compatible with what I wrote before:

['glop' => $glop, 'plop' => $plop] = [
    'plop' => 42,
    'glop' => "Hello",
];
var_dump($plop, $glop);
// int(42)
// string(5) "Hello"

Well, this is not a revolution, but it’s always nice to see more consistency in the language!

‣ The RFC: Square bracket syntax for array destructuring assignment


Generalize support of negative string offsets

Another small evolution, which makes PHP more consistent with itself and with other languages and will in some situations simplify things a bit: support for negative indexes, when working with strings, has been generalized.

This means we can now use a negative index to access a character inside a string, counting from its end:

$str = "Pascal";
var_dump($str[2]);  // string(1) "s"
var_dump($str[-2]); // string(1) "a"

As a reminder, with PHP 7.0, the last line would have returned an empty string.

This syntax also works to change a specific character, where PHP 7.0 would have raised a warning:

$str = "Pas.al";
$str[-3] = 'c';
var_dump($str);  // string(6) "Pascal"

Several string-related functions have been updated and now accept counting from the end of the string. For example:

var_dump( strpos("Pascal", "c", -5) ); // int(3)

Here too, PHP 7.0 would have raised a warning, BTW.

‣ The RFC, which lists all updated functions: Generalize support of negative string offsets


Class Constant Visibility

I often need to define an internal constant in a class — and I often see this need while doing code reviews.

For this, with PHP 7.0, we have two options:

  • Defining a class constant with const: we have the advantage of using a constant, we cannot change its value, but it is necessarily public.
  • Or setting up a private static variable: we have the advantage of a private piece of data, which will not be available outside of the class (it’s not part of its API), but it’s a variable and not a constant.

PHP 7.1 answers this and we can now specify visibility on class constants:

class MyClass {
    public const MY_PUBLIC = 42;
    private const MY_PRIVATE = 1234;

    public function test() {
        var_dump( self::MY_PRIVATE );
    }
}

Of course, the three public, protected and private keywords keep the meaning they already had for attributes and methods:

var_dump( MyClass::MY_PUBLIC ); // int(42)
(new MyClass())->test(); // int(1234)

// Fatal error: Uncaught Error: Cannot access private const MyClass::MY_PRIVATE
var_dump( MyClass::MY_PRIVATE );

Here’s an evolution that could seem minor at first sight, but I already know I will use it ;-)

‣ The RFC: Support Class Constant Visibility


PHP 7.1 : des améliorations de syntaxe, un langage plus cohérent

12 septembre 2016 php, php-7.1

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


Une nouvelle version de PHP, c’est aussi l’occasion d’améliorer certains points de syntaxe, ou d’apporter quelques évolutions mineures au langage. Voici quelques-uns des points qui ont le plus attiré mon regard, pour PHP 7.1.

Allow specifying keys in list()

La première de ces évolutions porte sur le mot-clé list et sur l’extraction de données depuis un tableau : list() accepte désormais les clés des éléments à extraire.

Par exemple, pour n’extraire que les éléments de clés 'glop' et 'who' d’un tableau de trois éléments, nous pouvons désormais utiliser l’écriture suivante :

$array = [
    'glop' => "Hello",
    'plop' => 123456,
    'who' => "World",
];

list (
    'glop' => $a,
    'who' => $b
) = $array;

var_dump($a, $b);
// string(5) "Hello"
// string(5) "World"

Cette approche est plus sure et plus lisible que celle où nous nous basions uniquement sur la position des différents éléments du tableau.

‣ La RFC : Allow specifying keys in list()


Square bracket syntax for array destructuring assignment

Nous pouvons depuis PHP 5.4 utiliser une syntaxe courte pour construire des tableaux : [10, 20, 30]. À partir de PHP 7.1, nous allons pouvoir utiliser cette même syntaxe courte pour la déconstruction de tableaux, sans avoir à employer le mot-clé list :

$array = [10, 20, 30];
[$a, $b, $c] = $array;
var_dump($a, $b, $c);
// int(10)
// int(20)
// int(30)

Il est à noter que cette syntaxe est bien sûr compatible avec le point vu juste au-dessus :

['glop' => $glop, 'plop' => $plop] = [
    'plop' => 42,
    'glop' => "Hello",
];
var_dump($plop, $glop);
// int(42)
// string(5) "Hello"

Pas une révolution, mais plus de cohérence dans le langage !

‣ La RFC : Square bracket syntax for array destructuring assignment


Generalize support of negative string offsets

Encore une petite évolution, qui rend PHP plus cohérent avec lui-même et avec d’autres langages, et qui va dans certains cas bien nous simplifier les choses : le support pour les indexes négatifs lors de manipulations de chaines de caractères a été généralisé.

Cela signifie qu’on peut désormais utiliser un index négatif pour accéder à un caractère d’une chaine, en partant de la fin de celle-ci :

$str = "Pascal";
var_dump($str[2]);  // string(1) "s"
var_dump($str[-2]); // string(1) "a"

Pour rappel, avec PHP 7.0, la seconde écriture aurait retourné une chaine vide.

Cette syntaxe fonctionne également en affectation pour modifier un caractère précis, là où PHP 7.0 aurait levé un avertissement :

$str = "Pas.al";
$str[-3] = 'c';
var_dump($str);  // string(6) "Pascal"

Plusieurs fonctions de manipulation de chaines ont également été enrichies, de manière à accepter de compter à partir de la fin de la chaine. Par exemple :

var_dump( strpos("Pascal", "c", -5) ); // int(3)

Là aussi, PHP 7.0 levait un avertissement.

‣ La RFC, qui liste toutes les fonctions modifiées : Generalize support of negative string offsets


Class Constant Visibility

Il m’arrive parfois — et je vois fréquemment le cas en revues de code — d’avoir besoin de définir une constante interne à une classe.

Pour cela, avec PHP 7.0, deux possibilités :

  • Définir une constante de classe avec const : on a l’avantage d’avoir une constante qui ne peut changer de valeur, mais elle est nécessairement publique.
  • Ou définir une variable statique privée avec private static : on a l’avantage d’avoir une donnée privée qui ne sera pas utilisée depuis l’extérieur de la classe (elle ne fait pas partie de son API), mais il s’agit d’une variable et pas d’une constante.

PHP 7.1 répond à cette problématique, en permettant de spécifier une visibilité sur les constantes de classes :

class MaClasse {
    public const MA_PUBLIQUE = 42;
    private const MA_PRIVEE = 1234;

    public function test() {
        var_dump( self::MA_PRIVEE );
    }
}

Fort logiquement, les trois mots-clés public, protected et private reprennent le sens qu’ils avaient déjà pour les attributs et méthodes :

var_dump( MaClasse::MA_PUBLIQUE ); // int(42)
(new MaClasse())->test(); // int(1234)

// Fatal error: Uncaught Error: Cannot access private const MaClasse::MA_PRIVEE
var_dump( MaClasse::MA_PRIVEE );

Voici une évolution qui pourrait sembler mineure au premier abord, mais je sais déjà que je vais l’utiliser ;-)

‣ La RFC : Support Class Constant Visibility


PHP 7.0: new features from the not-so-distant past

September 9, 2016 php, php-7.0, php-7.1, english

Cet article est aussi disponible en français.
This is the 5th post in a series about PHP 7.1.


I will publish other posts about PHP 7.1 next week but, to conclude this one, here’s a quick flashback listing some nice features PHP 7.0 brought us last year.
After all, these are available in PHP 7.1 too, and, as many are still using PHP 5, they constitute additional reasons for switching to PHP 7.x ;-)

A couple of new operators

PHP 7.0 introduced two new operators, once again to simplify and shorten some syntax.

Null Coalesce Operator

The first one, ??, aims to help this kind of construct disappear:

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

We can now write that line this way:

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

This gives the same result as the previous one and will not cause a notice if the variable used as first operand is not defined. The only difference, often an interesting one, is this operand is now evaluated only once.

As isset() returns false when data are null, this operator behaves the same way and we can use it on more complex expressions, like method calls. I’d add it’s lazy-evaluated:

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

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

‣ The RFC: Null Coalesce Operator


Spaceship Operator

The other operator added with PHP 7.0 is a comparison operator, often called “spaceship” because of how it looks:

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

It returns -1 if its left operand is smaller than the right one, 1 if it’s bigger and 0 if they are equal. Because of this, it is quite useful when we are writing a callback function for sorts, for example with the usort() function.

It also works when we need to compare arrays:

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

‣ The RFC: Combined Comparison (Spaceship) Operator


Unicode Codepoint Escape Syntax

If you like using Unicode characters in your strings, this new feature is for you! With PHP 7.0, you can use the \u{xxxx} syntax to specify a character using its code:

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

Or, for an example with some other nice characters:

$ 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!!!

(Did you know there were so many ‘cat’ symbol in Unicode?)

‣ The RFC: Unicode Codepoint Escape Syntax


Scalar type-declarations

This is probably the new feature of PHP 7: we can now specify type-declarations (they were previously called ‘type-hints’) with scalar types, and not only with objects / arrays / callables.

Scalar type-declarations for parameters

The different scalar types of PHP can be used for parameters: int, float, string and bool.

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

PHP is still weakly typed and conversions will automatically be applied when possible. When they are not possible, a TypeError exception is thrown:

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

In this case, we’d get:

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

‣ The RFC: Scalar Type Declarations


Return type declaration

We can also, with PHP 7, specify a return-type for a function or method, be it scalar or one of the types already accepted by PHP 5 for parameters:

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

‣ The RFC: Return Type Declarations


Strict typing

If the weak typing approach is not for you or you are sure of the data-types your code handles, you can enable a strict typing mode for a file:

<?php
declare(strict_types=1);

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

add(10, '20');

Here, as '20' is not an integer and we are using strict typing mode, a TypeError will be thrown:

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

Basically, when it comes to the distinction between weak and strict typing:

  • For parameters, it’s up to the caller’s choice: she is the one knowing if she’s working with weak or strict types.
  • For the return value, it’s the callee’s choice: she knowns if the function she wrote uses weak or strict types.

In any case, inside the function, data are of the specified types: the weak or strict mode only determines whether conversions are allowed.


Group use declarations

Now that we are using namespaces everywhere, we often see files beginning with an incredible number of lines looking like this:

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 gives us the option to group those use directives:

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

We can also use this new syntax for constants and functions:

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

‣ The RFC: Group Use Declarations


Enhancements on error handling

PHP 7.0 comes with two evolutions around error handling.

Internal fatal errors → exceptions

With PHP 7.0, some internal fatal errors are transformed to instances of Error, which can be caught. For exemple:

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

This portion of code would have caused a Fatal Error with PHP 5, ending the execution of the script. It now leads to the following output, showing we entered the catch block:

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

This same principle is used for other errors, like when we are using invalid PHP code in another analysis context:

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

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

Let me insist: this change, which will help those running PHP programs for a long time (think: daemons), is for now only applied to some fatal errors — and only when they are raised from PHP’s engine.

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


Errors / exceptions hierarchy

Along with this change, the hierarchy of exceptions classes has been re-thought, introducing a new Throwable interface, shared by all exceptions types.

Usual exceptions still inherit from Exception, while errors extend Error — which is a sister-class of Exception:

interface Throwable
    Exception implements Throwable
        // All usual exceptions
    Error implements Throwable
        AssertionError extends Error
        ParseError extends Error
        TypeError extends Error

This way, the code catching Exception does not magically also start catching Errors with PHP 7.0. This ensures the behavior of your code will not change when upgrading from PHP 5 to PHP 7.

‣ The RFC: Throwable Interface


Anonymous classes

Another new feature of PHP 7.0 is anonymous classes. This extends the idea of anonymous functions, existing since PHP 5.3, adding object-related features: inheritance, implementing interfaces…

For example, let’s instantiate an anonymous object and call one of its methods:

$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();

We’ll get the following output. We can especially notice the "Monde" we gave as a parameter to the constructor, and the fact state has been shared between both methods, using the $qui attribute.

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

A typical use case would be a callback dealing with two options, like a success() and a failure() method, only using one object implementing the corresponding interface — and not using two separate functions anymore.

‣ The RFC: Anonymous Classes


Evolutions regarding generators

Generators are still a young feature of PHP, as they’ve been added with PHP 5.5 and the community and developers only begin thinking about more advanced way to use them.

‣ If you’d like to know more about generators, here’s a series of posts on the matter, written by several members of the community a few months ago: Generators’ week: here we go!


PHP 7.0 brought two new features to generators.

Generators and return

It previously wasn’t possible to return a value from a generator. PHP 7.0 changed that:

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

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

var_dump( $generator->getReturn() );

This would get us the following output, with values dumped from the loop, and then the return value:

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

How can this help? We can for example think of a function that would generate intermediary values, finally returning some kind of total calculated from these.

‣ The RFC: Generator Return Expressions


Generator Delegation

The yield keyword has been reworked and we can now use yield from to generate values from a traversable (with yield alone, we would have generated only one thing: the traversable itself):

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

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

Here, we use yield from to generate the three values contained in the array, and get the following output:

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

As a generator is traversable, we can also use yield from to generate values from other ‘sub-generators’ — hence the idea of ‘delegation’:

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);
}

Here, we’d get:

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

This is especially interesting when we need to aggregate data coming from several sources — for instance, to load data from a database (using a first generator) and a CSV file (using another generator).

‣ The RFC: Generator Delegation


What else?

Of course, I have not listed here all new features of PHP 7.0 and only wrote about those I noticed the most. Your turn to play with them now ;-)

And see you next week for more posts about PHP 7.1!


PHP 7.0 : quelques rappels du passé récent

9 septembre 2016 php, 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 !