Stand: 2007-06-23

Warum nicht PHP?

Anders als die Verbreitung vermuten lässt, ist PHP voll von teils haarsträubenden Mängeln.

Inhalt

Vorteile von PHP

Im Wesentlichen hat PHP zwei Vorteile:

  1. Mit PHP kommt man einigermaßen schnell zu einer dynamischen Website und
  2. PHP ist weit verbreitet und bei jedem 0815/Provider installiert.

Mangelnde Sicherheit

Die schlechte Konzeption der Sprache und der chaotische Entwicklungsprozess führen immer wieder zu Sicherheitslücken. Der Sicherheitsspezialist Stefan Esser hat das PHP-Projekt verlassen, weil er es für aussichtslos hielt, PHP von innen sicherer zu machen. Nach eigener Darstellung wurde er angegriffen, weil er die Sicherheit von PHP kritisierte und viele seiner Vorschläge zur Verbesserung der Sicherheit seien abgelehnt worden. Weiter berichtet er, dass Fehler teilweise nicht korrekt behoben oder wieder eingeführt würden.

Zahlreiche Sicherheitslücken listet heise online auf.

API-Dokumentation

Die geringen Ansprüche an die Qualität bei PHP treten auch in der Dokumentation des APIs auf. Das fängt schon damit an, dass die Funktionen nicht zu Themenbereichen (z.B. Datenbanken, Dateisystem...) zusammengefasst werden, sondern 182 kleine kleine Funktionssammlungen in einer langen Liste stehen, die noch dazu römisch nummeriert ist, was kaum jemand flüssig lesen kann. Diese Struktur ist übrigens nach Aussage von Projektmitgliedern deshalb zustande gekommen, weil das gewählte Dokumentationswerkzeug, nicht ausreichend viele Hierarchieebenen unterstützt.

Auf den einzelnen übersichtsseiten der Funktionssammlungen erscheinen oft zunächst sehr lange Ausführungen, Voraussetzungen, Installationshinweise, Beispiele usw. bevor die eigentlichen Funktionen aufgelistet werden, die man aber am häufigsten braucht. Wenigstens ein Link, der wie in der Java-API-Dokumentation direkt zu der Liste der Funktionen führt, wäre hilfreich.

Die Dokumentation ist noch dazu lückenhaft, wie folgendes Beispiel zeigt:

Unvollständige PHP-Dokumentation

Quelle: http://de.php.net/manual/de/function.imap-bodystruct.php, Datum: 2007-01-21

Die PHP-Dokumentation ist voll von unzureichenden Informationen. Wem soll die folgende Auflistung von Konstanten ohne Erläuterung helfen?

Udokumentierte Konstanten

Quelle: http://www.php.net/manual/de/ref.tidy.php, Datum: 2007-01-20

Würde es bei der Entwicklung von PHP ernsthafte Qualitätsrichtlinien geben, wäre es undenkbar, dass so etwas in einer finalen Version erscheint.

Version ist das Stichwort für den nächsten Kritikpunkt: Es gibt nur eine Dokumentation für alle PHP-Versionen. Es ist zwar gekennzeichnet, was in welcher Version dazugekommen ist, aber der übersichtlichkeit ist das nicht dienlich.

Für den Programmierer weniger bedeutsam, aber ebenfalls Ausdruck mangelnden Qualitätsbewußtseins ist die Struktur des HTML-Quelltextes der Dokumentation. Es gibt nur überschriften auf der höchsten Hierarchieebene (h1), auch wenn diese logisch untergeordnet sein müssten.

Die Einordnung von Funktionen erscheint mitunter fragwürdig. So wird md5 bei den Stringfunktionen eingeordnet, obwohl es sich dabei in erster Linie um eine kryptographische Funktion handelt. Noch kurioser ist die Einordnung von md5_file unter den Stringfunktionen.

Konfuse Funktions- und Parameternamen

Die Namen von Funktionen und Parametern sehen aus wie Kraut und Rüben.Zahlreiche Aliasnamen zeugen von mangelnder Konzeption. Einige Beispiele finden sich auf der Seite mit den Oracle-Funktionen, zum Beispiel: "ocicolumntyperaw -- Alias of oci_field_type_raw()".

