PHP: ein Fraktal schlechten Designs

Dieser Artikel erschien ursprünglich am 9. April 2012 auf dem Blog fuzzy notepad unter dem Titel PHP: a fractal of bad design von eevee.

Der Artikel hat mich sehr fasziniert, nicht zuletzt, weil ich PHP selbst nicht wirklich mag und es vermeide mir die Sprache weiter anzueignen, wo es nur geht 😀
Auch wenn mir bewusst ist, dass, wenn ich jemals wieder in die Webentwicklung komme, das unvermeidbar sein wird, fand ich diesen Artikel sowohl lehrreich als auch amüsant.

Ich sah, dass andere den Artikel auf Spanisch übersetzt haben, also dachte ich mir, warum sollte es nicht auch eine deutsche Übersetzung geben? 😉

Disclaimer: dieser Artikel ist lediglich eine Übersetzung des englischen Originals. Die in diesem Artikel wiedergegebenen Meinungen und Ansichtsweisen entsprechen nicht zu 100% meinen eigenen. Ich könnte beispielsweise auch keine breitgefächerten Tiefenanalysen jedes einzelnen Arguments durchdiskutieren, da mein Wissen in PHP dafür offensichtlich nicht tief genug geht. Für Diskussionen wendet euch bitte an die Kommentare im Original-Artikel 🙂

Randbemerkung meinerseits: Ich habe zur Arbeit an der Übersetzung dieses Artikels Markdown verwendet, eine sehr simple Auszeichnungssprache, mit der man recht einfach und vor allem sehr angenehm HTML formatieren kann ohne wirklich HTML zu schreiben, wodurch ich zum ersten Mal auch handfeste Praxis in der Sprache üben konnte. Schaut euch das auf jeden Fall mal an! 😀


Vorwort

Ich bin unleidlich. Ich beschwere mich über vielerlei Dinge. In der Welt der Technologie gibt es vieles, das ich nicht mag, was wenig überrascht — Programmieren ist eine immer noch junge Disziplin und keiner von uns hat auch nur den blassesten Schimmer davon was er tut. Zusammen mit Sturgeon’s law habe ich also etwas über das ich mich mein Leben lang auslassen kann.

Das hier ist jedoch nicht dasselbe. PHP ist nicht nur einfach umständlich oder schlecht geeignet für was ich es benutzen möchte oder suboptimal oder gegen meine Religion. Ich kann euch allerlei gute Dinge über Sprachen erzählen die ich meide und allerlei schlechtes über Sprachen die mir Spaß machen. Los, fragt mich was! Daraus können sich sehr interessante Gespräche entwickeln.

PHP ist die einzige Ausnahme. Nahezu jedes Feature in PHP ist irgendwie kaputt. Die Sprache, das Framework, das Ökosystem, alles ist einfach nur schlecht. Ich kann nicht mal einen einzigen verdammten Punkt aufzeigen, weil der Schaden so systematisch ist. Jedes mal, wenn ich eine Liste an Dingen zusammenstellen möchte, die mich an PHP stören, verliere ich mich in einer Tiefensuche und entdecke immer mehr abstoßende Nebensächlichkeiten (daher fraktal).

PHP ist eine Beschämung, ein Schandfleck meines Handwerks. Es ist so kaputt und dennoch wird es von jedem bevollmächtigten Amateur gelobhudelt, der erst noch was zu lernen hat, dass es schon nicht mehr auszuhalten ist. Es hat erbärmlich wenig Eigenschaften, die das wieder wett machen und ich würde lieber vergessen, dass es überhaupt existiert.

Doch ich muss mir das von der Seele schreiben. Also gut, ein letzter Versuch.

Eine Analogie

Ich habe eben noch Mel damit in den Ohren gelegen, um meiner Frustration Ausdruck zu verleihen und sie bestand darauf, dass ich es hier nochmal wiedergebe:

Ich weiß nicht mal wo ich anfangen soll zu erklären was mit PHP nicht stimmt, weil— okay. Stell dir vor du hast äh, einen Werkzeugkasten. Eine Sammlung von Werkzeugen. Sieht ganz in Ordnung aus, Standardzeugs eben.

Du nimmst dir einen Schraubenzieher und siehst, dass es eins dieser seltsamen dreiköpfigen Teile ist. Okay, na ja, das wird sich wohl als weniger nützlich für dich erweisen, aber irgendeinen Nutzen wirst du dafür schon irgendwann finden.

Du nimmst dir einen Hammer, doch zu deinem Entsetzen musst du feststellen, dass er an beiden Enden eine Nagelklaue hat. Tut aber trotzdem, ich meine, du kannst Nägel immerhin noch mit der Mitte einschlagen, wenn du ihn seitlich hältst.

Du nimmst dir eine Zange, diese hat jedoch nicht diese geriffelte Oberfläche; stattdessen ist sie flach und glatt. Das ist noch weniger nützlich, aber damit lassen sich immer noch Schrauben drehen, also was soll’s.

Und das wär’s. Alles in dem Kasten ist irgendwie sonderbar und skurril, aber vielleicht ist es ja nicht komplett nutzlos. Und ein Problem als ganzes lässt sich auch nicht erkennen; es hat immer noch alle Werkzeuge.

Jetzt stell dir vor du triffst millionen von Zimmerleuten, die diesen Werkzeugkasten benutzen und dir erzählen „Ich weiß gar nicht was du hast? Ich hab nie mehr benutzt und es tut seinen Dienst!“ Und sie zeigen dir all die Häuser die sie damit gebaut haben, in denen jeder Raum ein Fünfeck ist und die Dächer auf dem Kopf stehen. Du klopfst an die Eingangstür, sie fällt nach innen ein und alle schreien dich an, weil du ihre Tür kaputt gemacht hast.

Das stimmt nicht mit PHP.

Standpunkt

Ich behaupte, dass folgende Qualitäten wichtig sind, damit eine Sprache produktiv und nützlich eingesetzt werden kann und PHP missachtet jede davon ach so hingebungsvoll. Wenn ihr mir nicht zustimmen könnt, dass folgende Punkte unabdingbar sind, tja, dann werden wir uns wohl in vielem nicht einig werden.

  • Eine Sprache muss vorhersehbar sein. Es ist ein Medium um menschliches Gedankengut auszudrücken, damit ein Computer sie ausführen kann. Also ist es wichtig, dass das menschliche Verständnis eines Programms auch richtig ist.
  • Eine Sprache muss konsistent sein. Ähnliche Dinge sollten sich ähneln und unterschiedliche sich unterscheiden. Einen Teil einer Sprache zu beherrschen sollte dabei helfen auch noch den Rest zu begreifen.
  • Eine Sprache muss prägnant sein. Neue Sprachen existieren um die anhaftende Boilerplate älterer Sprachen zu reduzieren (wir könnten auch alle Maschinencode schreiben). Eine Sprache muss daher darum bemüht sein zu vermeiden eigene Boilerplate mit einzuschleppen.
  • Eine Sprache muss verlässlich sein. Sprachen sind Werkzeuge um Probleme zu lösen; sie sollten neue Probleme, die sie mitbringen auf ein Minimum reduzieren. Etwaige „Reingelegt!“ Momente lenken nur ab.
  • Eine Sprache muss debugfähig sein. Wenn etwas schief läuft muss der Programmierer es wieder in Ordnung bringen und dabei ist nur jede Hilfe recht.

Mein Standpunkt ist daher wie folgt:

  • PHP steckt voller Überraschungen: mysql_real_escape_string, E_ALL
  • PHP ist inkonsistent: strpos, str_rot13
  • PHP benötigt Boilerplate: Fehlerprüfung für C API Aufrufe, ===
  • PHP ist unzuverlässig: ==, foreach ($foo as &$bar)
  • PHP ist undurchsichtig: standardmäßig keine Stacktraces auch nicht für fatale Fehler, komplexe Fehlerberichterstattung

Ich kann nicht für jedes Argument einen Absatz an Erklärungen abliefern wieso es gerade in diese Kategorien fällt, sonst säße ich hier bis zum Sankt-Nimmerleinstag. Ich vertraue darauf, dass meine Leser im Stande sind zu denken.

Versucht nicht mit diesen Dingen zu kommentieren

Ich hatte schon viele Debatten über PHP. Dabei mangelte es auch nicht an Totschlagargumenten. Kommt mir bitte nicht mit diesen. 😦

  • Erzählt mir nicht, dass „gute Programmierer in jeder Sprache guten Code schreiben können“ oder schlechte Programmierer blah blah. Das ist nichtssagend. Ein guter Zimmermann kann einen Nagel mit einem Stein oder einem Hammer einschlagen, aber wie viele Zimmerleute seht ihr, die mit Steinen auf ihr Zeug eindreschen? Einen guten Programmierer macht zum Teil auch die Fähigkeit aus die Werkzeuge zu wählen die am besten funktionieren.
  • Erzählt mir nicht, dass es die Aufgabe des Programmierers ist sich tausend komische Exceptions und überraschende Verhaltensweisen zu merken. Ja, das ist in jedem System notwendig, weil Computer doof sind. Das heißt aber nicht, dass es keine Obergrenze dafür gibt, wie viel Sperenzchen in einem System erlaubt sind. PHP besteht aus nichts anderem als Exceptions und es ist nicht in Ordnung, wenn man mehr Aufwand dafür betreibt mit der Sprache zurecht zu kommen, als sein Programm zu schreiben. Meine Werkzeuge sollten mir nicht noch mehr Arbeit oben drauf packen.
  • Erzählt mir nicht „So funktioniert die C API eben“. Wo um alles in der Welt ist der Sinn darin eine höhere Programmiersprache zu verwenden, wenn sie nichts anderes als ein paar String Helper und tonnenweise wortgetreue C Wrapper bereitstellt? Schreibt doch einfach C! Hier, es gibt sogar eine CGI Bibliotek dafür.
  • Erzählt mir nicht „Das kommt eben davon, wenn du nur Blödsinn damit machst“. Wenn es zwei Features gibt wird irgendwer irgendwann einen Grund finden sie zusammen zu benutzen. Und nochmal, dies ist nicht C; es gibt keine spec und keinen Grund für „undefiniertes Verhalten“.
  • Erzählt mir nicht, dass Facebook und Wikipedia in PHP geschrieben sind. Darüber bin ich mir im Klaren! Sie könnten genauso gut in Brainfuck geschrieben sein, aber solange es Leute gibt, die schlau genug sind sich mit dem Hickhack auseinandersetzen, können sie die Probleme mit der Plattform überwinden. Nach allem was wir wissen könnte sich die Entwicklungszeit halbieren oder verdoppeln, wenn diese Produkte in einer anderen Sprache geschrieben wären; dieser Punkt alleine sagt nichts aus.
  • Erzählt mir am besten gar nichts! Das ist meine große Chance; wenn das hier eure Meinung über PHP nicht ins Wanken bringt, dann wird es wahrscheinlich nichts jemals schaffen, also hört auf euch mit irgendeinem Typen im Internet zu streiten und baut ’ne coole Webseite in Rekordzeit um mich vom Gegenteil zu überzeugen 🙂

