How a PHP interpreter works

Wie ein PHP-Interpreter funktioniert

PHP ist, wie viele andere Sprachen für Webanwendungen, eine interpretierte Sprache. Beim Ausführen einer in PHP geschriebenen Anwendung denken wir normalerweise nicht daran, was während der Ausführung mit dem Code tatsächlich passiert. In diesem Artikel erfahren Sie, wie der fertige Code von einem PHP-Interpreter verarbeitet wird.

Kompilierung und Interpretation

Kompilierte Sprachen wie zum Beispiel C, C++ unterscheiden sich von interpretierten Sprachen dadurch, dass ihre Umsetzung in Maschinencode nur einmal erfolgt. Nach dem Kompilierungsprozess kann die Anwendung vielmals ausgeführt werden, ohne dass eine weitere Kompilierung erforderlich ist. Nach der Kompilierung der Anwendung gibt es keine zusätzliche Zeitverzögerung für deren weitere Verarbeitung, aber der Entwicklungsprozess ist ebenfalls schwieriger (Änderungen erfordern eine Neukompilierung). Alternativ haben wir interpretierte Sprachen wie PHP, Python und Ruby. Sie sind weniger effizient, da ihr Code von einer separaten Anwendung (einem Interpreter) verarbeitet wird, die den Anwendungscode "on the fly" übersetzt. Eine solche Strategie bedeutet geringere Leistung und Ausführungszeiten der Anwendung, ermöglicht auf der anderen Seite jedoch eine größere Flexibilität und Einfachheit in der Softwareentwicklung. Schauen wir uns also genauer an, wie ein PHP-Interpreter funktioniert.

Zend Engine

Zend Engine ist sowohl die Engine als auch das Herzstück der PHP-Sprache. Sie besteht aus einem Quellcode-zu-Bytecode-Compiler und einer virtuellen Maschine, die diesen Code ausführt. Sie wird direkt mit PHP geliefert – wenn Sie PHP installieren, installieren Sie gleichzeitig die Zend Engine. Sie ist für die gesamte Codeverarbeitung verantwortlich, vom Moment, in dem Ihr HTTP-Server die Ausführung des angeforderten PHP-Skripts an sie sendet, bis der HTML-Code generiert und an den Server zurückgegeben wird. Vereinfacht gesagt, wird die gesamte Verarbeitung eines PHP-Skripts vom Interpreter in vier Stufen durchgeführt:

  • lexikalische Analyse (Lexing),
  • Syntaxanalyse (Parsing),
  • Kompilierung,
  • Ausführung.

Mit der Einführung des OPcache-Mechanismus kann der gesamte Prozess grundsätzlich bis zum letzten Schritt - dem Starten/Ausführen der Anwendung auf einer virtuellen Maschine - übersprungen werden. Die Situation wird noch komfortabler, wenn Sie wissen was neu ist in PHP-Version 8. Ich meine natürlich den JIT-Compiler, der es Ihnen ermöglicht, PHP-Code zu kompilieren. Dadurch ist es möglich, den Maschinencode direkt auszuführen - unter Umgehung des Prozesses von Interpretation oder Ausführung durch eine virtuelle Maschine.

Ich möchte hinzufügen, dass es in der Vergangenheit eine andere interessante Option gab - das Transpilieren von Code, z.B. in die C++-Sprache. Eine solche Lösung wurde in HipHop für PHP verwendet, das von den Facebook-Programmierern erstellt wurde und nicht mehr weiterentwickelt wird. In einem späteren Stadium wurde jedoch das Transpilieren durch das HHVM (HipHop Virtual Machine) Projekt auf Basis der Just-in-Time (JIT)-Kompilierung ersetzt.

Nichtsdestotrotz, lassen Sie uns betrachten, wie die einzelnen Interpretationsschritte in ihrer grundlegendsten Form aussehen.

Lexikalische Analyse (Lexing)

Manchmal auch Tokenisierung genannt, ist es eine Phase, die buchstäblich darin besteht, eine Zeichenfolge aus dem in PHP geschriebenen Quellcode in eine Sequenz von Token zu verwandeln, die beschreibt, was jeder nächste gefundene Wert bedeutet. Die auf diese Weise generierte Tokenmenge hilft dem Interpreter bei der weiteren Verarbeitung des Codes.

PHP verwendet den re2c-Lexer-Generator mit der Definitionsdatei zend_language_scanner.l. In seiner Grundform führt er reguläre Ausdrücke in der übertragenen Datei aus, was die Identifizierung einzelner Codeelemente ermöglicht, z.B. aus der Sprache-Syntax, wie "if", "switch", "function", usw.

Wenn Sie besser verstehen möchten, wie solche Token generiert werden, wird dies gut dargestellt, indem Sie den folgenden PHP-Code implementieren:

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

Natürlich funktioniert der Lexer nicht genau so, aber es sollte Ihnen eine Vorstellung davon geben, wie der Code analysiert wird. Wenn Sie jedoch wissen möchten, wie die generierten Tokens für einen Beispielcode aussehen:

<?php

$my_variable = 1;