Ein Schema ist bei der Benennung von Funktionen nicht erkennbar. Mal werden die Teile des Namen mit Unterschrich getrennt und mal werden sie direkt aneinander gehängt und mal steht das "i" für insensitive am ersten und mal am zweiten Teil und mal steht statt "i" das Wort "case":

Obwohl PHP-Funktionen im allgemeinen klein geschrieben werden, gibt es einige, die unter der Verwendung von camelCase groß geschrieben werden. Offensichtlich wird der Funktionsname aber nur in der übersicht und überschrift so geschrieben und ansonsten wird alles in Kleinbuchstaben geschrieben.

Die Apache-Funktionen sind uneinheitlich benannt und beginnen nicht alle mit einem vorangestellten "apache".

Aus C-Zeiten kommt die Unsitte, für alles kryptische Abkürzungen zu verwenden, die auch auch bei PHP nicht überwunden wurde. Die Lesbarkeit des Quelltextes wird dadurch erheblich eingeschränkt.

Wieso heißt das, worin gesucht wird, mal "haystack", mal "subject" und mal "string"? Gleiches gilt für "needle".

Fehlende Mächtigkeit

In PHP gibt es viele Funktionen mit sehr ähnlichen Aufgaben, statt wenigen Mächtigen. Für die Funktionalität von

reicht Python die Methode list.sort([cmp[, key[, reverse]]]).
Quelle: http://toykeeper.net/soapbox/php_problems/

Durch viele kleine, überflüssige Funktionen ist das PHP-API sehr aufgeblasen. Es ist überflüssig, von zig Funktionen eine Variante zu haben, die nicht zwischen Groß- und Kleinschreibung unterscheidet. Da es strtolower(string str) gibt, sind diese Funktionen überflüssig.

Der Zugriff auf Methoden und Variablen einer Klasse erfolgt mit zwei Notationen, abhängig davon, ob die aufgerufene Methode statisch ist oder nicht. Um auf statische, konstante oder ¼berschriebene Methoden oder Variablen zuzugreifen braucht man :: und sonst -> als Operator:

Klasse::statischeMethode();
Klasse::KONSTANTE;
MutterKlasse::ueberSchriebeneMethode();
MutterKlasse::ueberSchriebeneVariable.
Objekt->;dynamischeMethode();
Objekt->;variable;

Ein Operator für alle diese Fälle, wie in Java, hätte es auch getan.

Fehlende Namensräume

PHP kennt keine Namensräume, was bedeutet, dass bei PHP alle Funktionen auf einer Ebene nebeneinander stehen. Abgesehen davon, dass das unübersichtlich ist, bringt es Probleme mit sich, wenn verschiedene Komponenten zusammengeführt werden und zwei Funktionen den gleichen Namen haben. Es könnte auch sein, dass in einer späteren PHP-Version eine Funktion mit einem Namen definiert wird, der zuvor schon in eigenen Programmen verwendet wurde.

Keine Namensräume zu verwenden, ist vergleichbar damit, alle Dateien auf einer Festplatte in ein Verzeichnis zu werfen.

Schwache Typisierung

Um den Programmierer nicht mit Datentypen aufzuhalten, verzichten viele Skriptsprachen auf eine statische Typisierung. In der Tat kann eine Menge Quelltext eingespart werden, wenn die Typisierung nicht statisch sondern dynamisch zur Laufzeit erfolgt. Umgekehrt können Schnittstellen mit statischer Typisierung sehr viel genauer beschrieben und so Fehler frühzeitig vermieden werden. Bei der Frage, ob statische oder dynamische Typisierung besser ist, prallen Welten aufeinander.

Unabhängig von philosophischen Fragen ist die dynamische Typisierung bei PHP schwach. Ohne weiteres lässt sich eine Zeichenkette zu einer Zahl addieren. Aber was bedeutet das Ergebnis?

"1" + 1 = ?

Manchmal mag es praktisch sein, dass PHP so wenig streng ist, aber dadurch werden auch viele Fehler erst möglich. Gerade wenn mehrere PHP-Entwickler zusammenarbeiten, ist es hilfreich, wenn der Compiler für etwas Disziplin sorgt.

