URL dieses Artikels:

zu Ausgabe: 2.2006
Einer für alle, alle für einen: PHP kompatibel
Kompatibilitätscheck für Code
Christian Wenz
Kontrollierte Systeme vereinfachen die Entwicklung, aber Unheil droht, wenn PHP-Code auf diversen Installationen laufen muss. Ein PEAR-Paket prüft das weitgehend automatisch.

In der letzten Ausgabe des PHP Magazins [1] wurde das PEAR-Paket PHP_Compat vorgestellt, das auf Wunsch neuere Funktionen in PHP soweit wie möglich auf älteren PHP-Systemen emulieren kann. In diesem Beitrag kommt ein verwandtes PEAR-Paket zum Zuge. PHP_CompatInfo von Davey Shafik prüft ein Skript darauf, wie "kompatibel" es ist. Das klingt nach einer Sensation, ist es aber nicht, denn: Alle Fälle kann auch PHP_CompatInfo nicht abfangen. Dennoch bietet das Skript eine gute Unterstützung bei dem Unterfangen, seinen Code möglichst kompatibel zu gestalten. Der Artikel zeigt den Einsatz und wirft einen kurzen Blick auf den Quellcode.

Installation
Wie jedes andere PEAR-Paket wird auch PHP_CompatInfo am einfachsten mit dem PEAR-Installer auf das System gepackt:

pear upgrade PHP_CompatInfo


Allerdings gilt es zu beachten, dass das Paket einige andere PEAR-Pakete optional mitverwendet, getreu der (nicht immer unumstrittenen) PEAR-Maxime der Wiederverwendung anderer PEAR-Pakete. Es handelt sich dabei um:
  • Console_Getopt
  • Console_Table

Beide PEAR-Pakete sind aber nur optionale Abhängigkeiten und werden von einem CLI-Frontend von PHP_CompatInfo benötigt.


Abb. 1: Installation von PHP_CompatInfo


Alternativ kann PHP_CompatInfo auch von Hand installiert werden. Auf der Projekt-Homepage [2] gibt es ein TGZ-Archiv, das neben einigen Beispielen die beiden Hauptdateien enthält, CompatInfo.php und Cli.php. In einem Verzeichnis, das im include_path steht, gilt es nun ein Unterverzeichnis PHP zu erstellen und dort die Datei CompatInfo.php hineinzukopieren. Unterhalb von PHP gehört das Verzeichnis CompatInfo, in dem die Datei Cli.php landet. Ebenfalls unterhalb von PHP muss ein Verzeichnis data angelegt werden, in das die beiden Dateien const_array.php und func_array.php gehören.
Wenn Console_Getopt verwendet wird, sind analoge Schritte notwendig. Unter [3] gibt es ein TGZ-Archiv mit (unter anderem) der Datei Getopt.php, die in ein Unterverzeichnis Console abgelegt werden muss. Ähnliches gilt für Console_Table, erhältlich unter [4].
Das "Geheimnis" des Pakets liegt in den Tokenizer-Funktionen von PHP, einer wenig bekannten Erweiterung von PHP, die seit Version 4.3.0 automatisch mit dabei ist (außer PHP wurde mit --disable-tokenizer kompiliert). Diese greift direkt auf den Tokenizer in der Zend Engine zu und ermöglicht somit eine Codeanalyse ohne eigenen lexikalischen Scanner. Die Erweiterung implementiert nur zwei Funktionen:
  • token_get_all(): Liefert alle Tokens innerhalb eines Codestücks (das als String-Parameter übergeben wird).
  • token_name(): Wandelt einen Token-Wert (etwa den numerischen Wert, für den eine Konstante wie beispielsweise T_OPEN_TAG steht) in eine String-Repräsentation der Konstanten um (etwa "T_OPEN_TAG").

Unter Verwendung des Ergebnisses von token_get_all() überprüft PHP_CompatInfo den Quellcode und gibt am Ende zurück, welche PHP-Version zur Verwendung des Ganzen notwendig ist. Doch wo kommt diese Version her? Schließlich können beispielsweise ältere PHP-Versionen nicht wissen, was in neueren Versionen hinzugefügt wird. Dazu legt der PEAR-Installer im Verzeichnis PHP/data zwei Hilfsdateien an, const_array.php (mit allen PHP-Konstanten samt Versionsnummern) und func_array.php
(mit allen PHP-Funktionen samt Versionsnummern). Dank Tokenizer hat PHP_CompatInfo Zugriff auf alle Konstanten und Funktionen im Skript und kann dann mit den Hilfsdateien die entsprechenden Versionsnummern ermitteln.

