Was? Eine Frechheit!

Na gut, die Überschrift ist natürlich ein wenig polemisch. Aber manchmal gibt es eben Momente, dass ich in die Tastatur beißen könnte. Man sucht und sucht einen Fehler, bis man irgendwann feststellt, dass es sich um ein Sprach-„Feature“ handelt — schon wieder.

Kennen Sie das auch von PHP? Dann treffen Sie hier vielleicht alte Bekannte wieder, über die Sie sich auch schon mal aufgeregt haben. Oder tue ich jemanden Unrecht und ein Beispiel stimmt gar nicht? Dann schreiben Sie mir. Ergänzungen und Anmerkungen sind willkommen unter sch..._php@heckmeck.de.

Wie gesagt: Oft wollte ich in die Tastatur beißen. Stattdessen habe ich dann diese Liste gepflegt. Selbstverständlich sind die Einträge alle parteiisch und voreingenommen. Ich persönlich programmiere lieber in Java und Perl, vielleicht finden Sie das ja auch Sch...

Einschränkungen

Die meisten Einträge beziehen sich auf PHP4. Mit PHP5 ist ja alles besser geworden, immerhin schreibt PHP-Guru Andi Gutmans dazu:

The new features and changes aim to rid PHP of any weaknesses it may have had and make sure that it stays in the lead as the best web scripting language on the globe.

Jetzt gibt es immerhin etwas mehr richtige Objektorientierung (mit Aufrufketten (siehe unten), Interfaces, abstrakten Klassen und so). Wenn ich die Zeit finde, werde ich zu jedem Beitrag den PHP5-Status dazuschreiben.

UPDATE 2012: Ein kleines Update zum Thema „Security“.

Die Liste

Ähnliche Seiten zum gleichen Thema

Perl vs. PHP; octo’s subjektiver Vergleich
http://verplant.org/perl.vs.php.html
Experiences of using PHP in large websites
http://www.ukuug.org/events/linux2002/papers/html/php/index.html
Why PHP sucks
http://www.bitstorm.org/edwin/en/php-sucks/
PHP in contrast to Perl
http://tnx.nl/php
PHP annoyances
http://n3dst4.com/articles/phpannoyances/

TRUE == FALSE

Hier hat PHP ein für alle Mal den Vogel abgeschossen. Oder ist es etwa vermessen, anzunehmen, die Äquivalenzbeziehung „==“ sei transitiv? Sprich: Wenn a==b und b==c, dann muss doch wohl a==c gelten!

Nicht so bei PHP! (via)

if (TRUE == "x" && "x" == 0 && 0 == FALSE) {
  echo "TRUE == FALSE!"
}

Request-Parameter werden umbenannt!

Wenn Sie fürs Web programmieren, müssen Sie in der Regel Parameter verarbeiten, die beim Aufruf Ihrem Programm übergeben werden. In der Regel sind das Formulardaten, und was der Benutzer im Browser eingetragen hat, können Sie über die Namen der jeweiligen Felder abfragen.

Das ist in allen normalen Sprachen und Frameworks fürs Web so. Der Benutzer übergibt den Parameter „aktion“ mit dem Wert „speichern“, und in meinem Webskript oder Servlet kann ich über den Namen „aktion“ dann abfragen, was der Benutzer wohl vorhat.

Meistens funktioniert das auch in PHP. Wenn Sie allerdings eines Tages einmal einen Parameter haben, in dessen Namen ein Punkt („.“) vorkommt, haben Sie Pech — wenn Sie PHP benutzen. Es wird nämlich nicht mehr funktionieren, und nach einiger Suche werden Sie dahinterkommen, dass PHP Ihre Parameter einfach umbenennt! Sweet.

Beispiel: Sie benutzen ein Formular, in dem man auf ein Bild klicken kann, um das Formular abzuschicken. Wenn Sie dem Bild einen Namen gegeben haben, erzeugen alle Browser daraus die Formulardaten „bild.x“ und „bild.y“, je nachdem, wo genau das Bild angeklickt wurde. Bevor Sie diese Felder aber in PHP abfragem können, hat PHP daraus „bild_x“ und „bild_y“ gemacht.

