PHP 5.3 : goto

28 octobre 2008php, php-5.3
 Cet article a été rédigé il y a plusieurs années et peut ne plus être tout à fait à jour…

Les exemples correspondant à ce point se trouvent dans le répertoire "goto".

Une des nouveautés introduite par PHP 5.3, et pas toujours bien acceptée semble-t-il, est l'ajout de la directive goto.

Sommaire :


Ajout de la directive goto

Le principe est le même que dans les autres langages que vous avez déjà eu l'occasion d'utiliser :

  • Vous définissez une étiquette (un label) à un endroit dans votre code,
    • Une étiquette se définissant en utilisant son nom, suivi du symbole ':'.
    • Le nom de l'étiquette est soumis aux mêmes contraintes que les noms de variables, sans le $.
  • et vous utilisez, à un autre endroit, la directive goto pour sauter vers cette étiquette.

Par exemple, pour définir une étiquette nommée "c", et y sauter :

echo '<p>a</p>';

goto c;

echo '<p>b</p>';

c:
echo '<p>c</p>';

L'affichage commencera par la première instruction echo, et l'exécution sautera ensuite vers l'étiquette c, n'exécutant pas la seconde sortie écran :

a

c

Quelle utilité pour vos programmes ?
Certains développeurs vous diront aucune...
D'autres, peut-être un brin plus modéré, admettront l'usage de goto dans quelques cas exceptionnels, typiquement pour sortir de profondes imbrications de boucles[1].


Pas de support des goto dynamiques

L'instruction goto souffre tout de même d'une certaine limitation, qui ne sera peut-être que bénéfique à la qualité du code de certaines applications PHP : il n'est pas possible de sauter vers une étiquette dont le nom serait déterminé dynamiquement, à l'exécution : le nom de l'étiquette vers laquelle l'exécution de code sautera doit être inscrit en dur dans le code PHP.

Par exemple, prenons la portion de code suivante :

echo '<p>a</p>';

$label = 'c';
goto $label; // Parse error: syntax error, unexpected T_VARIABLE, expecting T_STRING

echo '<p>b</p>';

c:
echo '<p>c</p>';

Essayer d'exécuter ce code ménera à une Parse Error : le code ne compile même pas :

not-goto-dynamic-label.png


goto et blocs

La nouvelle instruction goto permet de sauter en plein milieu de certains types de blocs ; typiquement, les blocs conditionnels if.
Par exemple, prenons le code suivant :

$a = 10;
goto label;
if ($a == 20) {
  label:
    echo 'a == 20 !';
} else {
    echo 'a != 20';
}

La sortie obtenue sera :

a == 20 !

Alors que a ne vaut définitivement pas 20 !
(Voici typiquement un très mauvais exemple d'utilisation de goto, soit dit en passant ^^ )


Par contre, vous ne pouvez pas sauter au milieu d'un bloc correspondant à une instruction de boucle (for, while, ...), ni au milieu d'un bloc switch.
Par exemple, en utilisant le code suivant :

goto label2;
for ($i=0 ; $i<10 ; $i++) {
  label2:
    echo $i . "\n";
}

Vous obtiendrez une Fatal Error :

not-goto-into-loop.png


Pas de support de break vers une étiquette

Enfin, l'ajout du support de goto et de la notion d'étiquettes aurait pu amener à penser que les instructions break et continue auraient pu être modifiées, de manière à être capables de sauter vers une étiquette, ce qui est tout de même plus lisible -- et surtout, plus maintenable, que de les utiliser pour sortir d'un nombre de boucles, ce qui casse à chaque fois que l'on modifie nos imbrications...
(Ceux qui ont déjà développé en Perl verront probablement de quoi je parle, à ce sujet)

Mais cette fonctionnalité n'a pas été introduite :-(

Par exemple, voici break utilisée pour sortir d'une double imbrication :

for ($i=0 ; $i<5 ; $i++) {
    for ($j=0 ; $j<5 ; $j++) {
        for ($k=0 ; $k<5 ; $k++) {
            echo "$i ; $j ; $k \n";
            if ($k == 2) {
                echo "break 2\n";
                break 2;
            }
        }
    }
}

Est-ce que ce code ne serait pas plus lisible écrit de la manière suivante :

for ($i=0 ; $i<5 ; $i++) {
  label:
    for ($j=0 ; $j<5 ; $j++) {
        for ($k=0 ; $k<5 ; $k++) {
            echo "$i ; $j ; $k \n";
            if ($k == 2) {
                echo "break 'label'\n";
                break 'label';  // ne break que de un niveau => ne fonctionne pas
                    // et sans les quotes :
                    // Notice: Use of undefined constant label - assumed 'label'
            }
        }
    }
}

(La sortie de cet exemple trop longue pour être reproduite ici)

Mais ce code ne fonctionne pas :

  • En utilisant << break label; >>, on obtient une notice :

not-break-label.png

  • Et en mettant nous-même les quotes (en utilisant << break 'label'; >>, donc), nous ne sortons que d'un niveau de la boucle, et pas deux comme nous l'aurions souhaité.

Peut-être pour une prochaine version ?


Note

[1] Encore que, en PHP, nous avons la possibilité d'utiliser l'instruction break en lui précisant de combien de niveaux de profondeur elle doit sortir...