Nebenbeobachtung: Ich lieeeebe Python. Ich kann euch auch die Ohren damit vollheulen, was mich daran nervt, wenn ihr wollt. Ich behaupte nicht es sei perfekt; Ich habe nur die Vorteile gegen die Probleme aufgewogen und entschieden, dass es am besten dafür geeignet ist, was ich damit machen will.

Und ich habe noch keinen PHP Programmierer getroffen, der dasselbe mit PHP zustande gebracht hat. Jedoch bin ich genügend über den Weg gelaufen, die sich für alles und jenes entschuldigen, was PHP macht. Diese Denkweise ist erschreckend.

PHP

Kernsprache

CPAN wurde als „Standardbibliothek von Perl“ bezeichnet. Das sagt nicht viel über Perls Standardbibliothek aus, aber es vertritt die Aussage, dass aus einem robusten Kern großartige Dinge hervorgehen können.

Philosophie

  • PHP war ursprünglich ausschließlich für Amateur-Programmierer gedacht (und zwischen den Zeilen gelesen auch für Amateur-Programme); es hat sich seitdem von seinen Ursprüngen auch nicht sonderlich weit entfernt. Nachfolgend ein Auszug aus der PHP 2.0 Dokumentation, betreffend + und Konsorten bei der Typumwandlung:

    Sobald man separate Operatoren für jeden Typ einführt wird die Sprache zusehends komplexer, z. B. kann man ‚==‘ nicht mehr für Strings benutzen [sic], man benutzt dafür jetzt ‚eq‘. Ich verstehe den Sinn dahinter nicht, besonders bei etwas wie PHP, wo die Skripte eher simpel gehalten sind und in den meisten Fällen von Amateur-Programmierern geschrieben werden, die eine Sprache mit einer einfachen, logischen Syntax wollen, bei der die Lernkurve nicht zu steil ist.

  • PHP ist so konzipiert, dass es um jeden Preis angetuckert kommt. Wenn es entweder etwas völlig unsinniges tun oder mit einem Fehler abbrechen kann, tut es etwas völlig unsinniges. Besser als wenn gar nichts passiert.
  • Es gibt keine klare Design-Philosophie. Frühere PHP Versionen waren von Perl inspiriert; die riesige stdlib mit „out“ Parametern ist von C; Teile der Objektorientierung sind entworfen wie C++ und Java.
  • PHP lässt sich im großen Stil von anderen Sprachen inspirieren, bringt es aber fertig für jeden unverständlich zu sein, der mit den jeweiligen Sprachen vertraut ist. (int) sieht nach C aus, aber int gibt es nicht. Namespaces verwenden \. Die neue Array-Syntax führt zu [key => value], eine Besonderheit unter sämtlichen Sprachen mit Hash-Literals.
  • Die schwache Typisierung (d. h. stille, automatische Umwandlung zwischen Strings/Zahlen/usw.) ist so komplex, dass es jeden noch so kleinen eingesparten Programmieraufwand in keinster Weise rechtfertigt.
  • Nur wenig neue Funktionalität wird als neue Syntax implementiert; das meiste davon wird über Funktionen realisiert oder Dinge die wie Funktionen aussehen. Außer bei der Klassen-Unterstützung, die gleich einen ganzen Haufen neue Operatoren und Schlüsselwörter bekommen hat.
  • Einige der hier aufgeführten Probleme haben Erstanbieter-Lösungen — sofern man bereit ist Zend für Nachbesserungen ihrer quellenoffenen Programmiersprache zu bezahlen.
  • Es passiert viel in der Ferne. Man beachte diesen wahllos herausgepickten Code irgendwo aus den PHP Dokus.
    @fopen('http://example.com/not-existing-file', 'r');

    Was wird passieren?

    • Falls PHP mit --disable-url-fopen-wrapper kompiliert wurde, funktioniert es nicht. (In der Doku wird nicht näher beschrieben was „funktioniert nicht“ heißt; gibt null zurück, wirft eine Exception?) An dieser Stelle sei angemerkt, dass diese Flag in PHP 5.2.5 entfernt wurde.
    • Falls allow_url_fopen in php.ini deaktivert ist, funktioniert es immer noch nicht. (Wie? Kein Peil.)
    • Wegen dem @ wird keine Warnung wegen einer nicht existierenden Datei ausgegeben.
    • Ist jedoch scream.enabled in php.ini gesetzt wird es es doch ausgegeben.
    • Oder wenn scream.enabled manuell mit ini_set gesetzt wird.
    • Aber auch nur dann, wenn der richtige error_reporting Level gesetzt ist.
    • Wenn es doch ausgegeben wird, dann entscheidet display_errors darüber wo, einstellbar über die php.ini. Oder ini_set.

    Ich kann nicht voraussagen, wie sich dieser harmlose Funktionsaufruf verhalten wird, ohne nicht vorher die Flags zur Kompilierzeit nachgeschlagen zu haben, die Server-Konfiguration zu kennen und die Einstellungen, die ich in meinem Programm gesetzt habe, zu berücksichtigen. Und das ist alles integriertes Verhalten.

  • Die Sprache ist voll von globalen und implizierten Zuständen. mbstring verwendet einen globalen Zeichensatz. func_get_arg und Konsorten sehen aus wie handelsübliche Funktionen, operieren aber in der aktuell ausgeführten Funktion. Das Fehler/Exception Handling besitzt globale defaults. register_tick_function setzt eine globale Funktion für jeden ausgeführten Tick — Was?!
  • Es gibt keinerlei Threading-Support (weniger überraschend, wenn man den zuletzt angesprochenen Punkt bedenkt). Kombiniert mit dem Fehlen eines eingebauten fork (dazu später mehr) macht dies parallele Berechnungen extrem schwierig.
  • Teile von PHP sind prädestiniert um fehlerhaften Code zu erzeugen.
    • json_decode gibt bei ungültigem Input null zurück, auch wenn null ein völlig gültiges Objekt für JSON zum Dekodieren ist — diese Funktion ist vollkommen unzuverlässig, es sei denn man ruft jedes Mal json_last_error mit ihr auf, wenn man sie benutzt.
    • array_search, strpos und ähnliche Funktionen geben 0 zurück wenn sie die Nadel bei Position Null finden, aber false wenn sie gar nichts finden.

    Lasst mich zu diesem letzten Punkt etwas weiter ausholen.

    In C geben Funktionen wie strpos -1 zurück wenn der Eintrag nicht gefunden wurde. Wenn man darauf nicht prüft und versucht das als Index zu verwenden trifft man auf ungültigen Speicher und das Programm fliegt einem um die Ohren (vermutlich. Es ist C. Weiß der Geier, was passiert. Ich bin mir ziemlich sicher dafür gibt’s mindestens Tools).

    In, sagen wir, Python werfen die equivalenten .index Methoden eine Exception wenn der Eintrag nicht gefunden wurde. Prüft man nicht darauf, fliegt einem das Programm um die Ohren.

    In PHP geben diese Funktionen FALSE zurück. Versucht man FALSE als einen Index zu verwenden oder sonst was damit zu machen, als es mit === zu vergleichen, konvertiert es PHP einfach klangheimlich zu 0. Das Programm fliegt einem nicht um die Ohren; stattdessen macht es das Falsche ohne Warnung, es sei denn man hat daran gedacht jedes Mal die richtige Boilerplate an jeder Stelle einzubinden, an der man strpos und bestimmte ähnliche Funktionen verwendet.

    Das ist grottig! Programmiersprachen sind Werkzeuge; sie sollen mit mir arbeiten. Hier hat PHP jedoch eine subtile Falle für mich gestellt, in die ich treten kann und ich muss selbst bei so banalen Sachen wie Stringoperationen und Gleicheitsprüfungen immer auf der Hut sein. PHP ist ein Minenfeld.

Ich habe vielerlei Geschichten über den PHP Interpreter und seinen Entwicklern von vielerlei Orten gehört. Diese stammen von Leuten, die am PHP Kern gearbeitet, ihn debuggt haben und mit den Kernentwicklern zu tun hatten. Keine einzige Geschichte war ein Kompliment.

Ich muss das hier jetzt anbringen, da man es nicht oft genug sagen kann: PHP ist eine Gemeinschaft von Stümpern. Nur sehr wenige der Leute die es designen, daran arbeiten oder Code darin schreiben scheinen zu wissen, was sie tun. (Natürlich bist du, lieber Leser, die seltene Ausnahme!) Diejenigen, die tatsächlich irgendwann durchsteigen, tendieren zu anderen Plattformen, was die Kompetenz des Kollektivs senkt. Und genau das ist das Problem an PHP: es ist ein Haufen Blinder, die Blinde anführen.

Okay, zurück zu den Fakten.