Python wirft in so einem Fall dank starker Typisierung eine Exception:

>>> "1" + 1
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
"1" + 1
TypeError: cannot concatenate 'str' and 'int' object

Kruios wird es bei PHP mit Vergleichen:

'0.0' == false // = false
0.0 == false // = true
'0.0' == 0.0 // = true
'0.0' == 0 // = true
0 == null // = true
'0' == false // = true
'0' == null // = false
'' == null // = true
false == null // = true

Das führt zu:

'0.0' != false == 0.0 == '0.0'

und damit logisch zu:

'0.0' != '0.0'

Wozu der schluderige Umgang mit Typiserung führt, zeigt das folgende Musterbeispiel. Der Rückgabewert der Funktion mysqli_stmt_fetch ist vom Typ boolean, der bekanntlich nur die beiden Werte true und false umfasst - außer bei PHP:

Value Description
TRUE Success. Data has been fetched
FALSE Error occured
NULL No more rows/data exists or data truncation occurred

Was macht man nun, wenn man sich in einer Bedingung auf den Rückgabewert dieser Funktion verlässt?

if ( mysqli_stmt_fetch($stmt) ) {
	// wenn TRUE ...
} else {
	// wenn FALSE ...
}

Und was ist, wenn der Rückgabewert NULL ist?

Aufgesetzte Objektorientierung

Die Objektorientierung in PHP wurde nachträglich aufgesetzt. Das API selbst ist nicht objektorientiert und erschwert deshalb die Entwicklung objektorientierter Software. Ein String ist zum Beispiel ein Array aus Zeichen, aber kein Objekt. Die fehlende Objektorientierung führt auch dazu, dass bestehende Funktionen nicht neu definiert oder geerbt werden können.

Bei der Entwicklung objektorientierter Programme kommt erschwerend hinzu, dass PHP keinen Application-Server mitbringt. Deshalb müssen bei jedem Aufruf Objekte neu erzeugt und Informationen auf der Festplatte zwischengespeichert werden. Abgesehen davon, dass das schlecht für die Performance ist, macht es die Entwicklung unnötig umständlich.

Keine durchgängigen Exceptions

Ebenso wie die Objektorientierung ist die Ausnahmebehandlung aufgesetzt, aber nicht fest in das API integriert. Warum wirft fopen() keine Exception, sondern zeigt den Fehler über den Rückgabewert an?

Wenn ein PHP-Skript Bilder auf dem Server manipuliert, kann es passieren, dass ein von der Dateigröße her kleines Bild im Hauptspeicher so groß wird, dass die zulässige Speichergröße des Prozesses überschritten und das Skript in der Folge abrupt beendet wird. Mit einer Exception könnte dieser Fehler abgefangen werden, aber die entsprechenden PHP-Funktionen werfen keine Exceptions.

Keine Threads

Nebenläufigkeit mit Threads ist mit PHP nicht möglich. Deshalb können in einem PHP-Skript keine Aktionen (quasi) parallel ausgeführt werden. Viele andere Programmiersprachen, darunter Python, Java und C#, bieten diese Möglichkeit. Je nach Anwendungsfall kann die Laufzeit eines Programms dadurch dramatisch verkürzt werden.

PHP bietet Threads nicht nur nicht als Teil der Sprache sondern ist auch nicht Thread-sicher, weshalb es zu Problemen kommen kann, wenn man PHP im Apache-Webserver mit Threads ausführt.

Schwacher Interpreter

In PHP können können keine Ketten von Aufrufen interpretiert werden. Liefert eine Funktion zum Beispiel ein Array als Rückgabe, kann man nicht direkt hinter den Funktionsaufruf einen Index schreiben:

array_liefernde_funktion()[2]

Das funktioniert in PHP nicht. Man muss zunächst das Array zwischenspeichern. Mit Python oder Java kann der Index dagegen einfach hinter den Funktionsaufruf geschrieben werden.

Schlecht entworfene Datenbankfunktionen

