How a PHP interpreter works

Comment fonctionne un interprète PHP

PHP, comme c'est le cas pour de nombreux autres langages utilisés pour les applications web, est un langage interprété. Lorsque nous exécutons une application écrite en PHP, nous ne pensons généralement pas à ce qui se passe vraiment avec son code pendant l'exécution. Dans cet article, vous apprendrez comment le code final est traité par un interpréteur PHP.

Compilation et interprétation

Les langages compilés, tels que, par exemple, C, C++, diffèrent des langages interprétés du fait que leur transformation en code machine est effectuée une seule fois. Après le processus de compilation, vous pouvez exécuter l'application plusieurs fois sans nécessiter une autre compilation. Une fois que l'application est compilée, il n'y a pas de surcoût de temps supplémentaire pour son traitement ultérieur, mais son processus de développement est également plus difficile (les modifications nécessitent une recompilation). En alternative, nous avons des langages interprétés, tels que PHP, Python et Ruby. Ils sont moins efficaces car leur code est traité par une application séparée (un interpréteur) qui traduit le code de l'application "à la volée". Une telle stratégie signifie une performance et un temps d'exécution plus faibles, mais d'un autre côté, elle permet une plus grande flexibilité et une plus grande facilité de développement logiciel. Alors, examinons de plus près comment fonctionne un interpréteur PHP.

Zend Engine

Zend Engine est à la fois le moteur et le cœur même du langage PHP. Il se compose d'un compilateur de code source en bytecode et d'une machine virtuelle qui exécute ce code. Il est livré directement avec PHP – lorsque vous installez PHP, vous installez Zend Engine en même temps. Il est responsable de tout le traitement du code, depuis le moment où votre serveur HTTP envoie l'exécution du script PHP demandé, jusqu'à ce que le code HTML soit généré et renvoyé au serveur. Pour simplifier, tout le traitement d'un script PHP est effectué par l'interpréteur en quatre étapes :

  • analyse lexicale (lexing),
  • analyse syntaxique (parsing),
  • compilation,
  • exécution.

Avec l'introduction du mécanisme OPcache, tout le processus peut être essentiellement sauté jusqu'à la dernière étape - le lancement/l'exécution de l'application sur une machine virtuelle. La situation devient encore plus confortable si vous savez ce qui est nouveau dans la version PHP 8. Je parle bien sûr du compilateur JIT qui vous permet de compiler du code PHP. En conséquence, il est possible d'exécuter directement le code machine - en évitant le processus d'interprétation ou d'exécution par une machine virtuelle.

Je voudrais ajouter que dans le passé, il y avait une autre option curieuse - la transpilation de code, par exemple, dans le langage C++. Une telle solution a été utilisée dans HipHop pour PHP créé par les programmeurs de Facebook, qui n'est plus développé. À un stade ultérieur, cependant, la transpilation a été remplacée par le projet HHVM (HipHop Virtual Machine) basé sur la compilation à la volée (JIT).

Néanmoins, voyons ce à quoi ressemblent les étapes individuelles de l'interprétation dans leur forme la plus basique.

Analyse lexicale (Lexing)

Parfois aussi appelée tokenisation, il s'agit d'une phase qui consiste littéralement à convertir une chaîne de caractères du code source écrit en PHP en une séquence de tokens qui décrivent ce que signifie chaque valeur rencontrée. L'ensemble de tokens généré de cette manière aide l'interpréteur dans le traitement ultérieur du code.

PHP utilise le générateur de lexer re2c avec le fichier de définition zend_language_scanner.l. Dans sa forme basique, il exécute des expressions régulières dans le fichier transféré, ce qui permet d'identifier les éléments de code individuels, par exemple, de la syntaxe du langage, tels que "if", "switch", "function", etc.

Si vous souhaitez mieux comprendre comment ces tokens sont générés, cela est bien présenté en implémentant le code PHP suivant :

<?php
function lexer($bytes, ...) {
    switch ($bytes) {
        case substr($bytes, 0, 2) == "if":
            return TOKEN_IF;
    }
}
?>

Bien sûr, le lexer ne fonctionne pas exactement de cette façon, mais cela devrait vous donner une idée de la façon dont le code est analysé. Cependant, si vous souhaitez savoir à quoi ressemblent les tokens générés pour un exemple de code :

<?php

$my_variable = 1;

Cela ressemble à ceci :

T_OPEN_TAG ('<?php')
T_VARIABLE ('$my_variable')
T_WHITESPACE (' ')
=
T_WHITESPACE (' ')
T_LNUMBER ('1')
;

À première vue, vous pouvez remarquer que tous les éléments ne sont pas des tokens. Certains caractères comme =, ;, :, ? sont considérés comme des tokens à part entière.