Operatoren

  • == ist nutzlos.
    • Es ist nicht transitiv. "foo" == TRUE und "foo" == 0… aber natürlich ist TRUE != 0.
    • == konvertiert falls möglich zu einer Zahl (123 == "123foo"… obwohl "123" != "123foo"), was bedeutet, dass wenn möglich nach Float konvertiert wird. Also ergeben Vergleiche großer hex Strings (wie etwa Passworthashes) manchmal true, selbst wenn sie es nicht sind. Nicht mal JavaScript macht das.
    • Aus irgendeinem Grund ergibt "6" == " 6", "4.2" == "4.20" und "133" == "0133". Merke jedoch, dass 133 != 0133, weil 0133 oktal ist. Aber "0x10" == "16" und "1e3" == "1000"!
    • === vergleicht Werte und Typen… außer bei Objekten, wo === nur true ergibt, wenn beide Operanden dasselbe Objekt sind! Bei Objekten bewirkt == eine Überprüfung der Werte (jedes Attributs) und Typ, was === für jeden anderen Typ macht. Was.
  • Bei Vergleichen sieht es auch nicht besser aus.
    • Es ist nicht mal konsistent: NULL < -1 und NULL == 0. Sortieren ist daher nicht deterministisch; es kommt dabei dann auf die Reihenfolge an, in welcher der Sortieralgorithmus die Elemente vergleicht.
    • Die Vergleichsoperatoren versuchen Arrays auf zwei verschiedene Arten zu sortieren: zuerst nach Länge, danach nach Elementen. Wenn diese dieselbe Anzahl an Elementen haben, jedoch unterschiedliche Sätze an Schlüsseln, sind sie nicht vergleichbar.
    • Objekte sind immer größer als alles andere… ausgenommen man vergleicht sie mit anderen Objekten, dann sind sie weder größer noch kleiner.
    • Als typ-sichereres == haben wir ===. Für ein typ-sichereres < haben wir… nichts. "123" < "0124", immer, egal was man macht. Casten nützt auch nichts.
  • Trotz dem obigen Wahnsinn und der expliziten Ablehnung von Perls Paaren von String- und numerischen Operatoren, überlädt PHP + nicht. + ist immer eine Addition und . eine Verkettung.
  • Der Indexoperator [] kann auch {} geschrieben werden.
  • [] kann mit jeder Variable benutzt werden, nicht nur Strings und Arrays. Es gibt null zurück und gibt keine Warnung aus.
  • Mit [] ist kein Slicing möglich; es empfängt nur individuelle Elemente.
  • foo()[0] ist ein Syntax-Fehler (behoben in PHP 5.4).
  • Ungleich (sprichwörtlich!) jeder anderen Sprache mit einem ähnlichen Operator ist ?: links assoziativ. Also gibt das hier:
    $arg = 'T';
    $Fahrzeug = ( ( $arg == 'B' ) ? 'Bus' :
                  ( $arg == 'A' ) ? 'Flugzeug' :
                  ( $arg == 'T' ) ? 'Zug' :
                  ( $arg == 'C' ) ? 'Auto' :
                  ( $arg == 'H' ) ? 'Pferd' :
                  'Fuesse' );
    echo $Fahrzeug;

    Pferd aus.

Variablen

  • Variablen können nicht deklariert werden. Variablen die nicht existieren werden bei der ersten Benutzung mit einem null Wert erstellt.
  • Globale Variablen benötigen eine global Deklaration bevor diese Benutzt werden können. Dies ist eine natürliche Konsequenz aus dem vorhergehenden Punkt, also wäre es vollkommen vernünftig, wäre da nicht die Tatsache, dass globale Variablen ohne eine explizite Deklaration nicht einmal gelesen werden können — PHP wird stattdessen klangheimlich eine lokale Variable mit demselben Namen erstellen. Ich kenne keine andere Sprache mit derartigen Problemen bei den Geltungsbereichen.
  • Es gibt keine Referenzen. Was PHP Referenzen nennt sind in Wirklichkeit Aliase; es gibt nichts was einem Rückschritt gleich kommt, wie Perls Referenzen und es gibt keine pass-by-object Identität wie in Python.
  • „Referenzierbarkeit“ infiziert eine variable wie nichts sonst in der Sprache. PHP ist dynamisch typisiert, also haben Variablen generell keinen Typ… ausgenommen Referenzen, die sich mit Funktionsdefinitionen, Variablensyntax und -zuweisung schmücken. Sobald eine Variable zu einer Referenz wird (was überall passieren kann) bleibt sie auch eine. Es gibt keinen offensichtlichen Weg das festzustellen und zur Dereferenzierung muss die Variable komplett gelöscht werden.
  • Okay, ich hab gelogen. Es gibt „SPL Typen„, die ebenfalls Variablen infizieren: $x = new SplBool(true); $x = "foo"; schlägt fehl. Das ist wie statische Typisierung, wisst ihr.
  • Eine Referenz kann mit zu einem Schlüssel genommen werden der nicht in einer undefinierten Variable existiert (welche zu einem Array wird). In ein nicht existierendes Array zu schreiben verursacht normalerweise einen Hinweis, das hier allerdings nicht.
  • Konstanten werden durch einen Funktionsaufruf definiert, der einen String entgegennimmt; davor existieren sie nicht. (Das könnte tatsächlich eine Kopie von Perls use constant Verhalten sein.)
  • Bei Variablennamen ist Groß- und Kleinschreibung zu beachten. Bei Funktions- und Klassennamen nicht. Das beinhaltet Methodennamen, was camelCase zu einer seltsamen Wahl für die Namensgebung macht.

Konstrukte

  • array() und ein paar dutzend ähnliche Konstrukte sind keine Funktionen. array alleine ist bedeutungslos, $func = "array"; $func() funktioniert nicht.
  • Array unpacking kann über die Operation list($a, $b) = ... bewerkstelligt werden. list() ist funktionsähnliche Syntax, genau wie bei array. Ich verstehe nicht, wieso hierfür keine eigene Syntax geschaffen oder warum der Name so irreführend gewählt wurde.
  • (int) ist offensichtlich entworfen um wie C auszusehen, aber es ist ein einzelnes Token; es gibt in PHP kein int. Versucht es ruhig: nicht nur, dass var_dump(int) nicht funktioniert, es wirft auch einen Parserfehler, weil der Parameter wie ein Operator zum Casten aussieht.
  • (integer) ist ein Synonym für (int). Daneben gibt es noch (bool)/(boolean) und (float)/(double)/(real).
  • Es gibt einen (array) Operator, um zu einem Array zu casten und einen (object) Operator um in ein Objekt zu casten. Hört sich vollkommen Banane an, verfolgt aber fast einen Zweck: man kann (array) benutzen, um einen Funktionsparameter zu haben, der entweder ein einzelnes Item oder eine Liste ist und ihn identisch behandeln. Mit der Ausnahme, dass man das nicht verlässlich durchführen kann, denn wenn jemand ein einziges Objekt übergibt und es zu einem Array castet, enthält dieses Array die Attribute des Objekts. (In ein Objekt zu casten führt die umgekehrte Operation aus.)
  • include() und Co. sind im Prinzip #include wie man es aus C kennt: sie laden eine andere Quelldatei in die aktuelle. Es gibt kein Modulsystem, selbst für PHP Code.
  • So etwas wie verschachtelte oder logische Geltungsbereiche in einer Funktion oder Klasse gibt es nicht. Sie sind nur global. Eine Datei zu inkludieren bewirkt, dass ihre Variablen in den Geltungsbereich der aktuellen Funktion geladen werden (und ihr damit Zugriff auf dessen Variablen erlaubt), aber ihre Funktionen und Klassen in einen globalen Gültigkeitsbereich lädt.
  • Elemente an ein Array anzuhängen funktioniert mit $foo[] = $bar.
  • echo ist ein Ausdruck, keine Funktion.
  • empty($var) ist sowas von keine Funktion, dass alles außer einer Variable, z. B. empty($var || $var2) einen Parserfehler verursacht. Warum zum Geier muss der Parser über empty Bescheid wissen? (Behoben in 5.5)
  • Es gibt redundante Syntax für Blöcke: if (...): ... endif;, etc.

Fehlerbehandlung

  • PHPs einziger eigener Operator ist @ (eigentlich von DOS ausgeliehen), der Fehler unterdrückt.
  • Fehler in PHP stellen keine Stacktraces zur Verfügung. Man muss sich einen Handler dafür installieren. (Was für fatale Fehler aber nicht funktioniert — siehe weiter unten.)
  • PHP Parserfehler rotzen einfach nur den Parserstand hin und mehr nicht, vergessene Anführungszeichen sind also ein Graus zu debuggen.
  • Der Parser von PHP bezieht sich z. B. bei :: intern auf T_PAAMAYIM_NEKUDOTAYIM und beim << Operator auf T_SL. Ich sage zwar „intern“, aber wie oben angeführt, ist es das, was der Programmierer angezeigt bekommt, wenn :: oder << an der falschen Stelle auftauchen.
  • Das meiste der Fehlerbehandlung wird über eine geschriebene Zeile pro Fehler in den Server Logs abgewickelt, die niemand liest, und macht einfach weiter.
  • E_STRICT gibt’s, aber es scheint nicht viel zu verhindern und es gibt keine Dokumentation darüber, was es überhaupt macht.
  • E_ALL beinhaltet sämtliche Fehlerkategorien — außer E_STRICT. (Behoben in 5.4)
  • Es ist abstrus inkonsistent was erlaubt ist und was nicht. Ich weiß nicht wie E_STRICT hier mit reinspielt, aber das hier geht in Ordnung:
    • Versuch, auf eine nicht existierende Objekt-Eigenschaft zuzugreifen, d. h. $foo->x. (Warnung)
    • Eine Variable als Funktionsname, Variablenname oder Klassennamen zu benutzen. (Nichts)
    • Versuch, eine undefinierte Konstante zu benutzen. (Hinweis)
    • Versuch, auf eine Eigenschaft von etwas zuzugreifen, was kein Objekt ist. (Hinweis)
    • Versuch, einen Variablennamen zu benutzen, den es nicht gibt. (Hinweis)
    • 2 < "foo" (Silent)
    • foreach (2 as $foo); (Warnung)

    Und das hier nicht:

    • Versuch, auf eine nicht existierende Klassenkonstante zuzugreifen, d. h. $foo::x. (fataler Fehler)
    • eine String-Konstante als Funktionsname, Variablenname oder Klassennamen zu verwenden. (Parserfehler)
    • Versuch, eine undefinierte Funktion aufzurufen. (fataler Fehler)
    • Ein Semikolon am Ende eines Ausdrucks innerhalb eines Blocks oder der Datei vergessen. (Parserfehler)
    • list oder etwaige andere quasi-builtins als Methodennamen zu verwenden. (Parserfehler)
    • Den Rückgabewert einer Funktion indizieren, d. h. foo()[0]. (Parserfehler; okay in 5.4, siehe oben)

