PHP-Project-Checker
PHP-Project-Checker-Tutorial von Daniel Schwamm (29.01.2011 - 12.06.2011)
Inhalt
Wie aus meinen Tutorien ersichtlich bin ich begeisterter Delphi-Programmierer.
Aus beruflichen Gründen arbeite ich inzwischen allerdings mehr mit PHP. Diese
Script-Sprache ist auch nicht übel. Ziemlich umfangreich. Und verflixt
schnell, insbesondere für eine Interpreter-Sprache.
Vermisst habe ich bei PHP jedoch stets eine ähnlich leistungsstarke
Entwicklungsumgebung, wie sie Delphi bietet. Und um diesem Faktum
entgegenzuwirken, zimmerte ich mir meine eigene Programmieroberfläche
zusammen - den PHP-Project-Checker (PPC).
"PHP-Project-Checker": Eine kleine Programmierumgebung für PHP-Projekte mit
recht umfangreichen Syntax-Check, integriertem Webbrowser, Web-Seiten-Validierung
und Such-Optionen über alle Projekt-Dateien hinweg.
- Einsatz in der populären XAMPP-Umgebung möglich
- Erlaubt die Verwaltung vollständiger PHP-Projekten
- Besitzt einen integrierter Editor mit Zeilenanzeige
- Bietet einen integrierter Webbrowser, zur direkten Anzeige der Ergebnisse
- Erlaubt einen Syntax-Check über aller Projekt-Dateien ("Lint")
- Erkennt undefinierte Funktionen
- Erkennt unbenutzte Funktionen
- Erkennt undefinierte Variablen
- Erkennt unbenutzte Variablen
- Erkennt Funktionsaufrufe mit falscher Parameter-Anzahl
- Ermöglicht die Suche nach Texten über alle Projekt-Dateien hinweg
- Bietet einen direkten Aufruf der phpMyAdmin-Oberfläche an
- Bietet direkte Aufrufe zur W3C Markup Validation, W3C CSS Validation und JavaScript-Validation
- Bietet verschiedene Fenstergrössen, um Web-Pages bei wechselnden Auflösungen betrachten zu können
- Der Delphi-Source ist komplett freigegeben und erweiterbar
PPC benötigt eine PHP-Umgebung wie z.B. XAMPP (Paket mit Apache, MySQL,
PHP und Perl), um funktionieren zu können. Wichtig ist, dass sich PHP-Scripts
über einen lokalen (Apache-)Webserver aufrufen lassen.
Einige kleine Voreinstellungen sind nötig, um PPC einsetzen zu können.
Standardmässig verortet PPC seine PHP-Projekte im Ordner "HOMEDIR/projects",
wobei "HOMEDIR" der Arbeitsordner von PPC ist. Um dort PHP-Scripts ausführen
zu können, muss die "http.conf" des Apaches entsprechend angepasst werden,
z.B. so:
00001
00002
00003
00004
00005
00006
[...]
<IfModule alias_module>
Alias /projects c:/programme/phpprojectchecker/projects
[...]
</IfModule>
[...]
Nach dem Start von PPC muss einmalig die Basis-URL der PPC-Projekte eingetragen
werden. PPC merkt sich die URL in seiner INI-Datei. Mehr ist zur Einrichtung
nicht erforderlich.
Basis-URL für PPC-Projekte: Angabe der URL, unterhalb der die PPC-Projekte
abgelegt sind.
Über den Menü-Punkt "File|Project new" wird ein neues Projekt begonnen.
Es öffnet sich ein Datei-Dialog, über den in den gewünschten Ordner
navigiert wird. Man gibt einen Projekt-Namen an und drückt speichern.
Anschliessend werden dem Projekt eventuell bereits bestehende Dateien
hinzugefügt (über den "+"-Button), oder aber neue Dateien generiert.
Bestehende Dateien zum Projekt hinzufügen: Die Datei "service.php" wird dem
Project "xxx.ppc" hinzugefügt. "global.php", "index.php" und "service.php" sind
bereits in die Projekt-Liste aufgenommen worden.
Die Projekt-URL muss eventuell um den/die Unterordner ergänzt werden,
in denen sich die Script-Dateien befinden.
Über Radioboxen kann angegeben werden, ob die gerade geladene Editor-Datei
gestartet werden soll, oder eine "zentrale" PHP-Datei wie z.B. "index.php",
der man zusätzliche CGI-Parameter zuordnen kann, die bei Aufruf automatisch
an den Projekt-URL-Pfad angehängt werden.
Um die Web-Site zu starten, genügt ein Druck auf die F9-Taste bzw. der
Aufruf des Menüpunkts "Start|Run". Dadurch wird die Projekt-URL an den
integrierten Webbrowser übergeben und die Seite letztlich angezeigt.
Zentral-Script mit CGI-Parametern: Bei diesem Projekt wird über F9 das
Zentral-Script "index.php" mit CGI-Parameter aufgerufen. Die vollständige
URL lautet in diesem Fall: "http://localhost/projects/test/index.php?id=1234"
Fehler in PHP-Scripts lassen sich häufig erst zur Laufzeit erkennen.
Der PHP-Parser des Web-Servers wirft in diesem Fall eine Fehlermeldung aus,
die - für alle sichtbar - auf der Webseite ausgegeben wird, wie im folgenden
Beispiel zu sehen ist:
Der PHP-Project-Checker erkennt solche Laufzeitfehler. Wird der integrierte
Web-Browser verlassen, lassen sich alle PHP-Parser-Fehler in der
Debug-Liste finden. Ein Doppel-Klick auf einen solchen Eintrag führt
direkt zur ermittelten Fehlerstelle im Quellcode.
Der PHP-Project-Checker verfügt über recht mächtige Syntax-Check-Funktionen.
Diese erlauben es, Fehler in den Scripts schon vor der Ausführung zu finden.
Das hat den Vorteil, dass dabei auch Stellen im Projekt geprüft werden,
die zur Laufzeit nur selten abgerufen werden oder schwierig abzurufen sind
(wodurch sich dort Fehler unter Umständen sehr lange vor ihrer Entdeckung
"verbergen" können).
Der Syntax-Check des PPC wird durch STRG+F9 gestartet bzw. durch Aufruf des
Menüpunkts "Start|Syntax-Check". Es öffnet sich daraufhin ein Dialog, der die
Einstellung diverser Optionen erlaubt, die die Prüfroutine beeinflussen.
Syntax-Check-Dialog: Der Syntax-Check eines Projekts kann über
verschiedene Optionen gesteuert werden. Für den PHP-Lint-Check ist es
notwendig, dass zuvor der vollständige Pfad auf die "php.exe" der
XAMPP-Umgebung angeben wird.
Je nach Grösse des Projekts kann der Syntax-Check durchaus einige Minuten
lang dauern. Insbesondere der PHP-Lint-Check benötigt relativ viel Zeit,
weshalb es sich empfiehlt, ihn nur in Einzelfällen zu aktivieren. Meistens
dürfte der Syntax-Check dadurch innerhalb weniger Sekunden erfolgt sein.
Die gefundenen Warnungen und Fehler werden anschliessend in der
Debug-Liste aufgeführt. Wie bei den gefundenen Laufzeitfehlern (siehe weiter
vorne) bewirkt ein Doppel-Klick auf einen solchen Eintrag eine direkte
Navigation zur Problemstelle im Quellcode.
Syntax-Check-Meldungen: PPC hat einen falschen Funktionsaufruf gefunden,
mehrere unbenutzte Variablen, eine unbekannte Funktion und eine unbenutzte Funktion.
Die Suche nach Textstellen über alle Projekt-Dateien hinweg wird aktiviert
über die Taste F4 bzw. den Menüpunkt "Edit|Search Project". Die letzten
20 Suchbegriffe merkt sich der PPC in seiner INI-Datei.
Suche-Dialog: PPC hat den Suchbegriff "function" insgesamt viermal
gefunden, nämlich zweimal in der Datei "html.php" und zweimal in der Datei
"index.php".
Wie oben beschrieben, wird der Syntax-Check durch Druck von Taste F9
bzw. Aufruf des Menüpunkts "Start|Syntax Check" und anschliessender
Bestätigung des Syntax-Check-Dialogs in Gang gesetzt. Wir sehen uns jetzt
einmal an, was danach programmtechnisch exakt passiert.
Zu Beginn wird die Prozedur "syntax_check" aus der Unit "syntax_check_u.pas"
aufgerufen. Sie initialisiert eine Reihe von Stringlisten und durchläuft danach
in einer Schleife alle Dateien eines Projekts, um sie auf ihre Syntax hin zu
analysieren. Dabei werden die Stringlisten gefüllt und am Schluss ausgewertet.
Die Ergebnisse dieser Auswertung landen zuletzt in der Debug-Liste der
Haupt-Form.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
procedure tsyntax_check_f.syntax_check;
var
funcs_all_sl,funcs_project_sl,
funcs_unused_sl,funcs_unknown_sl,
func_calls_sl:tstringlist;
fn,long_fn,php_src,full_src,err,func_name:string;
r,x,y:integer;
prj_c:integer;
begin
screen.cursor:=crhourglass;
funcs_all_sl:=tstringlist.Create;
funcs_project_sl:=tstringlist.Create;
funcs_unused_sl:=tstringlist.Create;
funcs_unknown_sl:=tstringlist.Create;
func_calls_sl:=tstringlist.Create;
try
//save project an editor
main_f.debug_lb.clear;
main_f.src_save;
main_f.project_save(main_f.project_fn);
//scan all functions of all project files
screen.cursor:=crhourglass;
prj_c:=main_f.project_lb.items.count;
for r:=0 to prj_c-1 do
begin
fn:=main_f.project_lb.items[r];
long_fn:=main_f.long_fn(main_f.project_lb.items[r]);
main_f.status_set(
'Syntax-Check: ['+inttostr(r+1)+'/'+inttostr(prj_c)+'] '+
'Scan functions of '+long_fn+' ...'
);
application.ProcessMessages;
//scan and add all functions-headers to funcs_project_sl
fn_funcs_sl_add(long_fn,funcs_project_sl);
end;
//assign result to project-functions-listbox
main_f.project_functions_lb.items.assign(funcs_project_sl);
main_f.lb_scrollwidth_set(main_f.project_functions_lb);
//assign project-functions to unused-function-listbox
//kill later found used functions from this stringlist
funcs_unused_sl.assign(funcs_project_sl);
//all possible functions: add php-functions to project-functions
funcs_all_sl.assign(funcs_project_sl);
for r:=0 to main_f.php_functions_lb.items.count-1 do
begin
funcs_all_sl.add(main_f.php_functions_lb.items[r]+' - PHP');
end;
//check syntax, functions and variables of all project-files
for r:=0 to prj_c-1 do
begin
screen.cursor:=crhourglass;
fn:=main_f.project_lb.items[r];
long_fn:=main_f.long_fn(main_f.project_lb.items[r]);
main_f.status_set(
'Syntax-Check: ['+inttostr(r+1)+'/'+inttostr(prj_c)+'] '+
'Check '+long_fn+' ...'
);
application.ProcessMessages;
//php-lint-syntax-check
if lint_chb.Checked then
syntax_check_php_lint_u.php_lint(long_fn);
//no more to do?
if not(
functions_unknown_chb.checked or
functions_unused_chb.checked or
variables_unknown_chb.checked or
variables_unused_chb.checked
) then continue;
//isolate php-parts from source
php_src:=php_isolate_fn(long_fn,full_src,err);
//check functions
func_calls_sl.clear;
syntax_check_funcs_u.check(
fn,full_src,php_src,
funcs_all_sl,
funcs_project_sl,
funcs_unused_sl,
funcs_unknown_sl,
func_calls_sl
);
//check variables
syntax_check_vars_u.check(
fn,
full_src,
php_src,funcs_all_sl,
func_calls_sl
);
end;
//add unknown functions to debug-listbox
if functions_unknown_chb.Checked then
begin
for r:=0 to funcs_unknown_sl.Count-1 do
begin
fn:=trim(main_f.delim_param_get(funcs_unknown_sl[r],0,'|','0'));
y:=strtoint(trim(main_f.delim_param_get(funcs_unknown_sl[r],1,'|','0')));
x:=strtoint(trim(main_f.delim_param_get(funcs_unknown_sl[r],2,'|','0')));
func_name:=trim(main_f.delim_param_get(funcs_unknown_sl[r],3,'|',''));
main_f.debug_add(_db_warning,fn,y,x,func_name,'','UNKNOWN function?');
end;
end;
//add unused functions to debug-listbox
if functions_unused_chb.Checked then
begin
for r:=0 to funcs_unused_sl.Count-1 do
begin
func_name:=trim(main_f.delim_param_get(funcs_unused_sl[r],0,'-',''));
fn:=trim(main_f.delim_param_get(funcs_unused_sl[r],1,'-',''));
main_f.debug_add(_db_warning,fn,0,0,func_name,'','UNUSED function?');
end;
end;
main_f.status_set(
'Syntax-Check: ['+inttostr(prj_c)+'/'+inttostr(prj_c)+'] '+
'Okay, all files of project checked.'
);
finally
func_calls_sl.free;
funcs_all_sl.Free;
funcs_unknown_sl.Free;
funcs_unused_sl.Free;
funcs_project_sl.Free;
main_f.lb_scrollwidth_set(main_f.debug_lb);
screen.cursor:=crdefault;
end;
end;
Zuerst werden mit dem PHP-Project-Checker folgende String-Listen erzeugt:
- funcs_all_sl : Enthält alle PHP- and Projekt-Funktionen.
Format je Eintrag: function - filename
- funcs_project_sl: Enthält alle Projekt-Funktionen.
Format je Eintrag: function - filename
- funcs_unused_sl : Enthält alle unbenutzten Projekt-Funktionen.
Format je Eintrag: function - filename
- funcs_unknown_sl: Enthält alle unbekannten Projekt-Funktionen.
Format je Eintrag: filename|y|x|function
- func_calls_sl : Enthält alle Funktionsaufrufe einer PHP-Datei.
Format je Eintrag: y|x|function
In der ListBox "main_f.project_lb" der Haupt-Form sind alle Dateinamen der
Projekt-Dateien aufgeführt. Diese werden in einer Schleife einzeln
abgerufen und an die Funktion "fn_funcs_sl_add" übergeben. Dabei
wird die Stringliste "funcs_project_sl" gefüllt, die danach alle
Funktionen des Projekts enthält, jeweils mit ihren Parametern und ihrer
Quelldatei. Das sieht dann etwa so aus:
00001
00002
00003
00004
html_content_get() - html.php
html_get() - html.php
tst_xxx($a) - index.php
tst($a) - index.php
Die Stringliste "funcs_unused_sl" übernimmt die Werte von "funcs_project_sl".
Wird bei der weiteren Analyse der Aufruf einer Funktion gefunden, so wird
diese aus "funcs_unused_sl" entfernt. Die Funktionen, die bis zuletzt übrig
bleiben, werden im Projekt offenbar nicht benutzt.
Auch die Stringliste "funcs_all_sl" übernimmt die Werte von "funcs_project_sl".
In einer Schleife werden darüber hinaus sämtliche PHP-Funktionen ergänzt, die
in der ListBox "main_f.php_functions_lb" fix vorgegeben sind. Da diese
"eingebauten" PHP-Funktionen keiner bestimmten PHP-Datei zugeordnet werden
können, bekommen sie als Dateiangabe einfach nur die Kennung "PHP" zugewiesen.
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
html_content_get() - html.php
html_get() - html.php
tst_xxx($a) - index.php
tst($a) - index.php
abs - PHP
acos - PHP
acosh - PHP
addcslashes - PHP
addslashes - PHP
and - PHP
array - PHP
array_change_key_case - PHP
array_chunk - PHP
[...]
zend_thread_id - PHP
zend_version - PHP
Zuletzt stehen in der Stringliste "funcs_all_sl" alle
verwendbaren, weil definierten Funktionen eines Projekts. Wird daher bei der
weiteren Analyse des PHP-Quellcodes ein Funktionsaufruf gefunden, der in dieser
Liste nicht aufgeführt ist, dann handelt es sich offenbar um eine undefinierte,
sprich unbekannte Funktion.
Nun werden in einer weiteren Schleife erneut alle Projekt-Dateien durchlaufen.
Ist die PHP-Lint-Option gesetzt, werden die Dateinamen an die Funktion
"syntax_check_php_lint_u.php_lint" übergeben. Hier erfolgt der Syntax-Check
der Datei mittels der "php.exe" der XAMPP-Umgebung. Gefundene Fehler werden
in der Debug-Liste der Haupt-Form eingetragen.
Anschliessend wird der Quellcode der Datei über die Funktion "php_isolate_fn"
derart verarbeitet, dass dort alle Bestandteile herausgefiltert werden, die
sich nicht innerhalb eines PHP-Start-Tags ("<?") und PHP-End-Tags ("?>")
befinden. Und auch der übrig gebliebene PHP-Teil wird durch "Ausmarkierung"
von z.B. String-Inhalten noch weiter dahin gehend modifiziert, dass er einer
leichteren (Syntax-)Analyse zugänglich wird. Ein Beispiel mag dies
verdeutlichen:
Aus der PHP-Datei ...
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
HTML-Text
<?php
include '/lib/globals.inc';
include '/lib/string.inc';
function tst($a,$b){echo 'tst:test';};
echo 'Test';
?>
und noch mehr HTML-Text
... wird:
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
.........
<?php
include .................';
include ................';
function tst($a,$b){echo .........';};
echo .....';
..
... .... .... .........
Wie man sieht, wird der Original-Code quasi auf die PHP-Funktionsdefinitionen,
die PHP-Funktionsaufrufe und die PHP-Variablen reduziert, alles andere wird
durch Punkte überschrieben. Das Parsen dieses Codes mittels der Funktionen
"syntax_check_funcs_u.check" und "syntax_check_vars_u.check" nach Syntax-Fehlern
ist natürlich ungleich einfacher, als das Parsen des kompletten Original-Codes.
Nachdem alle Projekt-Dateien analysiert wurden, wird die Stringliste
"funcs_unknown_sl", also die Liste alle unbekannten Funktionen, durchlaufen
und alle Einträge darin an die "main_f.debug_add"-ListBox übergeben.
Das gleiche Verfahren wird anschliessend auch auf die Stringliste
"funcs_unused_sl" angewendet, also auf die Liste aller unbenutzten Funktionen.
Jetzt müssen nur noch die verwendeten Stringlisten aus dem Speicher geräumt
werden und der Syntax-Check des Projekts ist abgeschlossen.
"Pre-Debugger" für PHP-Projekte findet man im Web nicht. Nun ja, zumindest ich
nicht. Vermutlich geht es also anderen ähnlich. Wenn diese Suchenden es aber bis
hierher geschafft haben und damit wohl zu jenen gehören, die in PHP programmieren
und sich besser fühlen würden, wenn sie ihre Projekte hin und wieder "General-Überprüfen"
lassen können, dann mögen sie sich gerne meinen PHP-Project-Checker downloaden.
Klar, das Programm ist recht spartanisch. So wäre es z.B. sinnvoll, bei der Prüfung
auf "unnötige Funktionen" bestimmte Ordner ausschliessen zu können. Bindet man
nämlich sonst umfangreichen PHP-Bibliotheken in ein Projekt ein, verwendet von dort
aber wie üblich nur einen Bruchteil des Funktionsumfangs, dann werden bei jedem
Syntax-Check eventuell Hunderte von "Warnings" wegen unbenutzter Funktionen generiert.
So etwas verdirbt mir jedenfalls nicht den Spass an meinem Proggy. Ist halt nichts
Hundertprozentiges. But who cares?
"PHP-Project-Checker" wurde mit Delphi 7 programmiert. Der komplette Source und die
ausführbare EXE sind alle in diesem ZIP-Archiv verpackt:
PHP-Project-Checker.zip (ca. 120 kB)