PHP 7.1 : quelques bc-breaks et conclusion

16 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 onzième – et dernier – article d’une série à propos de PHP 7.1.


Pour terminer cette série d’articles, commencée il y a deux semaines, je vais essayer de lister quelques points qui pourraient, en particulier si votre code est un peu legacy, ralentir la montée de version.


Quelques BC-breaks ?

Dans l’esprit de beaucoup d’entre nous, une version mineure ne devrait pas apporter de changement cassant la compatibilité : du code qui tourne sous PHP 7.0 devrait fonctionner à l’identique sous PHP 7.1 et seuls des ajouts de fonctionnalités et de corrections de bugs devraient caractériser cette nouvelle version.

On retrouve d’ailleurs ce principe dans la RFC Release Process :

Backward compatibility must be respected with the same major releases, for example from 5.2 to 5.6.


Il est toutefois généralement considéré comme acceptable pour une version mineure que de nouveaux avertissements de niveau E_DEPRECATED ou E_NOTICE soient ajoutés, pour identifier des fonctionnalités qui seront supprimées d’une version majeure suivante ou des points qui sont fort susceptibles d’être liés à des bugs dans notre code – j’en ai d’ailleurs parlé hier.


Cela dit, le comportement de quelques points – assez spécifiques et rarement utilisés – va changer avec PHP 7.1 ; je vais tâcher de les lister ci-dessous, la liste peut vous intéresser si votre code en dépendait.


Warn about invalid strings in arithmetic

PHP est un langage au typage souple, qui permet d’effectuer des calculs numériques sur des chaines : elles seront converties en entier selon les règles spécifiées dans la documentation. Par exemple, "10 pommes" peut être interprété comme le nombre 10.

Ainsi, l’écriture suivante revient à additionner deux numériques :

var_dump('10 apples' + '5 oranges');
// int(15)

Cette approche, qui était assez dans la philosophie de PHP il y a quelques années, est plus souvent considérée comme surprenante aujourd’hui.

PHP 7.1 supporte toujours cette écriture, mais accompagnera les conversions effectuées automatiquement d’un avertissement de niveau E_NOTICE, pour attirer l’attention du développeur :

// Notice: A non well formed numeric value encountered in .../01-warn-invalid-string-arithmetic.php on line 7
// Notice: A non well formed numeric value encountered in .../01-warn-invalid-string-arithmetic.php on line 7

Si vous comptiez réellement sur ces conversions, vous pouvez passer par des transtypages explicites comme (int)"10 apples".


L’autre cas revu pour PHP 7.1 est celui où une chaine est convertie en numérique alors qu’elle n’en contenait même pas :

var_dump(10 + "plop");
// int(10)

Au lieu d’une E_NOTICE, ce cas, plus surprenant et plus probablement causé par une erreur, lève désormais un avertissement de niveau E_WARNING :

// Warning: A non-numeric value encountered in .../01-warn-invalid-string-arithmetic.php on line 12

Notez qu’il s’agit d’un avertissement et pas d’une levée de TypeError, qui aurait un impact nettement plus important sur la compatibilité.

‣ La RFC : Warn about invalid strings in arithmetic


Fix inconsistent behavior of $this variable

Jusqu’à PHP 7.0 inclus, il était possible, dans certains cas, de redéfinir la variable $this, en passant par une variable variable :

class MaClasse
{
    public function method()
    {
        $var = 'this';
        $$var = "plop !";
        var_dump($this);
        $this->otherMethod();
    }

    public function otherMethod()
    {
        var_dump(__METHOD__);
    }
}

$obj = new MaClasse();
$obj->method();

La portion de code ci-dessus, exécutée avec PHP 7.0, donnait la sortie suivante :

string(6) "plop !"
string(21) "MaClasse::otherMethod"

Le premier var_dump() montre que la variable $this a été redéfinie et qu’elle vaut désormais "plop !", mais le second var_dump() montre que l’appel de méthode $this->otherMethod() a tout de même fonctionné !