Es gibt noch eine Handvoll anderer guter Beispiele seltsamer Parserfehler in dieser Liste.

  • Die __toString Methode kann keine Exceptions werfen. Wenn man’s trotzdem versucht… äh, wirft PHP eine Exception. (Eigentlich einen fatalen Fehler, was annehmbar wäre, aber…)
  • Fehler und Exceptions in PHP sind grundverschieden. Sie scheinen in keinster Weise miteinander zu interagieren.
    • Fehler in PHP (interne und Aufrufe von trigger_error) können mit try/catch nicht abgefangen werden.
    • Ebenso werden von Exceptions keine Error Handler ausgelöst, die von set_error_handler installiert wurden.
    • Stattdessen gibt es set_exception_handler der sich um nicht abgefangene Exceptions kümmert, denn den Einsprungspunkt eures Programms in einen try Block zu packen ist im mod_php Modell unmöglich.
    • Fatale Fehler (z. B. new ClassDoesntExist()) können durch nichts abgefangen werden. Eine Menge harmloser Sachen schmeißen mit fatalen Fehlern nur so um sich und beenden euer Programm aus fragwürdigen Gründen. Shutdown Funktionen werden weiterhin ausgeführt, aber sie können keinen Stacktrace abrufen (sie laufen in der obersten Ebene) und können nicht feststellen, ob das Programm wegen eines Fehlers beendet oder weil das Programm zu Ende ausgeführt wurde.
    • Der Versuch ein Objekt mit throw in eine Exception zu werfen endet mit… einem Fatalen Fehler, keiner Exception.
  • Es gibt kein finally Konstrukt, was das Schreiben von Wrapper Code (Handler setzen, Code ausführen, Handler aufheben; monkeypatchen, testen, entmonkeypatchen) mühselig und schwierig macht. Trotz der Tatsache, dass die Objektorientierung und Exceptions größtenteils von Java kopiert wurden, ist das Absicht, denn finally „macht im Kontext zu PHP nicht viel Sinn“. Hä? (Behoben in 5.5)

Funktionen

  • Funktionsaufrufe sind anscheinend ziemlich happig.
  • Einige eingebaute Funktionen interagieren mit Funktionen, die Referenzen zurückgeben, auf äh, seltsame Weise.
  • Wie an anderer Stelle erwähnt, sind eine Menge Dinge, die wie Funktionen aussehen oder aussehen als sollten es Funktionen sein, eigentlich Sprachkonstrukte, die in Verbindung mit Funktionen nicht damit funktionieren.
  • Funktionsparameter können „Typankündigungen“ haben, was eigentlich statische Typisierung ist. Aber man kann nicht angeben, dass ein Funktionsparameter ein int, string, object oder ein anderer „Grundtyp“ ist, obwohl jede integrierte Funktion diese Art Typisierung einsetzt. Wahrscheinlich weil es int in PHP nicht gibt. (Siehe oben wegen int.) Man kann auch nicht auf pseudo-typ Dekorationen zurückgreifen, die in den integrierten Funktionen ebenfalls massig zum Einsatz kommen: mixed, number oder callback. (callable ist seit PHP 5.4 erlaubt.)
    • Deswegen erzeugt das hier:
      function foo(string $s) {}
      
      foo(&quot;Hallo Welt&quot;);

      folgenden Fehler:

      PHP Catchable fatal error:  Argument 1 passed to foo() must be an instance of string, string given, called in...
    • Wie euch vielleicht aufgefallen ist, ist die „Typankündigung“ hier gar nicht notwendig; es gibt keine string Klasse in diesem Programm. Erst wenn man es mit ReflectionParameter::getClass() versucht, um den Typ dynamisch zu untersuchen, dann wird PHP ankreiden, dass es die Klasse gar nicht gibt, was es unmöglich macht den Klassennamen in Erfahrung zu bringen.
    • Der Rückgabewert einer Funktion kann nicht angekündigt werden.
  • Die Parameter der aktuellen Funktion an eine andere zu übergeben (Bindung, nicht selten) wird erreicht über call_user_func_array('other_function', func_get_args()). Jedoch erzeugt func_get_args einen fatalen Fehler und beschwert sich darüber, dass es kein Funktionsparameter sein kann. Wie und warum kann das überhaupt eine Art Fehler sein? (Behoben in PHP 5.3)
  • Closures müssen jede Variable explizit angeben, die abgeschlossen sein soll. Warum kommt der Interpreter nicht selbst darauf? Verhunzt irgendwie das ganze Feature. (Okay, es kommt daher, dass eine Variable erst erstellt wird, wenn man sie das erste Mal benutzen möchte, es sei denn man gibt explizit etwas anderes vor.)
  • Abgeschlossene Variablen werden nach derselben Semantik „übergeben“ wie andere Funktionsparameter, d. h. Arrays, Strings, etc. werden als Wertparameter „übergeben“. Es sei denn man bedient sich &.
  • Da abgeschlossene Variablen im Grunde automatisch übergebene Parameter sind und es auch keine verschachtelten Geltungsbereiche gibt kann sich eine Closure auch nicht auf private Methoden beziehen, selbst dann nicht, wenn diese in einer Klasse definiert ist. (Womöglich in 5.4 behoben? Ungewiss.)
  • Es gibt keine benannten Parameter in Funktionen. Es wird von den Entwicklern sogar explizit abgelehnt, weil es „den Code unordentlicher macht“.
  • Funktionsparameter mit Defaults können vor Funktionsparametern ohne Defaults auftauchen, trotz dem dass die Dokumentation anführt, dass es sowohl schräg als auch sinnlos ist. (Warum ist es dann erlaubt?)
  • Überschüssige Parameter, die an eine Funktion übergeben werden, werden ignoriert (außer bei integrierten Funktionen, was einen Fehler auslöst). Bei fehlenden Parametern wird von einem null Wert ausgegangen.
  • „Variadische“ Funktionen erfordern Rumgemurkse mit func_num_args, func_get_arg, and func_get_args. Für sowas gibt es keine Syntax.

Objektorientierung

  • Die prozeduralen Teile sind an C angelehnt, aber die objektorientierten Teile an Java. Ich kann nicht genug betonen wie schrill das ist. Das Klassensystem ist um die low-level Java Programmiersprache entworfen, die naturgemäß und absichtlich eingeschränkter ist, als PHPs Zeitgenossen. Ich bin sprachlos.
    • Ich habe bis dato noch keine globale Funktion gefunden, die einen Großbuchstaben hat, doch bedeutende integrierte Klassen benutzen camelCase für ihre Klassennamen und haben Java-ähnliche Zugriffsmethoden wie etwa getFoo.
    • Perl, Python und Ruby haben alle eine Art Konzept, um über Code auf „Eigenschaften“ zuzugreifen; in PHP hat man nur das klobige __get und Co. (Die Dokumentation redet hierbei unverständlicherweise von „überladen“ bei solchen besonderen Methoden.)
    • Klassen haben etwas wie Variablen-Deklarationen (var und const) für Klassenattribute, der prozedurale Teil der Sprache hingegen nicht.
    • Trotz des großen Einflusses von C++/Java, in denen Objekte ziemlich undurchsichtig sind, behandelt PHP Objekte oft wie ausgefallene Hashes — foreach ($obj as $key => $value) iteriert beispielsweise durch jedes zugängliche Attribut des Objekts.
    • Klassen sind keine Objekte. Jegliche Metaprogrammierung muss sich über einen Stringnamen auf sie beziehen, wie Funktionen.
    • Integrierte Typen sind keine Objekte und können (im Gegensatz zu Perl) auch in keinster Weise so geschrieben werden, dass sie wie welche aussehen.
    • instanceof ist ein Operator, trotz der Tatsache, dass Klassen der Sprache erst spät hinzugefügt wurden und das meiste auf Funktionen und funktionsähnlicher Syntax aufgebaut ist. Einflüsse von Java? Klassen nicht first-class? (Keine Ahnung ob sie’s sind.)
      • Jedoch gibt es eine is_a Funktion. Mit einem Parameter, der bestimmt, ob es einem Objekt erlaubt ist ein String zu sein, der eine Klasse benennt.
      • get_class ist eine Funktion; es gibt keinen typeof Operator. Genauso wie is_subclass_of.
      • Das funktioniert jedoch nicht mit integrierten Typen (es gibt ja schließlich kein int). Dafür benötigt man is_int, etc.
      • Weiterhin muss auf der rechten Seite eine Variable oder ein String-Literal stehen; es kann kein Ausdruck sein. Das verursacht nämlich… einen Parserfehler.
    • clone ist ein Operator?!
    • Auf Objektattribute greift man über $obj->foo zu, Klassenattribute hingegen mit Class::$foo ($obj::$foo wird versuchen $obj zu einem String zu machen und es als Klassenname benutzen.) Auf Klassenattribute kann man nicht über Objekte zugreifen; die Namespaces sind völlig aufgeteilt, was Klassenattribute für Polymorphie völlig unbrauchbar macht. Natürlich sind Klassenmethoden von dieser Regel ausgenommen und können wie jede andere Methode aufgerufen werden. (Ich habe mir sagen lassen, dass C++ das auch so handhabt. C++ ist jedoch ein schlechtes Beispiel für gut umgesetzte Objektorientierung.)
    • Des Weiteren kann eine Instanzmethode weiterhin statisch aufgerufen werden (Class::method()). Macht man das von einer anderen Methode aus wird es wie ein normaler Methodenaufruf des aktuellen $this behandelt. Glaube ich.
    • new, private, public, protected, static, etc. Ein Versuch Java Programmierer zu gewinnen? Mir ist bewusst, dass das mehr persönlicher Geschmack ist, aber ich verstehe nicht, warum so etwas in einer dynamischen Sprache nötig ist — in C++ geht es meist um Kompilierung und Namensauflösung zur Kompilierzeit.
    • PHP bietet first-class Unterstützung für „abstrakte Klassen“, womit nicht-instanziierbare Klassen gemeint sind. In vergleichbaren Sprachen wird das durch werfen einer Exception im Konstruktor erreicht.
    • Unterklassen können keine privaten Methoden überschreiben. Unterklassen-Überschreibungen können die öffentlichen Methoden der Oberklassen nicht mal sehen, geschweige denn aufrufen. Das ist ziemlich problematisch für, sagen wir einfach mal, Pseudotests.
    • Methoden können bspw. nicht „list“ genannt werden, weil list() Spezialsyntax (und keine Funktion) ist und es den Parser durcheinander bringt. Eine Mehrdeutigkeit ist hier völlig sinnfrei und die Klasse zu monkeypatchen funktioniert fast genauso gut ($foo->list() ist kein Syntaxfehler).
    • Wird eine Exception geworfen während die Argumente eines Konstruktors ausgewertet werden (z. B., new Foo(bar()) und bar() wirft) wird nicht der Konstruktor aufgerufen, sondern der Destruktor (behoben seit PHP 5.3).
    • Exceptions in __autoload und Destruktoren verursachen fatale Fehler (behoben seit PHP 5.3.6. Nun kann ein Destruktor praktisch von überall aus eine Exception werfen, da er aufgerufen wird sobald der Referenzzähler die Null abschneidet. Hmm.)
    • Es gibt keine Konstruktoren oder Destruktoren. __construct ist ein Initiator, ähnlich Pythons __init__. Es gibt keine Methode die man aufrufen kann, um Speicher zuzuordnen und ein Objekt anzulegen.
    • Es gibt keinen Standardinitiator. Der Aufruf von parent::__construct() führt zu einem fatalen Fehler, wenn die Oberklasse ihr eigenes __construct nicht definiert.
    • Die Objektorientierung bringt eine Schnittstelle für Iteratoren mit sich, die von der eigentlichen Sprache abweichen (z. B. for...as), jedoch implementiert kein Bestandteil (etwa Arrays) diese Schnittstelle. Will man ein Array iterieren, muss man es in einen ArrayIterator packen. Es gibt keine Art Iteratoren zu verketten oder aufzuteilen oder anderweitig mit ihnen als first-class Objekt zu interagieren.
    • Schnittstellen wie Iterator reservieren sich einige Methodennamen ohne Präfixe. Wenn die eigene Klasse iterierbar sein soll (ohne das Standardverhalten des Iterierens sämtlicher Attribute), dabei aber handelsübliche Methodennamen wie key, next, oder current benutzen möchte, tja, Pech.
    • Klassen können überladen wie sie Strings konvertieren und wie sie sich beim Aufruf verhalten, aber nicht wie sie Zahlen in jeden anderen eingebauten Typ umwandeln.
    • Strings, Zahlen und Arrays haben alle eine Stringumwandlung; die Sprache stützt sich zu einem beträchtlichen Teil darauf. Funktionen und Klassen sind Strings. Versucht man jedoch ein eingebautes oder selbstdefiniertes Objekt (oder sogar eine Closure) in einen String umzuwandeln verursacht dies einen Fehler, wenn es __toString nicht definiert. Sogar echo wird dadurch potentiell fehleranfällig.
    • Es besteht keine Möglichkeit bei Gleichheit oder Sortierung zu überladen.
    • Statische Variablen innerhalb von Instanzmethoden sind global; sie teilen sich denselben Wert über sämtliche Instanzen der Klasse hinweg.