Von den PHP-Menschen heißt es dazu, das sei kein Bug, sondern Absicht. Schuld hat wohl die register_globals-Option, die alle Parameter direkt als globale Variablen zur Verfügung stellt. Weil der Punkt ein Operator ist, kann es natürlich keine Variable geben, die $bild.x heißt.

Das Schöne ist: Auch wenn Sie sich nicht auf die (ohnehin hirnverbrannte) „register_globals“-Option verlassen, sondern Ihre Parameter brav über das $_REQUEST-, $_GET- oder $_POST-Array abfragen, sind Sie hiervon betroffen! Dabei spricht nocht nicht mal etwas dagegen, diesen Parameter in $_REQUEST['bild.x'] zu speichern. Man wollte wohl einfach konsinstent mit der alten, unausgegorenen Art der Parameterverabeitung bleiben...

Aufrufketten

Aufrufketten, die normalste Sache der Welt. Ich rufe eine Methode auf und bekomme ein Objekt, auf dem ich wieder eine Methode aufrufe usw. In normalen Sprachen kann man das ganz einfach hintereinander weg schreiben, also etwa so:

$html = $this->getField(2)->toHTML();

Ganz analog würde der unbedarfte Programmierer erwarten, dass ich hinter einen Aufruf, der ein Array liefert, auch gleich einen Index schreiben kann, wenn mich z. B. nur ein Element interessiert:

$id = mysql_fetch_assoc($res)['id'];

Das geht aber in PHP nicht. Ich muss die Rückgaben in einer Variablen zwischenspeichern und kann erst dann darauf zugreifen:

$field = $this->getField(2);
$html = $field->toHTML();

$row = mysql_fetch_assoc($res);
$id = $row['id'];

Überraschung mit next

Betrachten wir einmal diesen Code. Was würden Sie als Ausgabe erwarten?

for ($i=0; $i<10; $i++) {
  if ($i < 5) {
    next;
  }
  print "$i ";
}

Ich würde denken, dass hier die Zahlen von 5 bis 9 ausgegeben werden: Es werden alle Zahlen von 0 bis 9 durchlaufen, aber wenn die Zahl kleiner als fünf ist, wird mit next zum nächsten Schleifendurchlauf gesprungen.

Wenn wir diesen Code in Perl ausführen, ist die Ausgabe auch:

5 6 7 8 9

Wenn wir genau denselben Code in PHP (4.1.2, 4.3.4) ausführen lassen, erhalten wir:

0 1 2 3 4 5 6 7 8 9

Hallo? Ich kann mit next aus einem if-Block aussteigen? Habe ich was verpasst und if ist neuerdings ein Schleifenkonstrukt?!

In der Online-Doku steht unter „next“ nur die Array-Funktion next(), die hier aber gar nicht aufgerufen wird (da keine Klammern da sind). Von einem Schlüsselwort dieses Namens habe ich ansonsten nichts gefunden, offensichtlich existiert es aber und wird auch als Kontrollanweisung ausgeführt. Ein Alias von break ist es auch nicht, da next 1 z. B. zu einem Syntaxfehler führt.

Fassen wir zusammen:

Ganz toll. Einmal mehr vielen Dank für die gestohlene Zeit!

Aufbau von HERE-Dokumenten

HERE-Dokumente, also im Quelltext eingebettete Textschnippel, müssen natürlich unbedingt anders sein als in Perl. In PHP sehen sie jedenfalls so aus:

$krabbe = <<<ENDMARKE
Bla bla bla,
bli blo blubb.
ENDMARKE;

Zum Vergleich: In Perl gibt es nur zwei < und der Text gehört nicht zur Anweisung selbst (der Semikolon trennt das HERE-Dokument von der Anweisung):

