search
subnavi
Werbung

Guter Code

Frage: Halte Code links. Verwende Wächter statt Schachtel-if

Antwort von Kristian Köhntopp:

In der strukturierten Programmierung erzeugt man Schleifen ohne Schleifenkurzschlüsse mit continue oder break und Funktionen mit genau einem Funktionsausgang durch return . Dadurch entsteht häufig Code mit sehr vielen geschachtelten Abfragen.

Meistens versucht man vor der eigentlichen Nutzlast einer Schleife oder einer Funktion eine Reihe von Vorbedingungen zu testen, die für den erfolgreichen Einsatz der Nutzlast sichergestellt sein müssen. Der generierte Code sieht dann wie folgt aus:

if (vorbedingung) {
  if (vorbedingung2) {
    if (vorbedingung3) {
      doit(); // Nutzlast
    } else {
      handle_error3();
  } else {
    handle_error2();
  }
} else {
  handle_error();
} 

Dies ist sehr schwer zu lesen und zu verstehen, weil der eigentliche Zweck der Funktion tief geschachtelt und sehr unübersichtlich versteckt ist. Geübte Programmierer verwenden stattdessen absichtlich die in der strukturierten Programmierung verpönten Schleifenkurzschlüsse und frühzeitigen Funktionsausstiege in einer bestimmten Form, um besser lesbaren Code zu schreiben:

if (!vorbedingung)
  handle_error();

if (!vorbedingung2)
  handle_error2();

if (!vorbedingung3)
  handle_error3();

doit; // Nutzlast 

Dieser Code skaliert sich besser: Egal wieviele Vorbedingungen zu erfüllen sind - die Einrücktiefe bleibt konstant. Außerdem steht der normale Fall jetzt in Falllinie und der Fehlercode ist als Ausnahme eingerückt und zur Seite gedrängt.

Ein praktisches Beispiel: Der folgende Code zum Durchlesen eines Verzeichnisses

$d = dir("d:/logfiles");
while($entry=$d->read()) {
  if (($entry != ".") && ($entry != "..")) {
    doit();
  }
}
$d->close(); 

wird durch die Umstellung zu

$d = dir("d:/logfiles");
while ($entry = $d->read()) {

  if ($entry == "." or $entry == "..")
        continue;

  doit();
}
$d->close(); 

Frage: Von HTML zu PHP: Schreibe Formularverarbeitungen in Normalform

Antwort von Kristian Köhntopp:

Da jede HTML-Datei auch ein gültiges, bedeutungsgleiches PHP-Programm ist, existiert ein einfacher und systematischer Weg, um von einem HTML-Formular zu einem PHP-Programm zu kommen, das dieses Formular bearbeitet. Hält man sich an diesen Weg, wird das resultierende Formular zugleich ein bestimmtes Format haben.

Der erste Schritt ist die Entwicklung eines reinen HTML-Formulares, das die zu verarbeitenden Daten abfrägt.

In einem zweiten Schritt wird man aus diesem Formular ein sogenanntes Affenformular machen, indem man dafür sorgt, dass das Formular sich selbst aufruft und seine alten Werte immer wieder einsetzt. Es heißt Affenformular, weil eine Million Affen dieses Formular eine Million mal aufrufen können, ohne etwas zu bewirken.

<form action="<?php echo $_SERVER['PHP_SELF']; ?>">
<input type="text"
       name="textfeld"
       value="<?php if (isset($_REQUEST['textfeld'])) echo htmlspecialchars($_REQUEST['textfeld']); ?>">
<br>
<input type="submit"
       name="do_form_x"
       value="Ausführen">
</form> 

Das Affenformular enthält an zwei Stellen PHP-Code: Bei 'action' wird der Dateiname des Skripts eingetragen. Durch Einsetzen von $_SERVER['PHP_SELF'] (oder vor PHP 4.1.0 $HTTP_SERVER_VARS['PHP_SELF'] ) an Stelle des Dateinamens kann das Formular seine eigene Adresse bestimmen und sich somit selbst aufrufen. Und außerdem werden die Defaultwerte der verschiedenen Formularfelder durch PHP wieder mit den Ausgangswerten belegt. Wenn das Formular also abgesendet wird und die Eingabewerte nicht korrekt sind, wird das Formular wieder dargestellt und die alten Werte werden als Standardwerte wieder eingesetzt. Damit kann der Anwender sie korrigieren, ohne sie noch einmal eingeben zu müssen.

Der dritte Schritt besteht dann darin, eine Reihe von Funktionen zu codieren, die die Werte aus diesem Formular validieren (siehe formular-verarbeitung ) und ggf. die passenden Fehlermeldungen erzeugen, die dann an den geeigneten Stellen wieder in das Formular eingesetzt werden. Das Endresultat muss dann eine Variable haben, an der entscheidbar ist, ob der Formularinhalt gültig ist oder nicht.

<?php

// Funktion zum Drucken von Fehlermeldungen
function errmsg($msg) {
   ?>
   <font color="#ff0000"><b><?php print nl2br($msg) ?></b></font>
   <?php
}

// Überprüft Eingabewerte für $textfeld auf Korrektheit.
function validate_textfeld($val) {
   $msg = "";
   if (strlen($val) < 3)
      $msg .= "Die Eingabe muss mindestens 3 Zeichen lang sein.\n";

   if (preg_match("/\s/", $val))
      $msg .= "Die Eingabe darf keine Leerzeichen "
             ."oder Tabulatoren enthalten.\n";

   return $msg;
}

// Für jedes Formularfeld werden nun ein oder mehrere
// Validatoren aufgerufen und das Ergebnis der Überprüfung
// gemerkt.
$valid = true;
if (isset($_REQUEST["textfeld"])) {
   $error["textfeld"] = validate_textfeld($_REQUEST["textfeld"]);
   if ($error["textfeld"] != "")
      $valid = false;
}

?>
<form action="<?php print $_SERVER["PHP_SELF"]; ?>">
<input type="text"
       name="textfeld"
       value="<?php print htmlspecialchars($_REQUEST["textfeld"]); ?>"><br>
<?php
// Ggf. Fehlermeldung ausdrucken.
if ($error["textfeld"] != "")
   print errmsg($error["textfeld"]);
?>
<input type="submit"
       name="do_form_x"
       value="Ausführen">
</form>
<hr>
<?php if ($valid and isset($_REQUEST["do_form_x"])) { ?>
<!-- Nutzlast -->
<?php } ?> 

Schließlich kann man im vierten Schritt daran gehen, die validierten Formulardaten einer Bearbeitungsfunktion zu übergeben und diese Funktion die eigentliche Arbeit machen zu lassen. Das Ergebnis dieser Verarbeitung wird in den meisten Fällen unterhalb des Formulars dargestellt werden, so dass man mit den Eingabedaten oben gleich die nächste Abfrage starten kann.

An dieser Stelle hat man ein funktionierendes Formular und zwei Baustellen, an denen man weiterarbeiten kann: Zum einen muss man sich Gedanken darüber machen, welche Funktionalität aus diesem Formular an anderer Stelle so auch wieder verwendet werden könnte und sollte sich überlegen, ob man nicht eine Klasse schreiben möchte, in die man diese Funktionalität anwendungsunabhängig kapseln könnte. Auf diese Weise wird man sich Schritt für Schritt eine kleine Bibliothek an Funktionen zulegen, die in vielen anderen Projekten ebenfalls Anwendung finden kann.

Zum anderen muss man sich überlegen, ob man die Verkettung von Bildschirmen, Knöpfen und Aktionen nicht ein wenig globaler lösen kann und welche Struktur man seinem Programm dafür geben wird.

Frage: Trenne Aussehen und Inhalt

Antwort von Martin Jansen:

Es ist immer günstig Aussehen und Inhalt zu trennen.

Die gängigsten Template-Systeme in PHP sind:

Frage: Was sind eigentlich if-Schleifen?

Antwort von Johannes Frömter:

Sogenannte "if-Schleifen" sind anscheinend nicht auszurottende Hirngespinste. Es gibt schlicht und einfach keine "if-Schleifen", weder in PHP noch in irgendeiner anderen Programmiersprache.

Es gibt if -Anweisungen, die einen Codeteil abhängig von einer Bedingung ausführen oder nicht, und es gibt for -, while - und do-while -Schleifen, die einen Codeteil mehrmals ausführen.

Schleifen und Bedingungen können selbstverständlich verschachtelt werden, d.h. in einer Schleife kann eine Bedingung abgefragt werden, oder abhängig von einer Bedingung kann eine Schleifenkonstruktion ausgeführt werden. Aber "if-Schleifen" erhält man deswegen trotzdem nicht.

Frage: Anführungzeichen oder Hochkomma?

Antwort von Johannes Frömter:

In PHP können Strings sowohl von normalen Anführungszeichen ( " ) als auch von Hochkommata ( ' , auch genannt Apostroph, einfache Anführungszeichen, single quote, ASCII 39) eingefasst werden. Es gibt jedoch einen grundlegenden funktionalen Unterschied zwischen diesen beiden Zeichen: der Bereich zwischen Anführungszeichen wird von PHP ausgewertet, während alles, was zwischen Hochkommata steht, von PHP schnell übergangen wird.

Diese Tatsache kann man natürlich nutzen, da man weniger escapen muss (z.B. bei Windows-Pfadangaben, s.u.), außerdem werden Strings in Hochkommata schneller verarbeitet. Allerdings werden dann Variablen nicht durch ihren Wert ersetzt ("interpoliert"), aber das kann ja durchaus auch gewollt sein. Die einzigen Zeichenfolgen, die innerhalb von Hochkommata eine besondere Bedeutung haben, sind \\ (ergibt einen einzelnen Backslash) und \' (ergibt ein geschütztes Hochkomma). Beispiele:

echo 'Micro$oft';  // ergibt: Micro$oft
echo "Micro$oft";  // ergibt: Micro + Inhalt der Variable $oft
echo "Micro\$oft"; // ergibt: Micro$oft

echo 'c:\temp';    // ergibt: c:\temp
echo "c:\temp";    // ergibt: c: + Tabulator + emp
echo "c:\\temp";   // ergibt: c:\temp
echo 'c:\\temp';   // ergibt: c:\temp

echo 'Kein Hochkomma: \x27';  // ergibt: Kein Hochkomma: \x27
echo "Ein Hochkomma:  \x27";  // ergibt: Ein Hochkomma: '
echo 'Ein Hochkomma: \'';     // ergibt: Ein Hochkomma: '

echo "<input name='foo' value='$bar'>";  // gültiges HTML 

Die Benutzung von Hochkomma statt Anführungszeichen wie im letzten Beispiel ist in HTML erlaubt (siehe die HTML 4.01 bzw. XML 1.0 Spezifikationen).

Frage: Mein Script funktioniert nicht mit Browser XY!

Antwort von Johannes Frömter:

PHP-Scripte werden bekanntlich auf dem Server ausgeführt und liefern in der Regel schlichte HTML-Dokumente an den Browser. PHP selbst kann also nichts dafür, wenn ein Script mit einem bestimmten Browser nicht "funktionieren" sollte. Die Fehlerursache ist vielmehr im Script selbst zu suchen, denn dieses produziert sehr wahrscheinlich ungültigen HTML-Quellcode.

Ein beliebter Fehler ist z.B. falsche Groß-/Kleinschreibung - Variablen in PHP sind case-sensitive, d.h. es wird zwischen Groß- und Kleinschreibung unterschieden! Folgendes HTML-Formular ist dadurch fehlerhaft geworden. Je nachdem, wie tolerant sich die einzelnen Browser verhalten, kann das Formular trotzdem funktionieren, oder es funktioniert eben nicht.

// PHP-Script (Verwendung von $_server statt $_SERVER):
<form action="<?php echo $_server['PHP_SELF']; ?>" method="post">

// HTML-Ergebnis:
<form action="" method="post">  // action ist leer! 

Am besten schickt man die Ausgabe des fraglichen Scriptes erst mal durch den W3C HTML Validation Service und merzt alle monierten Regelverstöße aus, bevor man Dritte mit dem (vermeintlichen) Problemscript behelligt.

Hilfe bei HTML-Problemen gibt es bei SELFHTML und in der Newsgroup de.comm.infosystems.www.authoring.misc . Browser-spezifische Dinge werden in der Gruppe de.comm.software.browser.misc diskutiert.

Frage: Schaden Kommentare der Performance?

Antwort von Richard Körber:

Kommentare und Einrückungen schaden nicht der Performance!

PHP4 überspringt alle Kommentare und überflüssigen Leerzeichen (zum Beispiel Einrückungsleerzeichen) bereits beim Parsen des Programms. Das geht selbst bei extrem umfangreichen Kommentaren vernachlässigbar schnell.

Zur Laufzeit macht es für PHP dann keinen Unterschied mehr, ob im Quelltext Kommentare standen oder nicht. Selbst in Schleifen wirken sich Kommentare nicht nachteilig auf die Performance aus.

Der minimale Zeitverlust beim Programmstart ist kein Grund, auf eine gute Kommentierung zu verzichten. Wer sein Script performanter machen möchte, sollte sich eher darauf konzentrieren, Schleifen zu optimieren, bevorzugt Variablen als Referenz zu übergeben oder schnellere Algorithmen zu finden. Mit einem PHP-Profiler lassen sich besonders zeitintensive Programmabschnitte schnell ausfindig machen.

Frage: Wie kann ich das Caching einer Seite verhindern?

Antwort von Kristian Köhntopp:

In HTTP 1.0 kann man das Caching einer Seite nur unvollständig steuern. Die ersten Webcaches haben die Lebensdauer von Seiten im Cache auf der Grundlage des Erzeugungsdatums geschätzt oder, wenn ein Expires -Header angegeben, diesen beachtet. Später ist der spezielle Header Pragma: no-cache eingeführt worden, um das Caching von Seiten durch Webcaches und Browser zu verbieten.

Erst mit HTTP 1.1 kann eine spezielle Cache-Steuerung hinzu, die zwischen privaten (browsereigenen) Caches und öffentlichen Caches unterschied. Über den besonderen Header Cache-Control kann man die Lebensdauer von Seiten in Caches steuern.

Microsoft kocht zusätzlich noch eine Spezialsuppe, indem sie für den MSIE 5.x spezielle Cache-Control Extensions definieren.

Um das Caching einer Seite zu erlauben, kann man den folgenden Code verwenden:

$expire = 15;  // Lebensdauer der Seite im Cache in Minuten

$exp_gmt = gmdate("D, d M Y H:i:s", time() + $expire * 60) ." GMT";
$mod_gmt = gmdate("D, d M Y H:i:s", getlastmod()) ." GMT";

// HTTP 1.0
header("Expires: " . $exp_gmt);
header("Last-Modified: " . $mod_gmt);

// HTTP 1.1
header("Cache-Control: public, max-age=" . $expire * 60); 

Um das Caching einer Seite auf private Caches zu begrenzen muss man Code wie diesen nehmen:

$expire = 15;  // Lebensdauer der Seite im Cache in Minuten

$mod_gmt = gmdate("D, d M Y H:i:s", getlastmod()) ." GMT";

// HTTP 1.0 kennt keine privaten Caches, also nix cachen
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . $mod_gmt);

// HTTP 1.1
header("Cache-Control: private, max-age=" . $expire * 60);

// MSIE 5.x special
header("Cache-Control: pre-check=" . $expire * 60, FALSE); 

Um das Caching einer Seite zu verhindern, ist der folgende Code passend:

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") ." GMT");
header("Cache-Control: no-cache");
header("Pragma: no-cache");
header("Cache-Control: post-check=0, pre-check=0", FALSE); 

Ein anderer Trick, mit dem man das Caching einer Seite gut verhindern kann, ist das Anhängen von Parametern an die URL einer Seite in der Form http://www.meinserver.de/bla.php?x=y oder das Einfügen von benutzerspezifischen Komponenten in die URL (in den Hostnamen, den Pfad oder den Dateinamen).

Frage: Wie erzeuge ich mit PHP einen Redirect auf eine andere Seite?

Antwort von Kristian Köhntopp:

Um einen Redirect zu erzeugen, muss man den HTTP-Header Location senden und dort die neue URL angeben. Zum Senden von HTTP-Headerzeilen verwendet man die PHP-Funktion header() . Diese Funktion kann nur dann verwendet werden, wenn PHP noch keinen HTTP-Body ausgegeben hat, wenn also weder Fehlermeldungen, Leerzeilen, Leerzeichen noch HTML ausgegeben worden sind.

// Redirect-Ziel
header("Location: http://www.ziel.de/zielseite.html"); 

Wichtig: RFC 2616 schreibt im Abschnitt 14.30 Location eine sog. absoluteURI vor, d.h. die Adresse muss mit http:// beginnen, relative Anweisungen à la "Location: index.html" sind nicht standardkonform! Manche Browser sind zwar so tolerant, relative Angaben zu verstehen und in der Lage, selbständig die absolute Adresse zu ermitteln, aber verlassen kann man sich darauf nicht; die PHP-Funktion fopen() z.B. scheitert an derart ungültigen Location-Angaben.

Frage: Wie kann ich mit PHP WAP-Seiten erzeugen?

Antwort von Richard Körber:

Zuerst sollte man ein eigenes Verzeichnis für die WAP-Seiten anlegen und dem Apache beibringen, auch Dateien mit der typischen WAP-Endung .wml von PHP generieren zu lassen. Dies geschieht zum Beispiel mit folgender .htaccess -Datei in dem WAP-Verzeichnis:

AddType application/x-httpd-php .php .wml
DirectoryIndex index.wml 

Schick ist, jetzt eine Subdomain wap auf dieses Verzeichnis verweisen zu lassen.

Es ist anschließend PHPs Aufgabe, mit der header() -Funktion einen korrekten Content-Type zu setzen. Ein Charset muss dabei angegeben werden! So könnte ein typisches WAP-PHP-Script beginnen:

<?php
  header("Content-Type: text/vnd.wap.wml;charset=iso-8859-1");
  print("\n\n<?xml version=\"1.0\"?>\n");
?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"> 

Darauf folgt der eigentliche WML-Content, in den man wie gewohnt PHP einbetten kann. Drei Dinge sind dabei wichtig:

    • WML ist eine XML-Sprache. Man muss penibelst darauf achten, dass der Code korrekt ist! Fehler in der Verschachtelung oder Schreibweise der Tags sind fatal.

      WAP lernt man zum Beispiel bei SELFWML oder bei WAP Basics .

    • Die von HTML bekannten Entities wie &ouml; funktionieren bei WAP nicht. Stattdessen verwendet man die ganz normalen Umlaute etc. Das kann unter Umständen recht ärgerlich werden, wenn man Funktionen geschrieben hat, die bereits solche Entities liefern. Laut WAP-FAQ sind aber auch Entities der Art &#246; erlaubt.

    • Handys haben recht wenig Speicherplatz. Die Seite sollte daher maximal 1400 Zeichen groß sein, damit alle Handys sie anzeigen können.

WAP erlaubt für Bilder ausschließlich das eigene Bildformat WBMP. Ein Konverter ist zum Beispiel im Paket von Deck-it enthalten. Bilder sollten sparsam verwendet werden und möglichst klein sein. Die Übertragung kostet Zeit, Geld und den sowieso schon knappen Speicherplatz auf dem Handy.

Cookies führen zwar zu keiner Fehlermeldung, aber man sollte sich nicht darauf verlassen, dass sie funktionieren!

Testen kann man die Seite mit WAP-Simulatoren, zum Beispiel Deck-it oder über verschiedene Online-Emulatoren. Letztendlich zählt aber nur eins: der Test mit dem echten Handy.

Antwort von Kerry W. Lothrop:

Das Verarbeiten von WML-Formularen funktioniert mit PHP fast wie bei HTML-Formularen, mit dem kleinen Unterschied, dass manche Browser Formulardaten als Unicode versenden. Die Funktion utf8_decode() kann verwendet werden, um die Sonderzeichen zu dekodieren.

Seit GD 1.8 kann PHP auch WBMP-Grafiken erstellen. Die relevanten Funktionen heißen imagewbmp() , jpeg2wbmp() und png2wbmp() .