Standardbibliothek

Perl benötigt „ein bisschen Montage“. Bei Python sind die „Batterien enthalten“. PHP ist eine „Küchenspüle, aber aus Kanada und auf beiden Hähnen steht C„.

Allgemeines

  • Es gibt kein Modul-System. Man kann sich PHP Erweiterungen kompilieren, aber welche davon geladen werden wird durch die php.ini bestimmt und man kann lediglich bestimmen ob sie existieren (und ihren Inhalt in den globalen Namespace injizieren) oder nicht.
  • Da Namespaces ein neueres Feature sind, ist die Standardbibliothek nicht im geringsten unterteilt. Es existieren tausende von Funktionen im globalen Namespace.
  • Bröckchen der Bibliothek könnten inkonsistenter nicht sein.
    • Unterstrich versus keiner: strpos/str_rot13, php_uname/phpversion, base64_encode/urlencode, gettype/get_class
    • “to” versus 2: ascii2ebcdic, bin2hex, deg2rad, strtolower, strtotime
    • Objekt+Verb versus Verb+Objekt: base64_decode, str_shuffle, var_dump versus create_function, recode_string
    • Argumentreihenfolge: array_filter($input, $callback) versus array_map($callback, $input), strpos($heuhaufen, $nadel) versus array_search($nadel, $heuhaufen)
    • Präfixverwirrung: usleep versus microtime
    • Bei Funktionen, die nicht auf Groß-/Kleinschreibung achten, hängt deren Schreibweise davon ab, wo das i im Namen steht.
    • Etwa die Hälfte aller Array-Funktionen beginnen mit array_. Die Andere nicht.
    • htmlentities ist das Gegenteil zu html_entity_decode und umgekehrt, mit völlig unterschiedlichen Namenskonventionen.
  • Küchenspüle. Die Bibliothek enthält:
    • Bindings zu ImageMagick, Bindings zu GraphicsMagick (welches ein Fork von ImageMagick ist) und eine Handvoll Funktionen, um EXIF Daten zu untersuchen (was ImageMagick bereits kann).
    • Funktionen um bbcode zu parsen, eine sehr spezifische Art von Auszeichnungssprache, welche von einer Handvoll namenhafter Foren-Software benutzt wird.
    • Viel zu viele XML Pakete. DOM (objektorientiert), DOM XML (nicht), libxml, SimpleXML, „XML Parser“, XMLReader/XMLWriter und ein Dutzend mehr Akronyme, aus denen ich nicht schlau werde. Es gibt sicherlich ein paar Unterschiede zwischen den genannten und es steht jedem frei herauszufinden welche.
    • Bindings für zwei bestimmte Systeme zur Verarbeitung von Kreditkartendaten, SPPLUS und MCVE. Was?
    • Drei Arten auf MySQL Datenbanken zuzugreifen: mysql, mysqli, und dieses PDO Abstraktions-Gedöns.

C-Einflüsse

Dies verdient eine eigene Rubrik, da es so absurd ist, die Sprache jedoch so stark durchdringt. PHP gehört zu den höheren, dynamisch-typisierten Programmiersprachen. Dennoch besteht ein massiver Teil der Bibliothek noch immer aus hauchdünnen Wrappern um C APIs, was folgendes nach sich zieht:

  • „Out“ Parameter, selbst wenn PHP ad-hoc Hashes oder mehrere Argumente mit wenig Aufwand zurückgeben kann.
  • Mindestens ein Dutzend Funktionen, um den letzten Fehler eines Subsystems (siehe unten) abzufangen, obwohl PHP schon seit acht Jahren Exceptions kennt.
  • Warzen wie mysql_real_escape_string, obwohl es dieselben Argumente wie das kaputte mysql_escape_string hat, nur weil es Teil der MySQL C API ist.
  • Globales Verhalten für nicht-globale Funktionalität (wie etwa MySQL). Bei mehreren MySQL Verbindungen muss man allem Anschein nach jedem Funktionsaufruf einen Verbindungs-Identifikator mitgeben.
  • Die Wrapper sind wirklich, wirklich, wirklich dünn. Beispielsweise löst der Aufruf von dba_nextkey, ohne vorher dba_firstkey aufzurufen, zu einer Zugriffsverletzung.
  • Die Wrapper sind oft plattformspezifisch: fopen(directory, "r") funktioniert unter Linux, gibt jedoch false unter Windows zurück und generiert eine Warnung.
  • Es gibt eine Reihe von ctype_* Funktionen (z. B. ctype_alnum), die an die C Zeichen-Klassen Erkennungsfunktionen mit ähnlichem Namen geknüpft sind, wie etwa isupper.

Generische Programmierung

Gibt es nicht. Wenn eine Funktion auch nur zwei leicht voneinander abweichende Dinge erledigen können soll, hat PHP einfach zwei unterschiedliche Funktionen dafür.

Wie sortiert man rückwärts? In Perl macht man das per sort { $b <=> $a }. In Python könnte man das mit .sort(reverse=True). In PHP gibt es dafür eine separate Funktion namens rsort().

  • Funktionen die einen C Fehler nachschlagen: curl_error, json_last_error, openssl_error_string, imap_errors, mysql_error, xml_get_error_code, bzerror, date_get_last_errors, sonst noch was?
  • Funktionen die sortieren: array_multisort, arsort, asort, ksort, krsort, natsort, natcasesort, sort, rsort, uasort, uksort, usort
  • Funktionen die Text finden: ereg, eregi, mb_ereg, mb_eregi, preg_match, strstr, strchr, stristr, strrchr, strpos, stripos, strrpos, strripos, mb_strpos, mb_strrpos, plus der Variationen die Ersetzungen vornehmen.
  • Es gibt auch eine Menge Aliasse, die die Sache nicht besser machen: strstr/strchr, is_int/is_integer/is_long, is_float/is_double, pos/current, sizeof/count, chop/rtrim, implode/join, die/exit, trigger_error/user_error, diskfreespace/disk_free_space
  • scandir gibt eine Liste der Dateien in einem Verzeichnis zurück. Anstatt sie (potentiell nützlich) in Verzeichnis-Reihenfolge auszugeben, gibt die Funktion sie bereits sortiert zurück. Und es gibt ein optionales Argument, um sie in umgekehrter alphabetischer Reihenfolge auszugeben. Es gab anscheinend noch nicht genug Sortierfunktionen (in PHP 5.4 kam ein dritter Wert als Argument für die Sortierrichtung hinzu, der die Sortierung deaktiviert).
  • str_split zerteilt einen String in gleich große Stücke. chunk_split zerteilt einen String in gleich große Stücke und fügt sie danach wieder mit Trennzeichen zusammen.
  • Das Auslesen von Archiven erfordert einen separaten Satz an Funktionen, je nach Format. Es gibt sechs verschiedene Gruppen von Funktionen, alle mit unterschiedlichen APIs für bzip2, LZF, phar, rar, zip und gzip/zlib
  • Da der Aufruf einer Funktion mit einem Array als Argument so umständlich ist (call_user_func_array) gibt es Paare á la printf/vprintf und sprintf/vsprintf. Diese tun ein und dasselbe, nur dass die eine Funktion Argumente entgegen nimmt und die andere ein Array von Argumenten.