Bei gewöhnlichen Datenbankabfragen werden die Steuerdaten (SQL-Anweisungen) zusammen mit den Nutzdaten an die Datenbank übertragen. Werden dagegen Prepared Statements verwendet, werden Steuer- und Nutzdaten getrennt. Das verbessert die Geschwindigkeit von Abfragen, die mehr als einmal ausgeführt werden und verhindert zudem SQL-Injektion.

Ein Prepared Statement hat im Allgemeinen folgenden Aufbau:

PreparedStatement ps = Connection.prepareStatement("SELECT age, height FROM tbl_patient WHERE name=?"); 
ps.setString(1, patient);  
ResultSet rs = ps.executeQuery();

Im ersten Schritt wird der Ausdruck an die Datenbank geschickt und im zweiten werden die Parameter gebunden (hier wird für den ersten Parameter der Inhalt der Variablen patient). Der dritte Schritt ist schließlich die Ausführung der Abfrage.

Bei den mysqli-Funktionen von PHP, drängt sich der Verdacht auf, dass die Entwickler dieses Prinzip nicht ganz verinnerlicht haben. Mit der Funktion mysqli_stmt_prepare wird wie gewöhnlich eine Abfrage mit Platzhaltern an das Datenbank übergeben. Die Parameter werden mit der Funktion mit dem hässlichen Namen mysqli_stmt_bind_param an das Prepared Statement gebunden und die Abfrage mit mysqli_stmt_execute ausgeführt. Soweit ist das auch noch normal.

Aber dann wird es kurios, denn mit der Funktion mysqli_stmt_bind_result werden Variablen zur Aufnahme der Ergebnisse an das Statement gebunden! Da die Abfrage wurde zu diesem Zeitpunkt jedoch schon ausgeführt und das Ergebnis sowieso nur Nutzdaten und keine Steuerdaten enthält, ist dieses Verfahren sinnlos. Es ist ein besonders umständlicher Weg, um an das Ergebnis zu kommen. Es wäre einfacher, wie üblich mit den zurückgegebenen Datensätzen bzw. daraus erzeugten Objekten zu arbeiten.

$stmt = mysqli_stmt_init($connection); // Wozu dieser separate Aufruf?
mysqli_stmt_prepare($stmt, "SELECT age, height FROM tbl_patient WHERE name=?");
mysqli_stmt_bind_param($stmt, "s", $patient); // "s" steht übrigens für den Typ String
mysqli_stmt_execute($stmt);
mysqli_stmt_bind_result($stmt, $age, $height); // Parameter an das Ergebnis zu binden ist kurios

Das gleiche Beispiel mit den (besseren) PostgreSQL-Funktionen von PHP:

$result = pg_prepare($dbconn, "myPS", "SELECT age, height FROM tbl_patient WHERE name=?");
$result = pg_execute($dbconn, "myPS", array($patient));

Wie bei PHP üblich ist auch hier wieder eine große Uneinheitlichkeit festzustellen. Die Funktionen für Prepared Statements sehen für jede Datenbank völlig unterschiedlich aus und auch bei derselben Datenbank unterscheiden sich Prepared Statements mehr als nötig von den normalen DB-Funktionen.

Die mysqli-Funktionen lassen sich übrigens auch im "object oriented style" nutzen, allerdings ist nirgends eine Klasse dazu dokumentiert. Man merkt an dieser Stelle ein mal mehr das inkonsequente Design der Bibliotheken, aber das sei nur nebenbei bemerkt.

Auswege

Wenn man nicht gerade aufgrund äußerer Zwänge auf PHP angewiesen ist, lohnt sich ein Blick auf Python. Besonders schnell lassen sich Webanwendungen mit Frameworks wie Django bauen. Wenn du neugierig geworden bist, kannst du mit dem Buch A Byte of Python oder Dive into Python loslegen!

Eine leichtgewichtige Art, die Java-Plattform zu nutzen, ist Grails, das an Ruby on Rails angelehnt ist, aber die Programmiersprache Groovy verwendet. Wer es lieber statisch typsiert hat, ist mit dem Play! Framework gut bedient.

Es lohnt sich auf jeden Fall, die Augen offen zu halten und nach etwas Besserem als PHP Ausschau zu halten!

Weitere Information