En route vers PHP 5.5 : Quelques backward-compatible breaks

6 novembre 2012php, php-5.5
 Cet article a été rédigé il y a plusieurs années et peut ne plus être tout à fait à jour…

De manière générale, PHP a tendance à garder un maximum de compatibilité entre versions, de manière à ce qu’un code écrit pour PHP 5.x continue à fonctionner avec PHP 5.(x+1)1.

Cela dit, chaque nouvelle version vient avec quelques changements qui peuvent avoir des impacts sur votre code ; dans l’ensemble, ces changements peuvent être de l’un des types suivants (qui ne sont pas mutuellement exclusifs) :

  • Nouvelle fonctionnalité : nouvelle classe ou fonction (ou même nouveau mot-clef) fournie par le langage, que vous ne pouvez pas redéfinir (plus définir) dans votre code,
  • Evolution qui irait à l’encontre de la manière dont votre code est écrite (par exemple : passage de paramètres par référence lors de l’appel d’une fonction),
  • Correctif pensé dans un but de sécurité ; qui peut avoir un impact si votre code n’était pas écrit correctement ou avait un comportement / répondait à un besoin inhabituel (par exemple, suppression de register_globals, ou directive max_input_vars)
  • Arrêt de support d’une bibliothèque / extension ou d’un système d’exploitation (par exemple, arrêt du support des ereg au profit des preg)

Autant que faire se peut, entre deux versions PHP 5.x, les changements susceptibles de casser votre code ou vos applications sont minimisés (et plus deux versions sont “proches”, moins il y a de risque d’en avoir – typiquement, le passage de PHP 5.2 à 5.3 est réputé plus difficile que le passage de PHP 5.3 à 5.4, qui ont été moins espacées dans le temps)

… Mais, bien entendu, à vous de lire les release notes et guides de mise à jour ; ainsi que de tester soigneusement en environnements de développement et de tests, avant de basculer un changement de version sur un serveur de production : c’est aussi / exactement à cela que servent les versions bêta et RC qui sont systématiquement publiées avant une mise à jour importante2.

Arrêt du support de Windows XP et 2003

La prochaine version de PHP (probablement PHP 5.5) devrait mettre fin au support de Windows XP et de Windows 2003.

Avant de râler, si je puis me permettre, voici quelques points pour remettre les choses en perspective :

  • Windows XP est sorti en octobre 2001 ; la dernière version stable étant le Service Pack 3 publié en 2008,
  • Windows Server 2003 est sorti en avril 2003 ; son successeur ayant été publié en 2008.
  • Supporter des systèmes d’exploitation qui datent de dix ans ou plus demande du travail… et du temps… que les développeurs / mainteneurs de PHP peuvent vouloir passer à développer de nouvelles fonctionnalités, plutôt que supporter des OS obsolètes.
  • Oh, au passage, pour ceux d’entre nous qui font du développement front end et qui n’en peuvent plus de devoir supporter IE6 ou qui ont la chance de l’avoir laissé tomber : Windows XP, si on compte, c’est quand même un peu l’IE6 du monde des OS ;-)

Déprécié : modificateur /e pour preg_replace()

Jusqu’à présent, la fonction preg_replace() acceptait le modificateur d’expression rationnelle /e (PREG_REPLACE_EVAL), qui permet d’inclure du code dans la chaine de caractères de remplacement.

preg_replace() avec exécution de code

Par exemple, la portion de code suivante était valide :

$subject = "une suite de mots";
$pattern = "/([^\s]+)/ei";
$replacement = 'ucfirst(\'$1\')';

$result = preg_replace($pattern, $replacement, $subject);
var_dump($result);

En exécutant cette portion de code avec PHP <= 5.4, nous aurions obtenu :

string(17) "Une Suite De Mots"

Maintenant, si vous exécutez cette même portion de code avec la branche master de PHP (et donc, probablement, PHP 5.5), vous obtiendrez un avertissement indiquant que ce modificateur /e est déprécié :