Text

  • preg_replace mit dem /e (eval) Flag schreibt eine Zeichenfolge mit Übereinstimmungen in einen Ersatz-String und wertet ihn erst danach aus.
  • strtok ist anscheinend nach der gleichnamigen C Funktion entworfen, was aus einer Vielzahl von Gründen eine schlechte Idee ist. Vergessen wir an dieser Stelle mal, dass PHP durchaus in der Lage ist ein Array zurückzugeben (was wiederum in C fummelig ist) oder ebender Hack den sich strtok(3) zunutze macht (den String direkt zu modifizieren) hier nicht zur Anwendung kommt.
  • parse_str parst einen Abfrage-String, ohne dass der Name darauf einen Rückschluss zulässt. Es verhält sich außerdem wie register_globals und dumpt jede Abfrage in den lokalen Gültigkeitsbereich als Variable, es sei denn, man übergibt ihm ein Array, dass es befüllen kann (natürlich gibt es nichts zurück).
  • explode weigert sich eine Zeichenkette ohne vorhandenes Trennzeichen aufzuteilen. Jede andere Implementierung einer solchen Funktion hat hierzu irgendein sinnvolles Standardverhalten; PHP hingegen hat hierfür eine völlig eigenständige Funktion, verwirrenderweise str_split genannt und beschrieben als „wandelt einen String in ein Array um“.
  • Um ein Datum zu Formatieren gibt es strftime, welches sich wie die C API verhält und die Sprachumgebung berücksichtigt. Es gibt außerdem noch date, welches völlig andere Syntax verwendet und nur in englischen Umgebungen funktioniert.
  • gzgetss — Get line from gz-file pointer and strip HTML tags.“ Ich brenne darauf zu erfahren, welche Verkettung von Ereignissen zur Konzeption dieser Funktion geführt haben.
  • mbstring
    • Es ist rein „multi-byte“, wenn es um Zeichensätze geht.
    • Funktioniert weiterhin mit normalen Strings. Hat einen einzigen globalen „default“ Zeichensatz. Einige Funktionen erlauben ein spezifisches charset, aber dann wird das auf sämtliche Argumente und den Rückgabewert übernommen.
    • Stellt ereg_* Funktionen bereit, die sind aber überholt. preg_* haben Pech gehabt, sie verstehen aber UTF-8 wenn man ihnen eine PCRE-spezifische Flag mitgibt.

System und Reflexion

  • Es gibt, im Allgemeinen, eine ganze Menge Funktionen, die die Grenzen zwischen Text und Variablen verwaschen. compact und extract sind nur die Spitze des Eisbergs.
  • Es gibt mehrere Arten in PHP tatsächlich dynamisch zu sein und auf den ersten Blick gibt es keine offensichtlichen Unterschiede oder relativen Vorteile zwischen ihnen. classkit kann selbstdefinierte Klassen modifizieren; runkit löst es diesbezüglich ab und kann sämtliche benutzerdefinierten Dinge modifizieren; Reflection* Klassen reflektieren das Meiste der Sprache; es gibt sehr viele individuelle Funktionen für das Reporting von Eigenschaften von Funktionen und Klassen. Sind diese Teilsysteme unabhängig voneinander, verwandt, überflüssig?
  • get_class($obj) gibt den Klassennamen eines Objekts zurück. get_class() gibt den Namen der Klasse zurück, aus der diese Funktion aufgerufen wurde. Wenn man mal dezent ignoriert, dass diese Funktion zwei radikal unterschiedliche Dinge tut: get_class(null)… verhält sich wie letzteres. Also kann man ihr nicht mal mit einem beliebigen Wert trauen. Überraschung!
  • Die stream_* Klassen erlauben die Implementierung von eigenen Stream Objekten, um mit fopen oder anderem Datei-Kram benutzt zu werden. „tell“ kann aus internen Gründen nicht implementiert werden (außerdem gibt es EINE MENGE Funktionen, die in dieses System involviert sind).
  • register_tick_function akzeptiert ein Closure Objekt. unregister_tick_function nicht; stattdessen wirft es einen Fehler und mault, dass die Closure nicht in einen String umgewandelt werden konnte.
  • php_uname gibt Informationen über das aktuelle Betriebssystem zurück. Es sei denn, es kann nicht feststellen worauf es läuft; dann gibt es das Betriebssystem zurück auf dem es gebaut wurde. Es gibt keine Auskunft darüber ob das der Fall ist.
  • fork und exec sind nicht integriert. Sie kommen nur mit der pcntl Erweiterung, die aber standardmäßig nicht enthalten ist. popen stellt keine pid bereit.
  • Der Wert von stat wird gecached.
  • Mit session_decode lässt sich ein beliebiger PHP Session String lesen, aber auch nur dann, wenn es bereits eine aktive Session gibt. Das Ergebnis wird dann nach $_SESSION gedumped, anstatt es zurückzugeben.

Sonstiges

  • curl_multi_exec verändert curl_errno bei einem Fehler nicht, sondern curl_error.
  • Die Argument-Reihenfolge von mktime ist: hour, minute, second, month, day, year.

Datenmanipulation

Programme sind nichts weiter als große Maschinen, die Daten verarbeiten und mehr Daten ausspucken. Viele Sprachen werden mit Augenmerk auf die Art der Daten entworfen, die sie manipulieren sollen, von awk über Prolog bis hin zu C. Wenn eine Sprache nicht mit Daten umgehen kann, kann sie gar nichts.

Zahlen

  • Integer sind vorzeichenbehaftet und 32-bit auf 32-bit Plattformen. Anders als bei allen von PHPs Zeitgenossen wird keine automatische Erweiterung zu bigint vorgenommen. Da wird man gerne mal von negativen Dateigrößen überrascht und Berechnungen funktionieren nicht mehr wie erwartet aufgrund der CPU-Architektur. Die einzige Option die sich einem für große Integer Werte anbietet besteht darin GMP oder BC Wrapper Funktionen zu benutzen (die Entwickler haben vorgeschlagen einen neuen, separaten 64-bit Typen hinzuzufügen. Das ist doch hirnrissig.)
  • PHP unterstützt oktale Syntax mit einer führenden 0, also ist z. B. 012 die Zahl zehn. Jedoch ist 08 die Zahl Null. Die 8 (oder 9) und jede darauf folgende Zahl verschwindet. 01c ist ein Syntaxfehler.
  • 0x0+2 erzeugt 4. Der Parser sieht die 2 sowohl als Hex-Literal als auch einen separaten Dezimal-Literal, also im Endeffekt als 0x002 + 2. 0x0+0x2 hat dasselbe Problem. Komischerweise ist 0x0 +2 immer noch 4, aber 0x0+ 2 ergibt 2. (Behoben in PHP 5.4. Aber gleichzeitig auch wieder kaputt in PHP 5.4, mit dem neuen 0b Literal Präfix: 0b0+1 ergibt 2.)
  • pi ist eine Funktion. Oder es gibt eine Konstante, M_PI.
  • Es gibt keinen Exponenten-Operator, nur die pow Funktion.

Text

  • Keine Unicode-Unterstützung. Nur ASCII funktioniert wirklich verlässlich. Es gibt, wie bereits weiter oben erwähnt, die mbstring Erweiterung, aber die stinkt.
  • Was bedeutet, die integrierten String-Funktionen auf UTF-8 formatierten Text anzuwenden kann diesen zerschießen.
  • Genauso gibt es kein Konzept für bspw. Vergleiche von Groß- und Kleinschreibung außerhalb von ASCII. Obwohl es vor case-insensitiven Versionen von Funktionen nur so wuchert erachtet keine davon é gleich zu É.
  • Man kann Schlüssel nicht mit Variablen-Interpolation in Anführungszeichen setzen, z. B. ist "$foo['key']" ein Syntaxfehler. Man kann die Anführungszeichen wieder entfernen (das würde überall sonst eine Warnung geben!), oder ${...}/{$...} benutzen.
  • "${foo[0]}" ist okay. "${foo[0][0]}" ist ein Syntaxfehler. Das $ innerhalb zu schreiben ist bei beidem in Ordnung. Schlechte Kopie einer ähnlichen Perl-Syntax (mit radikal anderer Semantik)?

Arrays