Es sieht folgendermaßen aus:

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

Auf den ersten Blick können Sie feststellen, dass nicht alle Elemente Tokens sind. Einige Zeichen wie =, ;, :, ? werden als eigenständige Tokens betrachtet.

Interessanterweise verarbeitet der Lexer nicht nur den Code in Tokens, sondern speichert auch die Informationen über den von den Tokens gespeicherten Wert sowie den Verweis auf die spezifische Linie, in der er aufgefangen wurde. Dies wird unter anderem verwendet, um eine Stack-Trace einer Anwendung zu generieren. Syntaxanalyse (Parsing) Dies ist ein weiterer Prozess, der wie Lexing darin besteht, die generierten Tokens in eine geordneter und organisierter Datenstruktur zu verarbeiten. Wie beim Lexing, verwendet PHP hier ein externes Tool namens GNU Bison basierend auf dem BNF-Datei, welche die Grammatik der Sprache enthält. Es ermöglicht Ihnen, eine kontextfreie Grammatik in eine nützlichere, ursachen- und wirkungsbezogene Grammatik zu verwandeln. Die LALR(1)-Methode wird für die Umwandlung verwendet, die das Eingabesignal mit einer Vorschau von n Tokens voraus (im Fall von PHP 1) von links nach rechts liest und eine rechterseitige Ausgabe erzeugt. Durch diesen Prozess ist der Parser in der Lage, Tokens mit den in der BNF-Datei definierten Grammatikregeln abzugleichen. Im Prozess des Abgleichs von Tokens wird validiert, ob die Tokens die richtigen Syntaxkonstrukte bilden.

Das endgültige Produkt dieser Phase ist die Generierung eines abstrakten Syntaxbaums (AST) durch den Parser. Dies ist die Baumansicht des Quellcodes, die in der Kompilierungsphase verwendet wird. Mit der php-ast -Erweiterung ist es möglich, sich ein Beispiel für eine solche Struktur anzusehen. Hier noch einmal ein Beispiel-Codeausschnitt:

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

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

Als Ergebnis erhalten Sie einen Baum mit einer Struktur wie dieser:

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

Obwohl diese Struktur aus der Perspektive eines Programmierers nicht viel aussagen mag, ist sie nützlich, um statische Codeanalysen mit Tools wie Phan durchzuführen.

Der AST ist die letzte Stufe der Analyse – im nächsten Schritt wird der Code in dieser Form zur Kompilierung übertragen.

Kompilierung

Ohne die Verwendung von JIT wird PHP in seiner Standardform vom generierten AST zu OPCode kompiliert, nicht – wie bei JIT – in Maschinencode. Der Kompilierungsprozess wird durch rekursives Durchlaufen des AST durchgeführt, wobei auch einige Optimierungen vorgenommen werden. Oftmals werden einfache arithmetische Berechnungen durchgeführt oder Ausdrücke wie strlen("test") werden direkt durch den Wert int(4) ersetzt.

Wie bei den vorherigen Phasen stehen Ihnen auch hier Tools zur Vorschau des generierten OPCode zur Verfügung. Zu den verfügbaren Tools gehören VLD oder OPCache. Nachfolgend ein Beispiel-Dump, bereitgestellt von VLD aus einer kompilierten Greeting Klasse mit einer sayhello Methode:

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

Durch die Betrachtung des obigen Dumps kann ein geübter PHP-Entwickler seine Struktur auf einer einfachen Ebene verstehen. Definiert ist hier die Klasse und Methode, gefolgt von:

  • Annahme eines Wertes durch die Funktion
  • Erstellung der temporären Variablen
  • Verkettung der Zeichenfolgen hinter den Variablen
  • Ausgabe der temporären Variablen
  • Rückkehr aus der Funktion nach deren Abschluss

Die vollständige Beschreibung des gesamten Themas OPCode und seiner Komponenten würde definitiv den Rahmen dieses Artikels sprengen. Wenn Sie mehr darüber erfahren möchten, wird Ihnen die offizielle Dokumentation helfen, den Anfang zu machen.

Ausführung

Dies ist die letzte Phase des Interpretationsprozesses. In diesem Stadium wird der generierte OPCode tatsächlich auf der Zend-Virtual-Machine (Zend Engine VM) ausgeführt. Das Endergebnis ist das, was ein bestimmtes Skript erzeugen sollte, d.h. das gleiche wie die Ausgabe von Befehlen wie echo oder print. Aus Sicht von Webanwendungen ist es üblicherweise ein fertiger Quellcode für eine Website.

Zusammenfassung

Die meisten von uns denken nicht darüber nach, wie der PHP-Code tatsächlich auf einem Server analysiert und ausgeführt wird – insbesondere wenn wir Server- und Anwendungsüberwachung an externe Dienstleister übergeben. Dennoch ist es gut zu verstehen, was wirklich mit dem Code Ihrer Anwendung passiert, wenn er an einem Interpreter übertragen wird. Solches Wissen kann sowohl bei der Sicherheits- als auch bei der Leistungsanalyse eines Projekt, das in PHP entwickelt wurde, helfen.

-