Automatischer Test
Die einfachste Variante von PHP_CompatInfo ist der Einsatz der Methode parseString(), die einen String überprüft (Listing 1).

Listing 1

<?php
require_once 'PHP/CompatInfo.php';
$code = " <?php
session_start();
session_regenerate_id();
?>";
$ci = new PHP_CompatInfo;

echo '<pre>';
echo var_dump($ci->parseString($code));
echo '</pre>';
?>


Abbildung 2 zeigt die Ausgabe unter PHP 4.4.1. Die Versionsnummer verwirrt etwas - session_start() gibt es seit PHP 4, session_regenerate_id() seit PHP 4.3.2 - aber immerhin, die verwendete Erweiterung (session) wird angezeigt, wenngleich die Konstante ("implements") ebenfalls aus dem Nichts kommt. Schon anders ist es, wenn PHP 5 eingesetzt wird (Abb. 3), dann stimmt das Ergebnis: Mindestens PHP 4.3.2 ist notwendig, um diesen Code auszuführen. Neben parseString() gibt es auch noch andere Methoden:
  • parseFile(): Analysiert eine Datei.
  • parseDir() oder parseFolder(): Analysiert ein Verzeichnis (Dateiendungen und auszuschließende Dateien können angegeben werden).
  • parseArray(): Parst eine Liste von Dateien oder Strings.

Somit lassen sich auch automatisiert ganze Projekte prüfen.


Abb. 2: Mit PHP 4 gibt es offenbar Probleme ...


Abb. 3: ... doch unter PHP 5 läuft das PEAR-Paket tadellos.

Test per Kommandozeile
In der Datei CompatInfo/Cli.php ist ein CLI-Frontend implementiert, mit dem es auch möglich ist, per Kommandozeile das Skript aufzurufen. Die Klasse wird folgendermaßen angesprochen:

<?php
require_once 'PHP/CompatInfo/Cli.php';
$picli = new PHP_CompatInfo_Cli();
$picli->run();
?>


Abbildung 4 zeigt das Ergebnis auf der Kommandozeile.


Abb. 4: PHP_CompatInfo gibt es auch als CLI-Version

Einschränkungen und Fazit
So schön die Idee und die Umsetzung auch sind, ganz fehlerfrei funktioniert das Ganze trotz allem nicht. Eventuelle Bugfixes zwischen den einzelnen Versionen (etwa im Bereich Variablen-Evaluierung innerhalb von Strings) können nicht berücksichtigt werden. Bei variablen Funktionen scheitert das Vorgehen ebenfalls, da dort erst zur Laufzeit ermittelt wird, welche Funktion aufgerufen werden soll. Listing 2 zeigt ein Beispiel, ausgehend von Listing 1 in diesem Artikel.

Listing 2

<?php
require_once 'PHP/CompatInfo.php';
$code = " <?php
session_start();
\$s = 'session_regenerate_id';
\$s();
?>";
$ci = new PHP_CompatInfo;

echo '<pre>';
echo var_dump($ci->parseString($code));
echo '</pre>';
?>


Die Auswirkungen des zu prüfenden Codes (in $code) sind dieselben wie zuvor, aber die Ausgabe des prüfenden Codes ist eine andere, wie Abbildung 5 dokumentiert. Der Grund: Die einzige Funktion, die der Tokenizer zu sehen bekommt, ist session_start(), und die gibt es in der Tat ab PHP-Version 4.0.0.


Abb. 5: Variable Funktionen bringen den Erkennungsalgorithmus aus dem Tritt


Das soll zeigen, dass PHP_CompatInfo nicht immer hundertprozentig zuverlässige Ergebnisse liefern kann, aber dennoch eine gute Hilfe ist, wenn es darum geht, eventuelle Versionsabhängigkeiten einer PHP-basierten Software zu ermitteln. Und das, ohne einen eigenen Parser schreiben zu müssen. Bis zur nächsten Ausgabe!
Christian Wenz ist Autor, Trainer und Berater mit Schwerpunkt Webtechnologien. In seiner Serie "x ohne x" zeigt er regelmäßig, wie PHP ungeahnte Fähigkeiten entwickeln kann. Er selbst probiert immer gerne die neuesten PHP-Versionen aus, seine Kunden dagegen (vor allem größere Firmen) sind upgrade-faul. Ziemlich systemunabhängig ist zumindest sein Weblog unter www.hauser-wenz.de/blog/.

Links & Literatur

© 2004 Software & Support Verlag GmbH. Vervielfältigung nur mit Genehmigung des Verlags. Fragen?