$krabbe = <<ENDMARKE;
Bla bla bla,
bli blo blubb.
ENDMARKE

Aber das könnte ja auch gut sein. Bei Perl schleichen sich z. B. in der Zeile mit der Abschlussmarke leicht zusätzliche Leerzeichen ein, die einen Syntaxfehler erzeugen und die zu entdecken schon mal ein Weilchen dauern kann.

Bei PHP steht dagegen noch ein Semikolon hinter der Endmarke; man sollte meinen, dass versehentlich angefügte Leerzeichen kein Problem mehr sind. Pustekuchen: Die letzte Zeile muss genau END; heißen, jedes zusätzliche Leerzeichen (auch hinter dem Semikolon) ist ein Syntaxfehler.

Variablenauswertung in Strings

Von Perl hat PHP die Eigenschaft übernommen, dass Variablen in Stringliteralen evaluiert werden. Man kann also direkt im String "Anzahl: $anz Stück" schreiben anstatt "Anzahl: " . $anz . " Stück".

Man schreibt also einen String zusammen und kann dabei jeden Ausdruck, den man sonst explizit anhängen müsste, direkt mit rein schreiben, ohne die Anführungszeichen zu verlassen. Dazu muss der Parser natürlich raten, wo wohl ein Variablenausdruck anfängt und wo er wohl aufhört.

Das funktioniert in PHP so weit auch, nur hat keiner weit genug gedacht, um Arrays sinnvoll zu behandeln. Das hier funktioniert zwar:

$zusammenfassung = "Anzahl: $daten[anzahl]";

Ist aber laut PHP Manual falsch, weil anzahl ja in Zukunft ein Schlüsselwort werden könnte und der Code dann nicht mehr kompilierbar sein könnte. Sehr vernünftig. Stattdessen soll man Strings im Index immer schön in Anführungszeichen setzen. Die empfohlene Schreibweise ist also:

$zusammenfassung = "Anzahl: $daten['anzahl']";

Möchten Sie jetzt raten, welche von beiden Schreibweisen funktioniert und welche kaputt implementiert ist?

Nicht mal das hier funktioniert:

$zusammenfassung = "Anzahl: $daten[statistik][anzahl]";

Da kommt nämlich raus:

Anzahl: Array[anzahl]

require()

require() merkt nicht automatisch, wenn eine Datei schon geladen ist. Dafür muss extra require_once() benutzt werden. Wofür braucht man dann eine solche Funktion wie require? Gibt es jemals eine Gelegenheit, dass ich versuchen möchte, meine bereits definierten Funktionen mit denselben Funktionen zu überschreiben?

Escaping bei regulären Ausdrücken

Bei regulären Ausdrücken muss der doppelte Escape-Aufwand betrieben werden. Wenn ich in einem String ein Newline meine, muss ich ohnehin schon " " schreiben, für ein Newline in einem regulären Ausdruck auch noch "\n".

$body = preg_replace('/\n/','---UMBRUCH---',$body);

Boolean-Wert von Arrays und Objekten

Das ist in PHP komisch geregelt, zumindest für Objekte. Aus dem Manual:

Nicht-skalare Datentypen (Arrays und Objekte) werden als FALSCH betrachtet, wenn sie keine Elemente enthalten, andernfalls geben sie WAHR zurück.

Für Arrays mag das sinnvoll sein, aber für Objekte? Ich kann mich nicht darauf verlassen, dass ich mit if ($objekt) { ... } abfragen kann, ob ich ein instantiiertes Objekt habe oder nicht?

Ja, ganz recht. Bei diesem Code:

class DummyHandler
{
  function handle()
  {
    return "bla";
  }
}

...wird das hier nicht funktionieren (zumindest nicht so, wie ich es erwarten würde):

$handler = new DummyHandler();

...

if ($handler) {
  return $handler->handle(); // hier landen wir nie, weil
                             // "DummyHandler" keine Felder hat!!!
} else {
  return "?";
}

