Zur Zeit (Stand 2009) werden die Provider, die Perl unterstützen immer weniger. Ein guter Grund also, sich mit PHP zu befassen. Ein weiterer Grund ist der: PHP ist, im Gegensatz zu Perl, hauptsächlich dafür gedacht, Webanwendungen zu schreiben. Was mit Perl zu machen ist, hinsichtlich Besucherinteraktionen oder Datenbankanbindung im Internet, geht mit PHP auf jeden Fall, wobei sich dabei sogar einige Vereinfachungen ergeben, beispielsweise bei RFC-gerechten Formatierungen für Datumsangaben oder Cookies.
Dieser Artikel gibt meine ersten Schritte mit PHP wieder und ist daher ausnahmsweise in der "ich"-Person geschrieben. Anzumerken ist vielleicht noch, dass ich seit über 10 Jahren ungezählte CGI-Scripts in Perl geschrieben habe, die sich in der Praxis auch bewährt haben.
Mein erstes PHP-Script sieht so aus:
<?php phpinfo(); ?>
Den Dreizeiler habe ich phpinfo.php genannt, auch den Server meines Providers geladen und im Browser aufgerufen. Die Ausgabe zeigte mir, dass PHP installiert ist und einige Infos, die ich ersteinmal ausgedruckt und beiseite gelegt habe. Weitere erste Erkenntnisse: Das Script muss nicht ausführbar gemacht werden, es reichen die Berechtigungen wie sie für HTML-Dateien üblich sind. Ein PHP-Script wird nicht "an sich" ausgeführt wie ein Perl-Script, sondern PHP läuft als Preprozessor, der einen Parser besitzt, welcher den PHP-Code, der in die Datei eingebettet ist, ausführt. Es entfällt somit eine explizite Angabe des Script-Interpreters und ein HTTP-Header wird automatisch generiert, ohne dass dafür eine spezielle print()-Anweisung notwendig ist. PHP-Code kann also innerhalb einer PHP-Datei neben ganz normalen HTML-Code notiert werden, beginnt mit der Zeichenfolge <?php und endet mit ?>. Sofern diese Token am Anfang bzw. am Ende einer PHP-Datei notiert sind, wird die ganze Datei "ausgeführt", ansonsten nur das, was zwischen diesen Markierungen geschrieben steht.
... ist z.B. notwendig, wenn eine andere Zeichenkodierung erfolgen soll, als das im Default des Webservers vorgegeben ist, oder auch mal eine Umleitung (Location-Header, Redirect). Dafür gibt es in PHP die Funktion header(), die recht einfach bedient wird:
header("Content-Type: text/html; charset=UTF-8"); // Zeichenkodierung setzen
header("Location: /"); // Leitet zur Startseite der Domäne
header("Location: $_SERVER[SCRIPT_NAME]"); // Leitet auf sich selbst
Die header()-Funktion muss (sofern diese Funktion erforderlich ist), wie das auch in Perl-CGI's so üblich ist, aufgerufen werden, bevor irgendeine andere Ausgabe mit print oder echo gemacht wird. Das gilt in PHP-Scripts insbesondere auch dann, wenn die Datei HTML-Code enthält, der außerhalb von <?php ?> notiert ist! Wird die Funktion header() nicht explizit aufgerufen, entscheidet der PHP-Preprozessor selbst, welche Header gesetzt werden und auch die Zeichenkodierung. Soll eine von der Konfiguration abweichende Zeichenkodierung verwendet werden, ist die Funktion header() zu bemühen, das empfiehlt auch das w3-Org-Konsortium. Von einer in der .htaccess festgelegten Zeichenkodierung wie beispielsweise AddDefaultCharset UTF-8 .php lässt sich PHP nicht beeindrucken, so meine Erfahrung.
Wichtiger Hinweis: Im Gegensatz zu Perl, sendet der PHP-Interpreter einen HTTP-Header nur dann, wenn das Script als URI requestet wurde, also im Kontext mit dem Webserver. Hilfreich beim Entwickeln oder zum Kennenlernen elementarer Funktionen, ist es schonmal, ein Script auf der Kommandozeile aufzurufen, jedoch ist eine Zeile like Content-Type: text/html bei einem PHP-Script auch dann nicht zu sehen, wenn mit der header()-Funktion eigene Header definiert wurden. Für die Fehlersuche und Prüfen der HTTP-Header machen sich spezielle UserAgents (LWP-Library in Perl) oder Plugins für Browser (Firefox: Tramperdata) oder TCP-Dumps am Netzwerkinterface notwendig.
Die PHP-Funktion header() steht im direkten Dialog mit dem Webserver, Argumente dieser Funktion sind Header-Felder, welche der Webserver folgerichtig einbaut. Soll ein eigens geschriebenes Script aus der Hand gegeben oder produktiv eingesetzt werden, sind die Header-Ausgaben stets auf Korrektheit zu prüfen.
Gesendet wird Last-Modified ganz normal mit der Header-Funktion:
header("Last-Modified: Wed, 17 Feb 2010 18:47:39 GMT");
Im Gegensatz zu Perl muss bei der Ausgabe des Status 304 die HTTP-Version angegeben werden:
header("HTTP/1.1 304 Not Modified");
Ansonsten findet ein PHP Script den vom UserAgent im Request-Header mitgesendeten Last-Modified wie gehabt in der Umgebung:
$_SERVER[HTTP_IF_MODIFIED_SINCE] Last-Modified: Wed, 17 Feb 2010 18:47:39 GMT
Wenn Parameter (Benutzereingaben) zu erwarten sind, ist eine Kontrollstruktur unumgänglich, egal, ob das GET- oder POST-Parameter sind. Der Zugriff auf die Parameter ist in PHP einfach, dazu gibt es die superglobalen Arrays $_GET bzw. $_POST. Wenn beispielsweise ein Inputfeld mit dem name-Attribut msg ausgezeichnet wurde, findet sich der Input bei einem GET in der Variablen $_GET['msg'], bei einem POST in $_POST['msg']. Je nach Benutzereingabe ist ein PHP-Script zu steuern, das mache ich genauso wie in Perl mit einer Kontrollstruktur:
if(cgiParams()){
// ab hier Schlüssel-Parameter gezielt abfragen
// weitere if() und elseif()
if(isset($_GET['foo'])){
// z.B. eine AJAX-Response
header("Content-Type: text/xml; charset=UTF-8");
}
elseif(isset($_POST['bar'])){
// oder ein Location-Header
header("Location: /");
}
}
else{
// So sieht die Ausgabe ohne Parameter aus
header("Content-Type: text/html; charset=ISO-8859-1");
}
Betrachte meine Funktion cgiParams(), diese prüft, ob überhaupt Parameter vorliegen:
// sind CGI-Parameter im Spiel?
function cgiParams(){
return(isset($_SERVER['CONTENT_LENGTH']) || strlen($_SERVER['QUERY_STRING']));
}
Die Superglobale Variable $_GET ist praktisch IMMER gesetzt, fatal wäre es daher mit isset($_GET) zu prüfen, ob Parameter anliegen, weil damit in JEDEM Request ein TRUE zurückgegeben wird. Mit meiner Funktion frage ich daher direkt die CGI-Umgebungs-Variablen ab, die entweder bei einem POST oder bei einem GET mit Daten gefüllt sind (CONTENT_LENGTH bzw. QUERY_STRING).
Eine Eigenart von PHP besteht darin, dass es beim Abfragen einer Array-Variablen, für die kein Index gesetzt ist, eine Warnmeldung gibt, womit sich dann das error_log füllt. Es ist so, dass der Index 'QUERY_STRING' zwar immer gesetzt ist, 'CONTENT_LENGTH' hingegen nur bei einem POST. Um Fehlermeldungen vorzubeugen, wird die Funktion isset() eingesetzt, Beispiel siehe oben: isset($_GET['foo']).
Merke: Scripts mit einer Kontrollstruktur zum ParameterHandling müssen mit dem PHP-Token beginnen und auch enden; kein HTML außerhalb von <?php ?>.
Diese Variablen, in Perl im hash %ENV enthalten, findet der PHP-Programierer im Array $_SERVER. Untenstehendes einfaches Script gibt die komplette Umgebung aus:
<?php
header("Content-type: text/plain");
var_dump($_SERVER);
?>
Wie in Perl, gibt es auch eine Umgebung auf der Kommandozeile. In der CGI-Umgebung ist beispielsweise der Scriptname /umg.php in der Variablen $_SERVER[SCRIPT_NAME] oder auch in $_SERVER[PHP_SELF] zu finden.
Hier gibt es ein paar Unterschiede/Vereinfachungen bezogen auf Perl. Einfach ists in PHP, den Wert eines Cookies auszulesen, $sid = $_COOKIE['cookiename'] gibt diesen String ohne Umschweife zurück. Zum Setzen eines Session-Cookies muss in PHP nicht unbedingt händisch der Header "Set-Cookie:..." bemüht werden, das erledigen vorgefertigte Funktionen, Beispiel:
session_name($sessName); // Initialisierung einer Session und Array $_SESSION
session_start(); // Set-Cookie:... (Header an Webserver)
$id = session_id(); // gibt genau den zufälligen String zurück, der auch im Cookie als Wert gesetzt wurde
// hier haben wir die $id schon bevor der Cookie gelesen werden muss
header("Location: $_SERVER[SCRIPT_NAME]"); // Redirect auf sich selbst
Zum Erzeugen einer Session-ID muss in PHP nicht eine extra Funktion berufen werden, das erledigt bereits die erste Funktion obenstehend.
Was an Handarbeit jedoch noch bleibt für eine ordentliche Session, ist die Datenbankanbindung.
Gegenüber Perl gab es (bisher für mich) keine großartigen Aktionen zum Umdenken. Es wird ein Database-Handle erstellt, mit dem der Zugriff erfolgt und genauso wie in Perl gibt es die Möglichkeit Statements zu präparieren (pre-prepared Statements). Bis auf den Syntax bleibt also alles beim Alten.
// Datenbankanbindung
$db = @new mysqli($db_host, $db_user, $db_pass, $db_name);
if (mysqli_connect_errno()) {
die ('Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
// das @ vor dem new-Operator unterdrückt etwaige Warnmeldungen
// Eine etwas komfortablere Funktion zur DB-Anbindung
// ermöglicht ein Timeout und damit die Umleitung zu einer Fallback-Seite
// falls kein DB-Handle erstellt werden kann (NULL return)
function dbase(){
global $cfg; // Hostname, User, dbName, Passwort
$db = mysqli_init();
$db->options(MYSQLI_OPT_CONNECT_TIMEOUT, 3);
$db->real_connect($cfg['mysql']['host'], $cfg['mysql']['user'], $cfg['mysql']['pass'], $cfg['mysql']['base']);
if (mysqli_connect_errno()) return;
else return($db);
}
// prepared Statement als Beispiel, Platzhalter für das letzte Feld
$st = $db->prepare("INSERT INTO talk VALUES($ts, '$sid' ,?)");
$st->bind_param('s', $msg);
$st->execute();
// Einfaches Insert-Statement
$q = "INSERT INTO talk VALUES($ts, '$sid', '$msg')";
$db->query($q);
// einen Record auslesen
$q = "SELECT nick FROM talker WHERE sessionid='$id'";
$res = $dbh->query($q);
$row = $res->fetch_assoc(); // nur eine Zeile erwartet
$nick = $row['nick']; // im Kontext ein String
// Durch die Abfrage gehen mit einer Schleife
$result = $dbh->query("SELECT name, vname FROM address");
while ($row = $result->fetch_assoc()) {
$name = $row['name'];
$vname = $row['vname'];
}
Die Beispiele zeigen, dass eine DB-Anbindung mit PHP einer Anbindung mit Perl ganz ähnlich ist und mögen hier genügen.
Für Konfigurationsdateien like:
[section] parameter=value
gibt es in PHP eine komfortable Funktion: $ini = $ini = parse_ini_file($iniFile, true);. Mit dem zweiten Parameter true wird die komplette ini-Datei auf das Array $ini gelesen. Auf den Inhalt der ini-Datei () kann dann mit $value = $ini['section']['parameter'] recht einfach zugegriffen werden. Es gibt einen beachtenswerten Unterschied zu Perl, Modul Config::IniFiles, die PHP-Funktion meldet einen Fehler, wenn die values bestimmte Zeichen wie z.B. runde Klammern oder das Ausrufezeichen enthalten. Eine für PHP lesbare ini-Datei muss dann so aussehen:
[section] parameter="Werte mit bestimmten Zeichen wie (!)"
wobei die Quote-Zeichen von der PHP-Funktion entfernt werden, von der Perl-Funktion hingegen nicht. Zum Einstellen der Kompatibilität zu Perl muss ich mir noch was einfallen lassen.
Als Perl'er, der strict und my benutzt, musste ich hier ein klein wenig umdenken. Es gibt in PHP globale und Superglobale Variablen. Superglobale Variablen gelten auch in Unterfunktionen, globale Variablen, die scriptweit gültig sind, gelten in Unterfunktionen nur dann, wenn das Schlüsselwort global davor notiert ist:
$foo = 'bar'; // globale Variable, gilt scriptweit
test($foo); // Funktionsaufruf ist korrekt
function test($var){
echo $foo; // Fehlermeldung
global $foo;
echo $foo; // bar
echo $var; // bar (gibt aus, was in $var als Argument übergeben wurde)
}
Ansonsten ist es ja auch in Perl schon immer ein guter Stil, Funktionen mit übergebenen Parametern aufzurufen und die Rückgabe zu verwenden.
Vermutlich der Beginn einer längeren Freundschaft :-)
Kurz und knapp: Meine alte Apache Version habe ich deinstalliert und durch einen Apache 2.2 ersetzt. PHP habe ich auf meinem XP-Rechner in der Version 5.3.0 installiert. Als Editor benutze ich mein gutes altes TextPad, was es mir erlaubt, PHP Scripts innerhalb des Editors auszuführen, ohne die Kommandozeile bemühen zu müssen.
Last-Modified: Tue, 22 Jun 2010 19:51:17 GMT