Oh, man.

  • Dieser eine Datentyp verhält sich wie eine Liste, ein geordneter Hash, eine geordnete Sammlung, Sparse List und manchmal wie eine komische Kombination dieser. Wie gut läuft das? Wie viel Speicher wird das brauchen? Keine Ahnung. Ist ja nicht so als hätte ich großartig eine Wahl.
  • => ist kein Operator. Es ist ein spezielles Konstrukt, das nur innerhalb array(...) und dem foreach Konstrukt existiert.
  • Negative Indizierung funktioniert nicht, da -1 genauso gültig als Schlüssel ist wie 0.
  • Obwohl es der Sprache einzige Datenstruktur ist, gibt es keine abgekürzte Syntax dafür; array(...) ist gekürzte Syntax. (PHP 5.4 bringt aber „Literale“, [...].)
  • Ähnlich unbegreiflich ist, dass Arrays mit einem E_NOTICE von Strings zu Array konvertiert werden.
  • Das => Konstrukt basiert auf Perl, was foo => 1 ohne Anführungszeichen erlaubt. (Darum existiert es überhaupt erst in Perl; ansonsten ist es nur ein Komma.) PHP gibt ohne Anführungszeichen eine Warnung aus; es ist die einzige Sprache in ihrer Nische, die über keine überprüfte Art verfügt, Hashes zu erstellen, deren Stringschlüssel nicht in Anführungszeichen gepackt werden müssen.
  • Array Funktionen verhalten sich oft perplex oder inkonsistent, da sie auf Listen, Hashes oder Kombinationen dieser angewendet werden. Schauen wir uns einmal array_diff an, welches „den Unterschied zwischen Arrays berechnet“.
    $first  = array(&quot;foo&quot; =&gt; 123, &quot;bar&quot; =&gt; 456);
    $second = array(&quot;foo&quot; =&gt; 456, &quot;bar&quot; =&gt; 123);
    echo var_dump(array_diff($first, $second));

    Was wird dieser Code bewirken? Wenn array_diff seine Argumente als Hashes behandelt, dann sind diese ganz klar verschieden; dieselben Indizes haben verschiedene Werte. Behandelt es sie aber als Liste, dann sind sie immer noch verschieden; die Werte sind in der falschen Reihenfolge.

    Tatsächlich betrachtet array_diff diese als gleich, da es sie als Sammlungen ansieht: es vergleicht nur Werte und ignoriert die Reihenfolge.

  • Auf ähnliche Weise hat array_rand das seltsame Verhalten sich zufällige Schlüssel herauszupicken, was nicht so hilfreich für den üblichsten aller Fälle ist, wenn man aus einer Ansammlung von Werten einen zufälligen Wert herauspicken muss.
  • Ungeachtet dessen, wie stark sich PHP Code auf die Erhaltung der Reihenfolge von Schlüssel verlässt:
    array(&quot;foo&quot;, &quot;bar&quot;) != array(&quot;bar&quot;, &quot;foo&quot;)
    array(&quot;foo&quot; =&gt; 1, &quot;bar&quot; =&gt; 2) == array(&quot;bar&quot; =&gt; 2, &quot;foo&quot; =&gt; 1)

    Ich überlasse es dem Leser herauszufinden was passiert, wenn die Arrays vermischt werden (Ich weiß es nämlich nicht.)

  • array_fill kann keine Arrays der Länge Null erstellen; stattdessen gibt es eine Warnung aus und gibt false zurück.
  • Alle der (zahlreichen…) Sortier-Funktionen fummeln direkt im übergebenen Array rum. Man kann keine sortierte Kopie erstellen; man muss das Array von Hand kopieren, anschließend sortieren und dann dieses verwenden.
  • Allerdings gibt array_reverse ein neues Array zurück.
  • Eine Liste sortierter Dinge und ein bisschen Zuordnung von Schlüsseln zu Werten hören sich doch total knorke an, um Funktionsargumente zu handhaben, aber nö.

Keine Arrays

  • Die Standardbibliothek beherrscht „Quickhash“, eine objektorientierte Implementierung „ausgewählter, stark typisierter Klassen“ um Hashes zu implementieren. Es gibt tatsächlich vier Klassen, jede davon mit einer anderen Kombination von Schlüssel- und Wertpaar-Typen. Es ist unklar, warum die integrierte Array Implementierung nicht für diese höchst alltäglichen Fälle optimiert wurde oder was die relative Performance ist.
  • Es gibt eine ArrayObject Klasse (die fünf verschiedene Schnittstellen implementiert) die ein Array wrappen und es sich wie ein Objekt verhalten lässt. Eigene Klassen können diese Schnittstellen implementieren. Sie haben aber nur eine handvoll Methoden, wovon gerade mal die Hälfte den integrierten Array-Funktionen ähnelt und integrierte Array-Funktionen wissen mit ArrayObject oder Array-ähnlichen Klassen nichts anzufangen.

Funktionen

  • Funktionen sind keine Daten. Closures sind eigentlich Objekte, aber normale Funktionen sind keine. Man kann sie nicht mal mit ihren blanken Namen aufrufen; var_dump(strstr) gibt eine Warnung zurück und nimmt an man meinte einen tatsächlichen String "strstr". Es gibt keine Möglichkeit zwischen einem beliebigen String und einer Funktions-„Referenz“ zu unterscheiden.
  • create_function ist im Grunde nur ein Wrapper für eval. Es erstellt eine Funktion mit einem regulären Namen und installiert sie global (damit sie der Garbage Collector in Ruhe lässt — bloß nicht in einer Schleife verwenden!). Diese weiß über den Geltungsbereich eigentlich gar nicht Bescheid, also ist es schon mal keine Closure. Der Name beinhaltet ein NUL Byte damit es niemals mit einer normalen Funktion in Konflikt gerät (weil der Parser von PHP auf ein NUL in einer Datei nicht klar kommt).
  • Eine Funktion mit dem Namen __lambda_func zu deklarieren macht create_function kaputt — die eigentliche Implementierung erreicht man durch erstellen der Funktion __lambda_func über eval und diese dann intern in den Namen der kaputten umzubenennen. Wenn __lambda_func schon existiert, kracht es schon bei eval mit einem fatalen Fehler.

Sonstiges

  • NULL inkrementieren (++) erzeugt den Wert 1. NULL dekrementieren (--) erzeugt NULL. Dekrementiert man einen String passiert damit nichts.
  • Es gibt keine Generatoren. (Behoben in 5.5. Wow. Sie haben im Grunde auch noch lediglich die ganze Python Generator API geklont. Beeindruckend. Jedoch ist $foo = yield $bar; ein Syntaxfehler; es muss $foo = (yield $bar) heißen. Seufz.)

Web framework

Ausführung

  • Eine einzige gemeinsam genutzte Datei, php.ini, steuert einen gewaltigen Anteil der Funktionalität von PHP und führt komplexe Regeln darüber ein, wann was und wo überschrieben wird. PHP Software, die an beliebige Systeme ausgeliefert werden soll, muss diese Einstellungen sowieso überschreiben, um ihre Umgebung zu normalisieren, was einen Mechanismus wie php.ini überflüssig macht.
    • PHP prüft auf eine php.ini an vielerlei Orten, es kann also gut sein (oder auch nicht…), dass sie die Konfiguration eures Hosts überschreibt. Nur eine dieser Dateien wird jedoch durch den Parser gejagt, also ist nach dem Überschreiben einiger Einstellungen noch lange nicht Feierabend.
  • PHP wird streng genommen als CGI ausgeführt. Jedes Mal, wenn eine Seite aufgerufen wird, erstellt PHP das ganze Teil neu bevor es ausgeführt wird. Nicht mal Entwicklungsserver für Python Toy Frameworks verhalten sich so.

    Dies hat zu einem ganzen Markt von „PHP Beschleunigern“ geführt, die nur einmal kompilieren und so die Geschwindigkeit von PHP auf die anderer Sprachen erhöht. Zend, die Firma hinter PHP, hat das zu ihrem Geschäftsmodell gemacht.

  • Für eine ziemlich lange Zeit gingen PHP Fehler standardmäßig direkt an den Client — vermutlich zur Hilfe bei der Entwicklung. Ich glaube das ist so nicht mehr ganz richtig, aber ich sehe immer noch sporadische mysql Fehlermeldungen am Anfang einer Seite.
  • PHP ist gespickt mit „easter eggs“ wie das erzeugen des PHP Logos mit dem richtigen Abfrage-Argument. Nicht nur, dass es beim Programmieren eurer eigenen Anwendung völlig irrelevant ist, es erlaubt auch festzustellen, ob ihr PHP benutzt (und vielleicht auch grob geschätzt in welcher Version), ungeachtet dessen wie viel mod_rewrite, FastCGI, reverse proxying oder Server: ihr auch konfiguriert.
  • Leerzeilen vor und nach Tags, auch in Bibliotheken, zählen als tatsächlicher Text und werden in die Rückantwort gepackt (oder verursachen „Header bereits gesendet“ Fehler). Eure Optionen beschränken sich entweder darauf strikt auf zusätzliche Leerzeilen am Ende jeder Datei zu verzichten (die nach dem schließenden ?> zählen nicht) oder das schließende ?> am Ende einfach weg zu lassen.

Verteilung

Die einfache Verteilbarkeit von PHP wird oft als eine der größten Vorzüge gesehen: einfach ein paar Dateien hinklatschen, das war’s. In der Tat ist das vergleichsweise um einiges einfacher als der ganze Prozess, den man mit Python, Ruby oder Perl durchläuft. Dennoch lässt PHP viel zu wünschen übrig.

Ich bin in jeder Hinsicht eher dafür Web-Anwendungen als App Server laufen zu lassen und einen Reverse Proxy dran zu packen. Der Aufwand ist minimal und die Vorteile mannigfaltig: man kann seinen Web- und App-Server separat verwalten, man kann so viele oder wenige App-Prozesse auf so vielen Maschinen laufen lassen wie man möchte ohne mehr Webserver zu benötigen, man kann die App mühelos als anderer Benutzer ausführen, man kann zwischen den Webservern hin- und herwechseln, man kann die App abschalten ohne den Webserver anrühren zu müssen, man kann seine App übergangslos verteilen indem man verändert worauf ein fifo zeigt, etc. Seine Anwendung an den Webserver zu schweißen ist absurd und nicht mehr zeitgemäß.

  • PHP ist fest an Apache gebunden. Es separat oder mit einem anderen Webserver betreiben zu wollen bedarf genauso viel (wenn nicht mehr) Rumgeblödel, als irgendeine andere Sprache zu installieren.
  • php.ini gilt für jede etwaige ausgefühte PHP Anwendung. Es gibt nur eine php.ini Datei und sie gilt global; arbeitet man auf einem geteilten Server und sie muss geändert werden oder man betreibt zwei Anwendungen, die unterschiedliche Einstellungen benötigen, hat man Pech; man muss alle nötigen Einstellungen auf einen gemeinsamen Nenner bringen und dann innerhalb der Apps selbst mit ini_set oder Apaches .htaccess spezialisieren. Sofern möglich. Und wow sind das eine Menge Orte die man überprüfen muss bevor man herausfindet woher eine Einstellung ihren Wert erhält.
  • Ebenso gibt es keinen einfachen Weg eine PHP Anwendung und ihre Abhängigkeiten vom Rest des Systems zu „abzukapseln“. Zwei Anwendungen die unterschiedliche Versionen einer Bibliothek oder sogar PHP selbst benötigen? Fangt schon mal an eine zweite Kopie von Apache hochzuziehen.
  • Der „bunch of files“ Ansatz, der einem beim Routing nebenbei bemerkt ziemlich auf den Sack gehen kann, bedeutet auch, dass man sehr vorsichtig beim white- und blacklisting seiner Sachen sein muss, denn die URL-Hierarchie ist gleichzeitig der gesamte Codebaum. Konfigurationsdateien und andere „partials“ benötigen C-ähnlichen Schutz, um nicht direkt geladen zu werden. Dateien von Versionskontrollen (z. B. .svn) benötigen Schutz. Mit mod_php ist alles auf dem Dateisystem ein potentieller Einsprungspunkt; bei einem App Server gibt es nur einen Einsprungspunkt und nur die URL bestimmt, ob er aufgerufen wird.
  • Man kann nicht einfach ein paar Dateien aktualisieren die im CGI-Stil laufen, es sei denn man hat Bock auf Abstürze und undefiniertes Verhalten, wenn Benutzer die eigene Seite während des Upgrades ansurfen.
  • Obwohl die Konfiguration von PHP auf dem Apache weitgehend „simpel“ ist, gibt es selbst hier subtile Fallstricke. Während die PHP Dokumentation SetHandler empfielt um .php Dateien als PHP auszuführen, funktioniert AddHandler genau so gut und Google spuckt zu letzterem auch doppelt so viele Suchergebnisse aus. Nachfolgend das Problem.Benutzt man AddHandler, gibt man Apache zu verstehen, dass „führe dies als php aus“ eine mögliche Art ist .php Dateien zu verarbeiten. Aber! Apache hat nicht dasselbe Verständnis zu Dateierweiterungen wie jeder andere Mensch auf der Welt. Es ist entworfen, bspw. index.html.en als sowohl Englisch als auch HTML zu erkennen. Für Apache kann eine Datei beliebig viele Dateierweiterungen gleichzeitig haben.

    Stellt euch vor, ihr habt ein Formular um Dateien in ein öffentliches Verzeichnis hochzuladen. Um sicherzustellen, dass jemand keine PHP Dateien hochlädt, prüft man einfach auf die .php Dateierweiterung. Alles was ein Angreifer nun tun muss ist eine Datei mit dem Namen foo.php.txt hochzuladen; der Uploader wird darin kein Problem sehen, aber Apache wird es als PHP erkennen und anstandslos ausführen.

    Das Problem hier ist nicht „den original Dateinamen zu verwenden“ oder „das nicht besser zu prüfen“; das Problem ist dass der Webserver konfiguriert ist, jeglichen Code auszuführen dem er über den Weg läuft — genau dieselbe Eigenschaft, welche PHP „einfach auszuliefern“ macht. CGI setzte noch +x voraus, das war wenigsten etwas, aber PHP macht das gar nicht erst. Und das ist nicht nur ein theoretisches Problem; ich habe mehrere Seiten gefunden, die mit diesem Problem live am Netz hängen.