Deprecated:  preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead 
    in /.../php-5.5/tests/preg_replace/preg_replace-1.php on line 8

string(17) "Une Suite De Mots"

Autrement dit, ce modificateur /e devrait disparaitre dans une prochaine version du langage, et vous ne devriez donc plus l’utiliser (niveau sécurité, ce n’est pas une solution vraiment optimale : il est relativement facile d’oublier un cas qui permettrait une injection de code).

preg_replace_callback()

A la place de la portion de code reproduite plus haut, vous pourrez vouloir utiliser quelque chose de ce type, en utilisant la fonction preg_replace_callback() :

$subject = "une suite de mots";
$pattern = "/([^\s]+)/i";
$callback = function ($item) {
    return ucfirst($item[1]);
};

$result = preg_replace_callback($pattern, $callback, $subject);
var_dump($result);

La sortie obtenue sera la même – sans l’avertissement à propos de /e, bien sûr :

string(17) "Une Suite De Mots"

(oui, pour un cas aussi simple, il n’était pas nécessaire de passer par une fonction anonyme, et il aurait été possible de juste passer en paramètre le nom de la fonction ucfirst)

Nouveaux mots-clefs, nouvelles classes et fonctions

Cette future nouvelle version de PHP (probablement PHP 5.5, encore une fois) apporte un nouveau mot-clef, et plusieurs nouvelles classes et fonctions.

Nouveau mot-clef yield

La version actuelle de la branche master de PHP (et donc, probablement, PHP 5.5) implémente les generators, à l’aide du mot-clef yield.

Celui-ci ne peut donc plus être utilisé comme identifieur dans votre code ; par exemple, il n’est plus possible d’utiliser une portion de code comme celle que je reproduis ci-dessous, déclarant une classe nommée yield :

<?php

class yield {
    public $plop = "Hello";
}
$obj = new yield();
var_dump($obj);

Exécuter cette portion de code entraine désormais l’erreur suivante :

$ $HOME/bin/php-5.5/bin/php ./bc-breaks/keyword-yield.php 
PHP Parse error:  syntax error, unexpected 'yield' (T_YIELD), expecting identifier (T_STRING) 
    in /.../php-5.5/tests/bc-breaks/keyword-yield.php on line 3

Alors que, à titre de vérification, elle s’exécutait sans aucun problème en PHP 5.3 / 5.4 :

$ php ./bc-breaks/keyword-yield.php 
class yield#1 (1) {
  public $plop =>
  string(5) "Hello"
}

De la même manière et pour la même raison, on ne peut plus déclarer une fonction nommée yield() ; autrement dit, la portion de code suivante :

<?php

function yield($plop) {
    echo "yield >> $plop\n";
}
yield("test");

Mène à présent à l’erreur suivante :

$ $HOME/bin/php-5.5/bin/php ./bc-breaks/keyword-yield-2.php 
PHP Parse error:  syntax error, unexpected 'yield' (T_YIELD), expecting '(' 
    in /.../php-5.5/tests/bc-breaks/keyword-yield-2.php on line 3

Alors qu’elle était tout à fait valide en PHP 5.3 :

$ php ./bc-breaks/keyword-yield-2.php 
yield >> test

Fonctions supprimées et ajoutées

La version courante de la branche master de PHP (et donc, probablement, PHP 5.5) introduit un nombre important de nouvelles fonctions :

  • La fonction de conversion en booléen boolval(), dont j’ai parlé ici
  • Quelques fonctions ont été ajoutées à l’extension curl : curl_escape(), curl_reset(), curl_share_close(), curl_share_init(), curl_share_setopt(), curl_unescape()
  • Quelques nouvelles fonctions de manipulation de dates : datefmt_format_object(), datefmt_get_calendar_object(), datefmt_get_timezone(), datefmt_set_timezone()
  • hash_pbkdf2(), dont je parlais ici
  • Toute une série de fonctions intlcal_*(), intlgregcal_*(), intltz_*() correspondant aux évolutions de l’extension intl, dont je parlais ici.
  • json_last_error_msg() ; cf commit : retourne le message d’erreur correspondant au dernier appel à json_encode() ou à json_decode() ; un peu sur le modèle de json_last_error() qui existe depuis PHP 5.3 mais ne retourne qu’un code.
  • openssl_pbkdf2()
  • Les fonctions password_get_info(), password_hash(), password_needs_rehash(), password_verify() correspondant à l’API simple pour le hachage de mots de passe, dont je parlais ici.