Ein recht unangenehmer Effekt des Umstands, dass PHP-Objekte in Wirklichkeit nur lieblos aufgemotzte Arrays sind.

$this-Zwang innerhalb von Objekten

Member-Funktionen und -Feldern muss immer $this-> vorangestellt werden. Das vergisst man zum Glück nie, weil man es von echten objektorientierten Sprachen ja sooo gewohnt ist...

checkdate()

Wenn ich in Perl ein Datum überprüfen möchte, mache ich das so:

use Date::Calc;

$is_valid = check_date($year, $month, $day);

Aber Moment mal! Jahr – Monat – Tag, das ist ja viel zu offensichtlich! PHP orientiert sich da lieber am amerikanischen Durcheinander-Datum:

$is_valid = checkdate($month, $day, $year)

Array-Indizes: String oder Integer oder was?

Ein Fundstück aus den Benutzerkommentaren der Online-Doku:

$arr["1"] and $arr[1] refer to the same element.
$arr["-1"] and $arr[-1] refer to different elements.

Inkonsistente Benamsung

In Objekten heißt es überall $this->bla und $this->blubb, aber immer parent::bla und parent::blubb. Warum nicht entweder $parent->blubb oder this::bla?

Noch schlimmer sind die PHP-Funktionsnamen selbst. Hier gibt es kein Schema, und man kann keine Annahmen darüber machen, wie eine bestimmte Funktion wohl heißen könnte.

Das fängt bei der Formatierung an: Wir haben str_pad und str_replace, aber auch strpos und strlen. Es gibt strip_tags und stripslashes, und die müsste genau genommen auch stripbackslashes heißen.

Außerdem gibt es in PHP keine Namespaces, auch in PHP5 nicht. Immerhin können Sie jetzt durch die Benutzung von Extensions auf objektorientierte Weise hoffen, dass z. B. die nächste Version der MySQL-Extension die gleichen Methoden verwendet und Sie nur an einer Stelle den Konstruktor ändern müssen.

Versionszählung

Die Versionszählung ist, gelinde gesagt, ein Witz. Wenn ich mir überlege, dass mein Code, der unter 4.0.0 noch läuft, unter 4.0.1 eventuell schon kaputt ist, sollte es vielleicht besser PHP 0.4 heißen.

Diese Arrays!

Das Durcheinander von Arrays, Hashes, numerischen Array-Keys und dem magischen internen Hash-Zähler ist ein Albtraum. Ist mein Array jetzt ein Array oder doch ein Hash? Wie kann man einen Hash überhaupt „Array“ nennen?

Natürlich kriegt man es am Ende irgendwie zum Laufen, aber immer wieder wird man unnötig vor den Kopf gestoßen. Zum Beispiel, wenn man naiverweise erwartet, dass im folgenden Code zwei Mal das Gleiche passiert:

foreach ($my_array as $element) {
    do_something($element)
}

// versus:

for ($i=0; $i<count($my_array); $i++) {
    do_something($my_array[$i]);
}

Sicherheit

PHP und Sicherheit, das ist ein Thema für sich. Die allermeisten Sicherheitslücken kommen sicherlich von PHP-Programmierern (Wordpress, Forensoftware XY, und und und). Das kommt bestimmt nicht von ungefähr, wenn selbst Rasmus Lerdorf, immerhin der Schöpfer von PHP, mit einem gedankenlosen Commit mal eben die crypt-Hashing-Funktion kaputt macht.

Aber auch PHP selbst steht mit Sicherheit auf Kriegsfuß, insbesondere beim Umgang mit Sicherheitslücken. Ein guter Ausgangspunkt ist vieles, was Stefan Esser (Suhosin, Month of PHP Security Bugs) geschrieben und gesagt hat. Eine stellvertretende Fallstudie gibt es hier: Wie PHP seit 2005 mit „HTTP response splitting“ umgegangen ist – Money Quote:

…because obviously inside PHP no one cares about reviewing security patches.