Fehlende Features

Ich betrachte all diese auf unterschiedlichen Graden als kritisch, um eine Web Anwendung programmieren zu können. Es erscheint schlüssig, dass PHP, mit seinem großen Verkaufsargument eine „Sprache fürs Web“ zu sein, einige davon beherrschen sollte.

  • Kein Template System. Es gibt PHP an sich, aber nichts das wie ein großer Interpolator fungiert statt einem Programm.
  • Kein XSS Filter. Nein, „denk daran htmlspecialchars zu benutzen“ ist kein XSS Filter. Das hier ist einer.
  • Kein CSRF Schutz. Den muss man sich selbst zusammenschustern.
  • Keine generische standard Datenbank API. Dinge wie PDO müssen jede Datenbank API individuell wrappen, um die Unterschiede weg zu abstrahieren.
  • Kein Routing. Die Webseite sieht genau so aus wie das Dateisystem. Vielen Entwickler wurde vorgegaukelt mod_rewrite (und .htaccess im Allgemeinen) seien ein akzeptabler Ersatz.
  • Keine Authentifikation oder Authorisierung.
  • Kein Entwicklungs-Server. („Behoben“ in 5.4. Hat zur Content-Length Schwachstelle weiter unten geführt. Außerdem muss man seine ganzen Überschreibungsregeln an ein PHP Wrapper Teil portieren, weil kein Routing existiert.)
  • Kein interaktives Debugging.
  • Kein einheitlicher Verteilmechanismus; nur „kopiere alle Dateien auf den Server“.

Sicherheit

Sprachgrenzen

PHPs schlechter Ruf in puncto Sicherheit rührt mitunter daher, dass es beliebige Daten einer Sprache in eine andere packt. Das ist eine schlechte Idee. "// <![CDATA[
"
mag in SQL nichts heißen, in HTML aber sehr wohl.

Was das ganze noch schlimmer macht ist der häufige Schrei nach „Säuberung der Eingaben“. Das ist komplett falsch; man kann keinen Zauberstab schwingen und plötzlich sind alle Daten von Haus aus „sauber“. Man muss die Sprache sprechen: benutzt Platzhalter mit SQL, benutzt Argumentlisten beim Aufruf von Prozessen, etc.

  • PHP ermutigt geradezu „Säuberung“: es gibt eine komplette Daten Filter Erweiterung dafür.
  • Die ganzen addslashes, stripslashes und sonstige Slash-bezogener Unfug sind alles nur Nebelkerzen die mal voll Nüsse bringen.
  • Es gibt, soweit ich das erkennen kann, keinen Weg einen Prozess sicher zu erstellen. Man kann NUR einen String über die Shell ausführen. Entweder escaped man wie ein Irrer und hofft, dass die standard Shell richtig escaped oder man ruft pcntl_fork und pcntl_exec manuell auf
  • Sowohl escapeshellcmd als auch escapeshellarg haben in etwa dieselben Beschreibungen. Unter Windows funktioniert escapeshellarg nicht (weil es Bourne shell Semantik erwartet) und escapeshellcmd ersetzt einfach einen Haufen Interpunktion mit Leerzeichen, weil keiner Ahnung davon hat wie man auf der Windows cmd escaped (was einem klangheimlich alles kaputt machen kann).
  • Bei den anfänglich integrierten MySQL Bindings, welche immer noch breit eingesetzt werden, gibt es keine Möglichkeit Prepared Statements zu formulieren.

Bis zu diesem Tag empfiehlt die PHP Dokumentation zu SQL Injections völlig bekloppte Praktiken wie Typ-Überprüfung, Benutzen von sprintf und is_numeric und überall manuell mysql_real_escape_string oder addslashes hinzuklatschen (was „sich als hilfreich erweisen könnte“!). Nirgendwo wird etwas von PDO oder Parametrierung erwähnt, außer in den Kommentaren. Ich habe mich gegenüber einem PHP Entwickler darüber vor grob zwei Jahren beschwert, er war alarmiert, aber die Seite hat sich nie verändert.

Standardmäßig unsicher

  • register_globals. Es ist seit einiger Zeit standardmäßig deaktiviert und komplett passé seit 5.4. Ist mir schnurz. Das ist einfach nur peinlich.
  • include akzeptiert HTTP URLs. Genauso peinlich.
  • Magische Anführungszeichen. So nah an standardmäßig sicher und doch so fern davon das Konzept zu begreifen. Genauso peinlich.
  • Man kann, bspw. mit PHPs XML Support ein Netzwerk aussondieren indem man den allgegenwärtigen Support von Dateinamen als URLs missbraucht. Nur libxml_disable_entity_loader() kann das beheben und das Problem wird höchstens in den Kommentaren der Doku erwähnt.

(5.5 bringt eine just-do-it Passwort Hash Funktion, password_hash, was hoffentlich den selbstgedrehten Cryptocode reduziert.)

Kern

Der PHP Interpreter hatte selbst ein paar faszinierende Sicherheitsprobleme.

  • In 2007 hatte der Interpreter eine Schwachstelle durch einen Integer Overflow. Der Fix fing an mit if (size > INT_MAX) return NULL; und von da an ging es nur noch bergab. (Für alle die es mit C nicht so haben: INT_MAX ist der größte Integerwert, der in eine Variable passt. Ich hoffe der Rest erschließt sich euch.)
  • In jüngerer Zeit schaffte es PHP 5.3.7 eine crypt() Funktion einzuschleppen die es tatsächlich jedem erlaubte sich mit jedem Passwort anzumelden.
  • Der Entwicklungsserver von PHP 5.4 ist anfällig für einen Denial of Service Angriff, weil er den Content-Length Header nimmt (den jeder auf einen beliebigen Wert setzen kann) und versucht so viel Speicher zu belegen. Das ist keine so gute Idee.

Ich könnte noch mehr hervor buddeln, aber das Argument ist nicht dass es X viele Exploits gibt — in Software gibt es nun mal Bugs, passiert, egal. Die Beschaffenheit dieser ist erschreckend. Und ich habe diese nicht gesucht; sie sind die letzten paar Monate auf meiner Türschwelle gelandet.

Fazit

Einige Kommentare haben richtig angemerkt, dass ich kein Fazit habe. Und, na ja, ich hab auch keins. Wenn ihr bis hier unten gelesen habt, dann habt ihr mir wohl schon zugestimmt, bevor ihr begonnen habt zu lesen 🙂

Wenn ihr nur PHP kennt und neugierig auf etwas anderes seid, probiert es doch mal mit dem Python Tutorial und Flask für das Web Zeug. (Bin zwar kein großer Fan von dessen Template Sprache, aber es tut.) Es zerbröselt zwar die Teile eurer App, aber es sind immer noch dieselben Teile und sollten euch bekannt genug vorkommen. Vielleicht schreibe ich nochmal einen richtigen Beitrag dazu; eine stürmische Einführung in eine ganze Sprache und Web Stack hat hier nicht wirklich Platz.

Später oder für größere Projekte möchte man vielleicht Pyramid benutzen, was sich auf mittlerem Niveau befindet, oder Django, ein komplexes Ungeheuer, das sich gut für Seiten wie die von Django eignet.

Seid ihr keine Entwickler, habt aber aus irgendeinem Grund trotzdem alles gelesen, bin ich nicht eher zufrieden bis jeder auf der Welt Learn Python The Hard Way durchgegangen ist, also ran da.

Und dann gibt's noch Ruby on Rails und einige Mitstreiter die ich nie benutzt habe und Perl ist mit Catalyst noch gesund und munter. Lest Zeug, lernt Zeug, baut Zeug, tobt euch aus.

Danksagungen

Danke an folgende für die Inspiration:

Lasst mich wissen, ob ihr noch irgendwas beisteuern wollt oder ob ich (faktisch!) mit etwas falsch liege.
// ]]>

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s