Ce comportement étant fort surprenant, ce type de redéfinition de $this n’est plus supporté par PHP 7.1 et mènera à la levée d’une exception de type Error :

Fatal error: Uncaught Error: Cannot re-assign $this in .../02-fix-inconsistent-this.php:8

‣ La RFC : Fix inconsistent behavior of $this variable


Replace "Missing argument" warning with "Too few arguments" exception

Historiquement, PHP permet d’appeler une fonction en lui passant moins de paramètres qu’elle n’en attendait :

function my_function($a, $b)
{
    var_dump($a, $b);
}

my_function(10);

Jusqu’à PHP 7.0 inclus, cet appel levait un avertissement de niveau E_WARNING et les paramètres non spécifiés recevaient la valeur NULL :

Warning: Missing argument 2 for my_function(),
    called in .../03-missing-argument-exception.php on line 9
    and defined in .../03-missing-argument-exception.php on line 3
int(10)
NULL

Le second paramètre étant déclaré dans le prototype de la fonction, il est rare qu’on prenne la peine de vérifier qu’il a bien été reçu ; et cela peut mener à des comportements étranges.


Avec PHP 7.1, cet avertissement est transformé en une exception de type Error ; il devient donc nettement plus difficile de l’ignorer !

Fatal error: Uncaught Error: Too few arguments to function my_function(),
    1 passed in .../03-missing-argument-exception.php on line 8
    and exactly 2 expected in .../03-missing-argument-exception.php:3

‣ La RFC : Replace “Missing argument” warning with “Too few arguments” exception


Forbid dynamic calls to scope introspection functions

PHP fournit plusieurs fonctions qui manipulent le scope de leur appelant : extract(), compact(), parse_str(),… Elles accèdent toutes, en lecture ou en écriture, aux variables définies dans la fonction où elles sont appelées.

Par exemple :

function my_function()
{
    $vars = [
        'a' => 123,
        'plop' => 'Hello',
    ];

    $func = 'extract';
    call_user_func($func, $vars);

    var_dump($a, $plop);
}

my_function();

Avec PHP 7.0, cette portion de code mènera à la sortie suivante :

int(123)
string(5) "Hello"

Toutefois, en cas d’appels dynamiques via call_user_func() ou la syntaxe $func() (ou d’autres approches), la façon dont ces fonctions vont se comporter n’est pas toujours clairement définie – j’ai presque eu de la chance que ça fasse ce que je voulais, ici…


Avec PHP 7.1, les appels dynamiques aux fonctions de manipulation de scope ne sont plus permis et mèneront à un avertissement de niveau E_WARNING :

Warning: Cannot call extract() dynamically in .../04-dynamic-call-scope-introspection-functions.php on line 11
NULL
NULL

‣ La RFC : Forbid dynamic calls to scope introspection functions


PHP 7.1, en conclusion

Pour conclure cette série d’une bonne dizaine d’articles sur PHP 7.1, que dire ?


Cette seconde version mineure de la branche PHP 7, qui arrive environ un an après la sortie de 7.0, apporte son lot de nouveautés – y compris des fonctionnalités qui me semblent intéressantes et que j’utiliserai, comme :

  • Les nullable types
  • Le pseudo-type iterable
  • La méthode Closure::fromCallable()
  • Quelques petites améliorations de syntaxe et de cohérence

Il s’agit aussi, clairement, d’une évolution et pas d’une révolution : cela devrait grandement faciliter la mise à jour depuis PHP 7.0.


Toutefois, je suis triste de voir apparaitre plusieurs bc-breaks, qui ne me semblent pas tous réellement justifiés sur cette version mineure. Même s’ils ne devraient avoir que peu d’impact sur les applications sur lesquelles j’interviens, elles risquent d’effrayer certains développeurs moins téméraires et de ralentir l’adoption de cette nouvelle version.


Pour terminer, un rappel : pensez à consulter la page Supported Versions de temps en temps. Vous verrez ainsi que le support actif de PHP 7.0 se termine dans un an, pour laisser la place à PHP 7.1 ;-)