Codeanalyse unter PHP 7 mit Phan
Nachdem ich auf meinem Entwicklungsrechner seit kurzem PHP 7 installiert habe, wollte ich einige (meiner) Projekte mit Hilfe des Code-Analyzers Phan
, von dem vor dem Release der neuen PHP-Version schon einiges zu lesen war, auf Fehler untersuchen. Phan
muss zwar unter PHP 7 ausgeführt werden, kann aber auch ältere Projekte, die beispielsweise für PHP 5.x entwickelt wurden, inspizieren.
Installation
Bevor es an die Installation von Phan geht, sind einige Voraussetzungen zu erfüllen: Insbesondere wird natürlich PHP 7 benötigt, zusätzlich aber auch die Extension php-ast
, die den abstract syntax tree
aufzeigen kann. Der Abstrakte Syntaxbaum (AST) ist neu in PHP 7 und stellt sozusagen einen Zwischenschritt dar, bei dem der PHP-Code in besser optimierbare Ausdrücke umgewandelt wird. Wie das bei HHVM im Detail aussieht, hat Sara Golemon ausführlich beschrieben. Zum Einlesen in die PHP-Implementierung ist der zugehörige RFC ein guter Einstiegspunkt.
In PHP 7 werden Skripte in einen Syntax-Baum zerlegt, der aus ast/Node
-Objekten besteht, also z. B. einer Statement-Liste (AST_STMT_LIST
), einer Variablen-Zuweisung (AST_ASSIGN
) usw. Für ein ganz simples Skript kann der AST dann so aussehen:
Das Skript nutzt die ast_dump()
-Funktion aus dem util.php
-Utitlity, um die Ausgabe besser lesbar zu machen. Wichtig ist auch, dass der Methode ast\parse_code()
der zweite Parameter ($version
) mitgegeben wird; Version 30 ist zwar noch als unstable
deklariert, hat aber bei mir bisher funktioniert.
AST-Erweiterung installieren
Die Extension php-ast
ist als experimentell gekennzeichnet (also keinesfalls produktiv einsetzen!) und wird folgendermaßen kompiliert:
$ cd php-ast
$ phpize
$ ./configure
$ make
$ sudo make install
Danach sollte die Extension noch der php.ini
hinzugefügt werden, z.B. mittels einer eigenen Konfigurationsdatei in conf.d/
.
Phan installieren
Die Installation von Phan
selbst ist einfach:
$ cd phan
$ composer install
Anwendung
Zum Ausprobieren eignen sich kleinere Projekte gut, ansonsten kann es passieren, dass einen die Menge der gefundenen Fehler erschlägt
Beispielhaft sei die Ausgabe von Phan hier an der Fotoverwaltung Lychee demonstriert, deren PHP-Code ich für verbesserungswürdig *hust* halte (ansonsten gefällt mir das Tool eigentlich gut). Weil die Dateien dieser Applikation in einer bestimmten Reihenfolge geladen werden müssen, habe ich sie in der Datei .filelist.txt
aufgelistet, die von Phan eingelesen wird.
modules/Database.php:12 ParamError required arg follows optional
modules/Album.php:96 VarError Variable $return is not defined
modules/Photo.php:640 TypeError arg#2(timestamp) is string but \date() takes int
modules/Photo.php:128 TypeError Access to undeclared static property validExtensions on \photo
modules/Photo.php:725 VarError Variable $return is not defined
modules/Photo.php:768 TypeError arg#3(sub_arrays) is int but \exif_read_data() takes bool
modules/Photo.php:774 TypeError Suspicious array access to bool
modules/Photo.php:512 UndefError reference to undeclared class \imagick
modules/Photo.php:514 UndefError reference to undeclared class \imagickpixel
modules/Photo.php:444 UndefError reference to undeclared class \imagickexception
modules/Import.php:72 TypeError Access to undeclared static property validExtensions on \photo
modules/Import.php:80 TypeError Access to undeclared static property validTypes on \photo
modules/Import.php:136 VarError Variable $contains is not defined
modules/Photo.php:668 TypeError arg#2(timestamp) is string but \date() takes int
modules/Session.php:41 VarError Variable $return is not defined
api.php:40 VarError Variable $dbHost is not defined
api.php:40 VarError Variable $dbUser is not defined
api.php:40 VarError Variable $dbPassword is not defined
api.php:40 VarError Variable $dbName is not defined
Wir haben es hier mit einer ganzen Reihe von Fehlermeldungen zu tun, auch wenn dies nur ein Auszug aus dem Analyse-Ergebnis ist. Einige davon möchte ich näher erläutern:
- "
ParamError required arg follows optional
" betrifft die Methodensignatur, genauer die Parameter:connect($host = 'localhost', $user, $password, $name = 'lychee')
(siehe hier). Der erste Parameter$host
ist optional, aber erforderliche Parameter sollten als erste aufgeführt werden. - "
TypeError arg#2(timestamp) is string but \date() takes int
": Im Handbuch steht, dassstring date ( string $format [, int $timestamp = time() ] )
als zweiten Parameter einen Integer-Wert erwartet; hier wird aber ein String übergeben. - "
VarError Variable $return is not defined
" bedeutet, dass eine Variable benutzt wird, die vorher nicht initialisiert wurde. In diesem Fall wird$return
als Array verwendet -$return['type'] = $info['mime'];
-, ohne vorher definiert worden zu sein (siehe hier).
Dies sind nur drei Beispiele dafür, wie im Code unsauber gearbeitet wurde. Keiner dieser Fälle führt zu einem kritischen Fehler oder zum Abbruch des Skripts. Alle drei machen den Code aber konfuser und schlechter wartbar und verlangen dem Interpreter mehr ab als nötig. Ich denke, gerade bei größeren Projekten sollte auf entsprechend sauberen Code geachtet werden; hier kann Phan
als (zusätzliches) Tool für die QA im Continuous Integration-Prozess durchaus sinnvoll sein.
Bonus: php7cc
Stefan hat mich auf php7cc
aufmerksam gemacht. Dieses Tool untersucht PHP-Projekte auf ihre Kompatibilität mit der neuen 7er Version. Es versucht also, Befehle zu finden, deren Verhalten sich in PHP 7 geändert hat oder die zu fatal errors führen. Eine Liste solcher Befehle findet sich hier.
Der Einfachheit halber habe ich mir php7cc
als PHAR-Paket heruntergeladen und ausgeführt:
<blockquote>
<p>Line 30: Function argument(s) returned by "func_get_args" might have been modified
func_get_args();
[snip]
Dieser Hinweis fand sich in vielen Dateien des Projekts. Und tatsächlich hat sich hier etwas geändert:
The func_get_arg() and func_get_args() functions will no longer return the original value that was passed to a parameter and will instead provide the current value (which might have been modified). For example
function foo($x) {
$x++;
var_dump(func_get_arg(0));
}
foo(1);
will now print "2" instead of "1".
Mit Hilfe von php7cc
lässt sich also schon frühzeitig prüfen, ob ein Skript unter PHP 7 ohne Probleme laufen sollte, oder ob Fehler auftreten können. Eine Garantie, dass alles zu 100% funktioniert, ist das zwar nicht. Aber mit diesen Hinweisen lassen sich Programme in den meisten Fällen so anpassen, dass sie sowohl unter PHP 5.x als auch PHP 7 laufen.
Aufgrund der (Geschwindigkeits-)Vorteile von PHP 7 dürften viele über den Umstieg auf diese Version nachdenken. Selbst kleinere Webhoster bieten einen Wechsel bereits heute schon an. Allerdings gibt es beim Upgrade einiges zu beachten. Die vorgestellten Tools helfen dabei, problematischen Code zu finden und zu bereinigen. Ob sie nun manuell ausgeführt oder in die Build-Toolchain eingebaut werden, hängt vom jeweiligen Projekt ab. Interessant sind die Einsichten aber allemal.
Kommentare
Ansicht der Kommentare: Linear | Verschachtelt