Cela signifie que vous ne pouvez plus définir, dans votre code PHP, de fonction portant un de ces noms.

A titre d’illustration, la portion de code suivante fonctionne en PHP 5.4 :

<?php
function boolval($valeur) {
    return (bool)$valeur;
}

var_dump( boolval('plop') );

Mais elle entraine une Fatal Error en PHP 5.5 :

$ $HOME/bin/php-5.5/bin/php ./bc-breaks/redefinition-fonction.php 
PHP Fatal error:  Cannot redeclare boolval() 
    in /.../php-5.5/tests/bc-breaks/redefinition-fonction.php on line 4

En parallèle, les **fonctions supprimées** sont, jusqu'à présent, au nombre de trois :

Ces fonctions qui permettaient d’obtenir les identifiants des logos de PHP n’étaient pas vraiment bien utiles… et exposaient des informations de PHP.

Leur suppression ne devrait probablement pas vraiment nous impacter (à vrai dire, je ne les ai jamais vu être utilisées dans un code “réel” d’application).

Nouvelles classes

Au niveau des ajouts de classes, moins de choses à signaler, puisque la liste des classes ajoutées, aujourd’hui, sur la branche master de PHP (et donc, probablement, PHP 5.5) s’arrête à celle que je reproduis ci-dessous :

  • Generator : la classe correspondant aux Generators
  • Et une série de nouvelles classes pour l’extension d’internationalisation : IntlBreakIterator, IntlCalendar, IntlCodePointBreakIterator, IntlException, IntlGregorianCalendar, IntlIterator, IntlPartsIterator, IntlRuleBasedBreakIterator, IntlTimeZone ; j’ai parlé de plusieurs d’entre elles ici.

Ici encore, sauf à jouer avec les espaces de noms, veillez à ne pas redéfinir ces classes dans votre code PHP.

Voir aussi

  • RFC: Remove preg_replace /e modifier : la RFC en rapport avec l’arrêt du support de /e par preg_replace() – elle donne notamment plus d’explications sur le pourquoi de ce changement.

Et je terminerai en vous pointant vers le fichier UPGRADING, qui contient un ensemble de sections intéressantes (dont Backward Incompatible Changes, Changed Functions, Changes to INI File Handling, …) et devrait être mise à jour à chaque changement incompatible avec les versions précédentes. Ce fichier sera bien évidemment à consulter après la sortie de PHP 5.5, pour déterminer ce qui risque de casser dans vos applications.

Cette volonté de conserver un maximum de compatibilité entre les versions de PHP *(et ça va plus loin que PHP 5.x -> PHP 5.(x+1))* est d'ailleurs une raison pour laquelle PHP a trainé un bon nombre de boulets "historiques" pendant fort longtemps *(voire continue de le faire)* -- je pense par exemple à `register_globals` ou à `safe_mode`, aux nommages de fonctions pas toujours homogènes, ou aux ordres de paramètres pas toujours logiques.
J'ajouterais volontier qu'avoir des tests automatisés *(unitaires, fonctionnels, de non-régression, de performance, ...)*, de qualité sur votre application aide fortement lorsqu'il s'agit de monter de version de PHP -- ne serait-ce que parce qu'ils permettent d'identifier des points que vous aurez peut-être à adapter, mais sont aussi en mesure de vous montrer que le travail à effectuer de votre côté n'est peut-être pas aussi important que vous pourriez le craindre.