Il est intéressant de noter que le lexer ne se contente pas de transformer le code en tokens, mais il stocke également les informations sur la valeur stockée par les tokens, ainsi que sur la référence à la ligne spécifique dans laquelle il a été intercepté. Cela est utilisé, entre autres, pour générer une trace de pile d'une application. Analyse syntaxique (parsing) Il s'agit d'un autre processus consistant, comme le lexing, à traiter les tokens générés dans une structure de données plus ordonnée et organisée. Comme pour le lexing, PHP utilise ici un outil externe appelé GNU Bison basé sur le fichier BNF contenant la grammaire du langage. Cela permet de convertir une grammaire libre de contexte en une grammaire plus utile, de cause à effet. La méthode LALR(1) est utilisée pour la conversion, qui lit l'entrée avec un aperçu de n tokens à venir (dans le cas de PHP 1) de gauche à droite et produit une sortie du côté droit. À travers ce processus, l'analyseur syntaxique est capable de faire correspondre les tokens aux règles de grammaire définies dans le fichier BNF. Dans le processus de correspondance des tokens, il est validé si les tokens forment les constructions syntaxiques correctes.

Le produit final de cette phase est la génération par l'analyseur d'un arbre syntaxique abstrait (AST). Il s'agit de la vue arborescente du code source qui sera utilisée lors de la phase de compilation. Grâce à l'extension php-ast, il est possible de prévisualiser un tel exemple de structure. En utilisant à nouveau un extrait de code :

$php_code = <<<'code'
<?php
$my_variable = 1;
code;

print_r(ast\parse_code($php_code, 30));

En conséquence, vous obtiendrez un arbre avec une structure comme celle-ci :

ast\Node Object (
    [kind] => 132
    [flags] => 0
    [lineno] => 1
    [children] => Array (
        [0] => ast\Node Object (
            [kind] => 517
            [flags] => 0
            [lineno] => 2
            [children] => Array (
                [var] => ast\Node Object (
                    [kind] => 256
                    [flags] => 0
                    [lineno] => 2
                    [children] => Array (
                        [name] => my_variable
                    )
                )
                [expr] => 1
            )
        )
    )
)

Bien que cette structure ne vous dise pas grand-chose du point de vue d'un programmeur, elle est utile pour effectuer une analyse statique du code à l'aide d'outils tels que Phan.

AST est la dernière étape de l'analyse ; à l'étape suivante, le code sous cette forme est transmis pour la compilation.

Compilation

Sans l'utilisation de JIT, PHP dans sa forme standard est compilé à partir de l'AST généré en OPCode, et non - comme c'est le cas avec JIT - en code machine. Le processus de compilation est effectué en parcourant récursivement l'AST, au cours duquel certaines optimisations sont également effectuées. Le plus souvent, des calculs arithmétiques simples sont effectués, ou des expressions telles que strlen("test") sont remplacées par une valeur directe int(4).

Comme pour les phases précédentes, il existe également des outils pour prévisualiser l'OPCode généré. Parmi les outils à votre disposition, vous avez VLD ou OPCache. Ci-dessous un exemple de vidage fourni par le VLD à partir d'une classe Greeting compilée fournissant une méthode sayhello :

Class Greeting:
Function sayhello:
number of ops:  8
compiled vars:  !0 = $to

line      # *    op                      fetch          ext     return     operands
----------------------------------------------------------------------------
   3      0  >   EXT_NOP
          1      RECV                                         !0
   5      2      EXT_STMT
          3      ADD_STRING                                   ~0    'Hello+'
          4      ADD_VAR                                      ~0    ~0, !0
          5      ECHO                                                 ~0
   6      6      EXT_STMT
          7    > RETURN                                               null

En visualisant le vidage ci-dessus, un développeur PHP qualifié peut comprendre sa structure à un niveau basique. Y sont définis ici la classe et la méthode, suivis de :

  • l'hypothèse de valeur par la fonction
  • la création de la variable temporaire
  • la concaténation des chaînes derrière les variables
  • l'impression de la variable temporaire
  • le retour de la fonction après sa completion

Décrire toute la question de l'OPCode et de ses composants de manière exhaustive dépasserait largement le cadre de cet article. Si vous souhaitez en savoir plus à ce sujet, la documentation officielle vous aidera à démarrer.

Exécution

C'est la dernière phase du processus d'interprétation. À ce stade, vous exécutez réellement l'OPCode généré sur la machine virtuelle Zend (Zend Engine VM). Le résultat final est ce qu'un script donné était censé générer, c'est-à-dire le même que la sortie de commandes telles que echo ou print. Du point de vue des applications web, il s'agit généralement d'un code source prêt pour un site web.

Résumé

La plupart d'entre nous ne pensent pas à la façon dont le code PHP est réellement analysé et exécuté sur un serveur – surtout lorsque nous confions la surveillance du serveur et des applications à des prestataires de services externes. Néanmoins, il est bon de comprendre ce qui se passe réellement avec le code de votre application lorsqu'il est transféré à un interpréteur. Une telle connaissance peut vous aider à la fois pour l'analyse de la sécurité et des performances d'un projet développé en PHP

-