Kategorie-Archiv: PHP

PHP 5.4 – Neuerungen für Schreibfaule

Nachdem PHP 5.4 nun offiziell erschienen ist, möchte ich dieses Ereignis zum Anlass nehmen, ein paar neue Features zu beleuchten. Sicher, PHP 5.4 macht zwar nicht einen so gewaltigen Satz wie es damals von Version 5.2 auf 5.3 gelang. Dennoch gibt es viele interessante Neuerungen. Dabei stehen oft Traits im Vordergrund, die ich heute aber nicht behandeln möchte (siehe dazu: http://www.codenaschereien.de/php/traits-was-soll-das-alles/). Werfen wir also einen Blick auf die Kleinigkeiten, die uns als PHP-Programmierer zukünftig das Leben nicht nur in Sonderfällen, sondern im täglichen Doing, erleichtern sollen.
PHP 5.4 – Neuerungen für Schreibfaule weiterlesen

Das fehlende Synchronize

PHP bietet von Haus aus leider nur wenig Features für parallele Datenverarbeitung an. Nebenläufigkeit ist oftmals kein Thema, da sie häufig vom Webserver übernommen wird. Natürlich gibt es auch das Modul PCNTL (siehe http://de3.php.net/manual/en/ref.pcntl.php, das es erlaubt per pcntl_fork() Kind-Prozesse zu erstellen. Jedoch muss man auch hier schauen, wie man die Nebenläufigkeit synchronisiert bekommt. Stellt man sich z.B. einen Webservice vor, der eine kritische Funktion für einen Benutzer nur nacheinander ausführen darf, muss man zugegebenermaßen als PHP-Programmierer neidisch in die Java-Ecke blicken, in der Entwickler das Problem mit dem Schlüsselwort synchronize leicht lösen können. Zusätzlich hält die Java-Welt seit Version 5.0 mit dem Paket java.util.concurrent einen ganzen Sack weiterer Tools bereit, die einem das Leben erleichtern.
Das fehlende Synchronize weiterlesen

Viel Wind um Traits – was soll das alles?

Es ist nicht noch nicht lange her, da erschien die neue Beta 2 der PHP-Version 5.4. Diese beinhaltet die mittlerweile doch schon an einigen Stellen im Internet diskustierten Traits. Grund genug, damit ich mir die Sache auch einmal ansehe und aus meiner Perspektive beleuchte. Und nein, ich bringe nicht das Singleton-Beispiel. Dieser Artikel soll auch nicht die Funktionsweise von Traits erklären, sondern deren Sinnhaftigkeit und Anwendung diskutieren. Wer sich mit dem Thema Traits bisher noch nicht beschäftigt hat, ist deshalb mit dieser kleinen Einführung gut bedient: http://blog.itws.de/578/php-5-4-feautre-traits-horizontaler-code-reuse/

Ist das wirklich Mehrfachvererbung?

Man könnte der Meinung sein, Traits seien eine Art weiteres Konzept der Mehrfachvererbung. Während ich mir so meinen Kopf darüber zerbrochen habe, ob PHP diese Funkionalität in der Vergangenheit wirklich gefehlt hat, kam bei mir die Frage auf: Machen wir hier nicht einen Salto rückwärts? Immerhin unterstützt PHP bereits Interfaces und damit ein ausgereiftes Konzept zur Mehrfachvererbung. Mehr noch: Über die Jahre hat sich das Konzept der mehrfachen Klassenvererbung, wie es in C++, Perl oder Python ermöglicht wird, als problematisch erwiesen – hauptsächlich aufgrund von Mehrdeutigkeiten. Allen voran stößt man hierbei immer wieder auf das Diamond Problem. Klären wir also zunächst mal, ob man bei Traits wirklich von Mehrfachvererbung sprechen kann. Nehmen wir an, folgendes würde in PHP funktionieren:

class A {
  public function a() {
   return 'a';
  }
}

class B {
  public function b() {
    return 'b';
  }
}

class C extends A, B {}

$c = new C();
echo $c->a();
echo $c->b();

Obiger Code ist natürlich nicht lauffähig, wäre aber eine mögliche Syntax in PHP für klassenbasierte Mehrfachvererbung. Nun das ganze mit Traits. Die nachfolgende Syntax wird mit PHP 5.4 laufen:

trait A {
  public function a() {
    return 'a';
  }
}

trait B {
  public function b() {
    return 'b';
  }
}

class C {
  use A, B;
}

$c = new C();
echo $c->a();
echo $c->b();

Auf den ersten Blick könnte man Traits also das gleiche Verhalten konstatieren, wie mit einer klassenbasierten Mehrfachvererbung. Dem ist aber nicht so. Einen wichtigen Aspekt habe ich nämlich bisher nicht angesprochen: Die is_a-Beziehung. Um vollständig die Mehrfachvererbung zu simulieren, müsste das Beispiel daher so aussehen:

interface A {
  public function a();
}

interface B {
  public function b();
}

trait traitA {
  public function a() {
    return 'a';
  }
}

trait traitB {
  public function b() {
    return 'b';
  }
}

class C implements A, B {
  use traitA, traitB;
}

$c = new C();
echo $c->a();
echo $c->b();

var_dump($c instanceof A); //--> true
var_dump($c instanceof B); //--> true

Spätestens jetzt sollte jedem Leser klar sein, das Traits nicht mit dem Konzept der Mehrfachvererbung gleichzustellen sind. Die zwei Gründe dafür sind:

  1. Traits stellen keine Relationen zwischen Klassen dar. is_a-Beziehungen werden nicht beeinflusst.
  2. Es wird schlichtweg nichts von oben nach unten vererbt, sondern einfach stur horizontal kopiert. Man könnte Traits deshalb auch als Copy and Paste des Compilers ansehen.

Darüber hinaus muss man noch die Feingranularität erwähnen. Erbt man beispielsweise von einer richtigen Klasse, werden – zumindest in PHP – alle Eigenschaften und Methoden vererbt. Man bekommt also immer alles – ob man das nun möchte oder nicht. Mit Traits kann Funktionalität fein gesplittet werden, ohne dass man mehrere Basisklassen erzeugen müsste. Außerdem können bei der Übernahme von Methoden auch die Modifier geändert werden (z.B. von public auf protected). Zusätzlich können Traits auch nicht instaziiert werden. Sie stellen keine Objekte, sondern lediglich „Codeschnipsel“ dar.

Traits – ein alter Hut!?

Nachdem wir geklärt haben Traits künftig nicht mehr mit Mehrfachvererbung gleichzustellen, kann man aus jener Sicht auch nicht mehr behaupten, es sei ein Konzept der klassenbasierten Mehrfachvererbung, das aus C++ & Co kopiert wurde. Und dennoch: Eigentlich hat das C++ bereits Jahre lang in Form des Präprozessors. Nachfolgend das obige Trait-Beispiel in C++:

#include <iostream>

#define A char a() { return 'a'; };
#define B char b() { return 'b'; };

class C {
  public:
    A
    B
};

int main() {
  C c;
  std::cout << c.a() << std::endl;
  std::cout << c.b() << std::endl;
  return 0;
}

Außerdem gab es bereits zahlreiche Sprachen vor PHP, die Traits einführten. In diesem Zusammenhang stößt man schnell auf Self oder Scala. Aber auch Sprachen wie Perl planen die Einführung von Traits – mit der künftigen Version 6. PHP folgt hier ehr einem alten Hut, als einem neuem Trend.

Alternativen?

Kann man das denn nicht auch anders machen? Mit includes/requires z.B.?

class B {
  require('./method.php');
}

Nein, das liefert einen Parser-Fehler und funktioniert daher nicht.
Und mit eval? Äh ja, diese Idee verwerfen wir gleich mal wieder, denn eval ist eval, langsam und wäre in Sachen Implementierung deutlich umständlicher. Dann hätten wir da noch Reflections. Diese sind meines Wissens im Moment aber höchstens dazu im Stande gerade einmal den Modifier einer Methode zu ändern – nicht aber ganze solche einem Objekt hinzuzufügen. Der grundlegende Unterschied dabei wäre außerdem das Kopieren der gewünschten Programmteile zur Laufzeit. Traits existieren zur Laufzeit nicht mehr – sie wurden vorher bereits vom Compiler aufgelöst. Allerdings kann man Traits über die Reflection-API zur Laufzeit abfragen. Dafür werden drei neue Methoden zur Verfügung gestellt:

ReflectionClass:: getTraitAliases()
ReflectionClass:: getTraitNames()
ReflectionClass:: getTraits()

Verlassen wir damit den Absatz der verrückten Ideen und kommen zu den Problemen mit Traits.

Probleme und Eigenheiten im Umgang mit Traits

Namenskonflikte stehen für mich unter einem besonderem Licht. PHP bietet hierfür eine Erweiterung der use-Direktive an. Innerhalb von use A,B,C,… { … } kann eine Methode oder Eigenschaft entweder per instead-Schlüsselwort direkt bestimmt, oder per as-Schlüsselwort auch umbenannt werden, um Konflikte zu vermeiden.

Oben hatte ich bereits kurz das Diamond-Problem angesprochen. Erbt eine Klasse von zwei anderen Klassen, die widerum ein und die selbe Basisklasse besitzen, muss irgendetwas entscheiden, welche Methoden der Ober-Ober-Klasse nun die richtigen sind. Auch wenn Traits nur eine horizontale Code-Kopie darstellen, können auch hier die gleichen Probleme auftreten, wenn man z.B. verschachtelte Traits nutzt. Daher sind die obigen Lösungen von Namenskonflikten unabdingbar. Beispiel:

<?php

trait A {
   public function a() {
     return 'a';
   }
}

trait B {
  use A;
}

trait C {
  use A;
}

class D {
  use B, C {
    B::a insteadof C;
  }
}

$d = new D();
echo $d->a();

Ein anderer Sachverhalt, den man sich bewusst machen sollte: Traits überschreiben gleichnamige Methoden einer Oberklasse:

trait T {
  public function a() {
    return 'a in T';
  }
}

class A {
  public function a() {
    return 'a in A';
  }
}

class B extends A {
  use T;
}

$b = new b();
echo $b->a(); //-> A in T

Dies ist eigentlich selbstverständlich, wenn man ein wenig darüber nachdenkt. Immerhin wird nichts anderes gemacht, als eine neue, gleichnamige Methode in die erbende Klasse eingefügt. Würde man das manuell durchführen, wäre das Ergebnis das gleiche. Was aber, wenn man die Methode direkt in der eigenen Klasse hat, die auch das Trait benutzt? In diesem Fall wird das Trait einfach überschrieben:

trait T {
  public function a() {
    return 'a in T';
  }
}

class B {
  use T;
  public function a() {
    return 'a in B';
  }
}

$b = new b();
echo $b->a(); //-> A in B

Brauchen wir das eigentlich?

An diesem Absatz scheiden sich einmal wieder die Geister. Man findet sich fast ein wenig in jene Zeit zurückversetzt, in der PHP-Programmierer über Sinn- und Zweck von Template-Engines wie Smarty lange diskutierten, oder den Nutzen der Einführung von Goto debattierten. Traits sind sicherlich ein interessantes und vor allem hilfreiches Feature, wenn es darum geht Code nicht zu duplizieren. Nun kann man sagen, wer seinen Code nicht duplizieren möchte, kann diesen auch einfach auslagern und wiederverwenden:

class A {
  public static function a() {
    return 'a';
  }
}

class B {
  public function a() {
    return A::a();
  }
}

class C {
  public function a() {
    return A::a();
  }
}

Wie im Beispiel gezeigt, funktioniert das eigentlich recht einfach. Allerdings benötigt man dafür mehr Code, als mit Traits. Und bei Zugriff auf klasseninterne Variablen muss man diese entweder übergeben, oder sich etwas Komplexeres ausdenken. Dazu kommt, für jede übernommene Methode muss man zumindest den Rumpf implementieren – eine stupide Angelegenheit. Traits nehmen uns also viel Arbeit ab.

Fazit

Das Ziel von Traits ist die leichte Wiederverwendung von Code und das Ersparen von Schreibarbeit. Durch die Trennung von Code-Kopierung und is_a-Relationen ist man flexibler als früher. Dazu kommt, dass man sehr feinkörnig bestimmen kann, welche Methoden man wiederverwenden möchte. Im Hinterkopf sollte man aber immer die möglichen, problematischen Konstellationen haben. Traits sind wahrscheinlich Fluch und Segen zugleich. Wie auch bei der klassenbasierten Mehrfachvererbung kann leicht unwartbarer Programmcode entstehen. Letztendlich liegt es also in der Verantwortung jedes Programmierers zu entscheiden, ob das neue PHP-Feature in seinem Code Sinn macht – oder eben nicht.

Weiterführende Links

Probleme mit Umlauten und Sonderzeichen

Ein Problem, auf das zahlreiche Entwickler immer wieder stoßen, sind Umlaute. Wer kennt sie nicht – Zeichen wie ö, ä oder ü? Viele von uns haben schon einmal PHP-Code gesehen, in dem Entwickler mittels utf8_encode() oder utf8_decode() versucht haben, irgendwie den Zeichensatz zurechtzubiegen, ohne genau zu wissen was sie da eigentlich machen, oder weshalb die seltsam anmutenden Zeichen entstanden sind. Oft treten die Probleme auch nach einem Umzug der Datenbank auf. Beispielsweise wenn ein Content Management System auf einen neuen Server portiert werden soll. Erläutern wir also kurz wo die Probleme mit Umlauten/Sonderzeichen liegen und wie man sie vermeiden kann.

Erst einmal sollte man sich verinnerlichen, welche Teile der Anwendung vom Encoding betroffen sind:

  • Dateisystem (Dateinamen & Pfade, nicht die Datei selbst)
  • Dateien (PHP-Dateien genauso wie Datendateien)
  • Datenbank
    • Die Verbindung zur Datenbank
    • Standard-Encoding für die Datenbank
    • Standard-Encoding für die Tabelle
    • Encoding für die Zelle
  • Webserver
  • Encoding der Ausgabe des PHP-Interpreters
  • Standard-Encoding des Browsers
  • Encoding im HTTP-Header (z.B. „charset=utf-8“)
  • Angegebenes Encoding in der entstandenen HTML-Ausgabe (per meta-Tag)
  • Benutzer-Eingaben (z.B. aus Forms)

Diese Angaben beruhen natürlich auf der Annahme, dass man eine HTML-Datei erzeugen und diese per Webserver in einem Browser wieder ausgeben möchte. Versendet man beispielsweise E-Mails per PHP-Skript, so kann man in obiger Liste den Browser durch das Mail-Programm ersetzen und die HTML-Ausgabe fällt – zumindest bei Text-Mails – weg.

Ist das Encoding kaputt, muss man im schlimmsten Fall alle möglicherweise betroffenen Stellen prüfen. Bricht das Encoding in einem der vielen Schritte, ist es danach i.d.R. überall kaputt. I.d.R. deshalb, weil man natürlich mit Funktionen wie utf_encode() ggfs. die betroffenen Zeichen wieder geraderücken kann. Das ist aber nur die halbe Wahrheit. Sobald man sich nämlich aus dem westeuropäischen Zeichensatz herausbewegt, sind die Umlaute dann auch wieder kaputt. Das liegt an der intern fest kodierten Verwendung von ISO-8859-1, dem westeuropäischen Zeichensatz, innerhalb von utf8_encode/utf_decode.

In den letzten Jahren hat sich UTF-8 als neuer Standard im Web festgesetzt. Noch vor einiger Zeit bestimmten die ISO Codes 8859-xy maßgeblich die Zeichenkodierungen. Leider haben diese aber den Nachteil, dass sie immer nur einen ziemlich beschränkten Teil aller zurzeit verfügbaren Zeichen darstellen können und man deshalb in mehrsprachigen Systemen viele unterschiedliche Zeichensätze einsetzen musste. Mit der Einführung von UTF-8 benötigt man nur noch eine Kodierung.

Wie kann man per PHP auf das Encoding Einfluss nehmen?

Letztendlich kann ich nicht auf alle Webserver-Konfigurationen & Co. eingehen. Dahingehend konzentriere ich meine nachfolgenden Beispiele hauptsächlich auf PHP und MySQL.

Bereits in der php.ini gibt es die per default deaktivierte Option:

default_charset = "UTF-8"

Diese Option bewirkt in eingeschaltetem Zustand eine Erweiterung des Header-Eintrages „Content-Type: text/html“ um „;charset=UTF-8“. Für Apache-Nutzer: Gleiches kann man erreichen, indem man in der Datei „/etc/apache2/conf.d/charset“ die nachfolgende Zeile durch das Entfernen von „‚#“ aktiviert:

#AddDefaultCharset UTF-8

In beiden Fällen ist ein erneutes Einlesen der Konfigurationen (z.B. durch einen Neustart des Webservers) erforderlich. Sind übrigens beide Optionen aktiviert – also die in der php.ini und die in der Apache-Config, so gewinnt die von PHP.

Wer nach dem Aktivieren von einer der obigen Optionen noch immer Probleme mit dem Zeichensatz hat und der Text aus einer einfachen Datei kommt (dabei ist es irrelevant, ob der Text innerhalb der PHP-Datei oder einer separaten Datei steht) prüfen,

…ob der Header nun auch richtig gesendet wird. In Firefox erhält man diese Information beispielsweise über die rechte Maustaste und den Menüpunkt „Seiteninformationen anzeigen“ im Reiter „Header“. Dort kann der Punkt „Content-Type: text/html; charset=utf-8“ geprüft werden.

…ob die Datei auch im richtigen Encoding geschrieben wurde. Dabei ist es irrelevant, ob der auszugebende String innerhalb der PHP-Datei selbst, oder einer separaten Datei steht (von Datenbanken mal abgesehen – darauf gehe ich später ein).  In VIM kann UTF-8 beispielsweise mit folgendem Eintrag in der .vimrc erzwungen werden:

set fileencoding=utf-8

In praktisch allen aktuellen IDE’s und Texteditoren gibt es eine entsprechende Option, das Fileencoding einzustellen. Eine Ausnahme stellt hier wohl Netbeans dar, das es nur erlaubt, das Encoding für ein gesamtes Projekt zu bestimmen :-(. Wichtig zu wissen ist wahrscheinlich auch noch: Nur weil man durch Aktivieren einer der weiter oben beschriebenen Optionen im Header dem Content-Type ein charset-Attribut hinzufügt, bedeutet das nicht das automatische Konvertieren der Ausgabe und in unserem Fall somit der Datei. Die Datei muss bereits im richtigen Format vorliegen.

Bei der Verbindung zu MySQL sieht es anders aus. Hier wird bei der Angabe des Zeichensatzes auch automatisch konvertiert. Der entsprechende SQL-Befehl dafür lautet:

SET NAMES utf8

Natürlich muss man aber auch hier erst einmal sicherstellen, dass die Daten in der Datenbank im richtigen Format gespeichert wurden (genau wie bei Dateien auch) und dass zusätzlich auch das Feld das korrekte Encoding erhalten hat. Beispiel:

In einer Zelle steht ein gemischt-kodierter latin1- und utf8-String „üöäüöä“. Der erste Teil „üöä“ steht mit latin1 kodiert, der zweite „üöä“ mit utf-8 in der Datenbank innerhalb einer Zelle. Nun tätigen wir folgende Abfrage:

SET NAMES latin1; SELECT myfield FROM mytable;

Ergebnis: üöä��

Nun das Ganze in utf-8:

SET NAMES utf8; SELECT myfield FROM mytable;

Ergebnis: üöäüöä

Wenn also bereits der Inhalt einer Zelle nicht korrekt geschrieben wurde, kann auch kein „SET NAMES“ mehr helfen.

Fazit: Zeichensätze sollten über mehrere Systeme hinweg nicht gemischt werden. Angefangen beim Einlesen der Eingabedaten bis hin zur Ausgabe im Browser sollte der gleiche Zeichensatz beibehalten werden. Dann sind auch Funktionen wie utf8_encode() und utf8_decode vollkommen überflüssig. Wer diese benötigt, sollte sich fragen, ob es nicht sinnvoller ist die Quell-/bzw. Zieldaten gleich in UTF-8 zu halten. Wer kein UTF-8 benutzt und gleichzeitig mehrere Sprachen mit ihren verschiedenen Sonderzeichen unterstützten muss, sollte außerdem vorsichtig Kodierungen umgehen. UTF-8 nimmt einem hier eine Menge Arbeit ab.

Doctrine & Timestamps in Updates

Wenn man so am entwickeln ist, erfährt man manchmal seltsame Dinge. So habe ich heute versucht, ein datetime-Feld mit Doctrine Version 1 zu befüllen. Allerdings nicht mit NOW(), sondern mit einem noch zu berechnenden Wert. Das sah so aus:

Doctrine_Query::create()->update('Model_X')
      ->set(
        'datefield',
        'DATE_ADD(datefield, INTERVAL 1 MINUTE)'
      )
      ->where('ID = ?', $id)
      ->execute();

Es soll also einfach eine Minute auf das entsprechende Feld draufgerechnet werden. Leider funktioniert der obige Code nicht. Ersetzt man das execute() durch ein getSqlQuery(), so steht im erzeugten SQL-Statement nur noch DATE_ADD(datefield). Letzteres wiederum, ist keine gültige Syntax für MySQL.

Die Lösung ist so einfach wie trivial: Man nutzt einfach nicht DATE_ADD, sondern impliziete Typkonversion und lässt MySQL die Arbeit verrichten:

Doctrine_Query::create()->update('Model_X')
      ->set('datefield', 'datefield + INTERVAL 1 MINUTE')
      ->where('ID = ?', $id)
      ->execute();

Manchmal ist weniger eben mehr 😉

PHP 5.4 ohne Risiko testen

Viele PHP-User möchten zurzeit evtl. die neue, gerade als BETA zur Verfügung gestellte neue Version 5.4 testen. Doch wie stellt man das möglichst unkompliziert an, ohne eine bestehende PHP-Installation (z.B. lokal) kaputt zu machen oder, aufwendig eine eigene VM dafür zu installieren?

Zunächst lädt man sich eines der ZIP-Files herunter unter: http://downloads.php.net/stas/php-5.4.0beta1.tar.gz
Dann muss das ganze entpackt, konfiguriert und kompiliert werden. Nachfolgend die Linux-Befehle, die das der Reihe nach erledigen:

wget http://downloads.php.net/stas/php-5.4.0beta1.tar.gz
tar xzvf php-5.4.0beta1.tar.gz
cd php-5.4.0beta1
./configure --with-readline
make test

Während des ./configure-Vorgangs sind bei mir zwei Probleme unter Ubuntu aufgetreten:

  1. Das Paket libxml2-dev hat gefehlt.
  2. Das Paket libreadline-dev war nicht vorhanden.

Beide Probleme konnte ich mich einem einfachen apt-get install lösen:

sudo apt-get install libxml2-dev
sudo apt-get install libreadline-dev

Bei anderen Problemen, liefert der nachfolgende Link ggfs. die richtige Hilfestellung: http://www.robo47.net/text/6-PHP-Configure-und-Compile-Fehler#libxml

Nach dem ./configure steht das Kompilieren mit „make test“ an. Normalerweise käme nun noch der Schritt „make install“. Das habe ich aber bewusst weggelassen. Wer nur ein wenig herumtesten möchte benötigt das nicht, sondern macht sich damit ggfs. eher seine bestehende Installation kaputt. Außerdem kann man das kompilierte PHP 5.4 nun gut testen, indem man den interactive-Mode benutzt (Hierfür war das „–with-readline“ beim ./configure nötig). Wer mehr über diesen Modus, den es bereits seit PHP 5.1 gibt, erfahren möchte, ist hier richtig: http://php.net/manual/en/features.commandline.interactive.php

Die PHP Commandline-Binary findet sich nun im Verzeichnis sapi/cli/php. Wer nun einfach ein wenig herumtesten möchte, kann php mit dem Parameter -a in den interactive mode schalten:

$ ./php -a
Interactive mode enabled

php > echo PHP_VERSION . "\n";
5.4.0beta1
php >

Im Vergleich zu früheren Versionen bricht der interactive mode ab PHP 5.4 nicht mehr ab, wenn ein FATAL ERROR auftritt. Gerade wenn man testen möchte, kann einem das in älteren Versionen ziemlich auf den Geist gehen. Na dann viel Spaß beim Testen!

PHP String Klasse

PHP und UTF8-String sind wie im vorherigen Artikel beschrieben, immer wieder ein Hindernis. Daher dachte ich mir, warum nicht eine UTF-8 fähige String-Klasse schreiben? Gesagt getan. Hier kann man das Resultat begutachten:

https://github.com/codenaschereien/PHPString/

Das Ganze befindet sich natürlich noch im Anfangsstadium und ist ausbaufähig. Anregungen und Bugmeldungen sind jederzeit willkommen. 😉

Nun noch ein paar kleine Beispiele, die den Sinn der Klasse erläutern sollen:

<?php

$s = 'aöbc';
echo $s[2];

Das Ergebnis ist ein „?“, da das „ö“ an Position 2 mehr als nur ein Byte benötigt. Vorraussetzung dafür ist natürlich, dass man die PHP-Datei auch in UTF-8 speichert. Mit der String-Klasse könnte es nun so aussehen:

<?php

require_once('String.php');
$s = new String('aöbc');
echo $s[2];

Als korrekten Rückgabewert erhalten wir nun „b“. Instanzen der Klasse String können außerdem insgesamt wie ein Array behandelt werden. D.h. schreiben, lesen und sogar unset() funktionieren an allen validen Positionen. Darüber hinaus kann die String-Klasse im Gegensatz zu normalen Strings auch mit foreach iteriert werden:

<?php

require_once('String.php');
$s = new String('aöbc');
foreach($s as $char) {
  echo $char;
}

In diesem Fall hat man also gleich zwei Vorteile: Foreach- und UTF-8-Unterstützung.

PHP & Unicode

Es ist ein immer wieder heiß diskutiertes und oft gewünschtes PHP-Feature. Das „Traumpaar“ PHP und Unicode sollte mit PHP6 Einzug halten. Geschehen ist seitdem nicht sehr viel. PHP 6 wurde zu PHP 5.3 umbenannt und eine vollwertige Unicode-Unterstützung fiel erstmal raus 🙁

Nun müssen wir mit jenen Funktionen leben, die wir aktuell besitzen. Die Auswirkungen sind oftmals erst auf den zweiten Blick sichtbar.

<?php

  echo wordwrap('ä....');

Das obige Beispiel als Grundlage und als UTF8-kodierte Datei angenommen – wann wird wordwrap bei angenommenen 80 Zeichen in Form einzelner ä’s umbrechen?

Der Default-Wert liegt bei 75 Zeichen. Normalerweise sollte also nach 75 Zeichen ein Umbruch erfolgen. Dieser wird jedoch bereits viel früher eingefügt. Und zwar nach 40 Zeichen.

UTF8-Zeichen können im schlechtesten Fall bis zu 4 Byte belegen. Ein „ä“, wie in unserem Fall, benötigt 2 Zeichen. Nachdem wordwrap byteweise – und nicht etwa zeichenweise – zählt, wird der Umbruch viel zu früh eingefügt. Da können PHP-Entwickler wirklich nur neidisch in die Java-Ecke sehen, denn hier war die Unicode-Unterstützung von Anfang an dabei.

Das Problem äußert sich aber nicht nur bei der Funktion word_wrap. Im Prinzip tritt es überall auf, wo Zeichen gezählt und Strings verarbeitet werden.

Nun kennt man evtl. bereits die Multi-Byte-Funktionen von PHP. Deren Funktionsnamen beginnen mit „mb_“ wie beispielsweise mb_strlen(). Es ist also nicht ganz so schlimm um PHP bestellt, wie man zunächst glauben möchte. mb_strlen() kann mit UTF8-Strings umgehen und erkennt dann auch Umlaute mit der korrekten Länge.

Leider bietet die MultiByte-Erweiterung nicht alle gängigen, nötigen Funktionen an. „wordwrap()“ ist ein gutes Beispiel hierfür. Oftmals sind es aber genau jene Funktionen, die man nur allzugerne vergisst. Darüber hinaus reihen sich sprintf, vprintf und weitere in die Problemgruppe ein. Oftmals hilft dann nur die Eigenimplementierung unter Zuhilfenahme der MultiByte-Funktionen.

Fazit: Wer Strings in PHP verarbeitet und auf UTF8 setzt (beispielsweise i.d. Datenbank oder als PHP-Dateiformat), sollte Stringoperationen immer auch einmal mit Sonderzeichen durchtesten und wenn möglich die Multi-Byte-Extension in Betracht ziehen.

PHP: Zuweisungen in IF-Statements

Gegeben sei folgender Quelltext:

if ( $a == 1 ) {
  ...
}

Sind wir mal ehrlich. Wie oft geschieht es, dass man statt wie im obigen Fall nur ein Gleichheitszeichen im Eifer des Gefechts der Tastatur entlocken kann? Ein Syntaxfehler ist das nicht, denn es ist absolut gültiger PHP-Code.

Aus dem Vergleich wird plötzlich eine ungewollte Zuweisung. Auf eine Warnung seitens des Interpreters braucht man auch nicht zu hoffen, da kein lesender sondern schreibender Zugriff erfolgt! Einzig und allein aktuelle IDE’s monieren eine unschöne Syntax, die gemieden werden sollte. Wer nun das kleine Ausrufezeichen in Eclipse und Co. aber übersieht, sucht ggfs. lange nach dem Fehler.

Auch wenn es nur ein kleiner Hebel ist, aber die Lösung des Problems ist denkbar einfach: Man schreibt den Vergleich einfach immer in der anderen Reihenfolge:

if ( 1 == $a ) {
  ...
}

Damit kann es auch den schnellsten Schreibern nicht mehr gelingen, lauffähigen Code bei der Eingabe von nur einem Gleichheitszeichen zu erzeugen. Denn einem konstanten Wert kann nichts mehr zugewiesen werden.

Fazit:

Allgemein sollte man zuweisungen innerhalb von IF-Blöcken vermeiden – es gehört nicht zum guten Ton in der Programmierung. Wer sich hier in seiner Schreibweise umgewöhnt, macht sich das Leben leichter und muss nach weniger Fehlern suchen.

Optimierung von PHP-Tags

PHP bietet einige Möglichkeiten, die Skriptsprache im Code einzuleiten und zu beenden. Beispiele:

<?php /* long form */ ...?>

<? /* short form */ ?>

<% /* ASP-style */ %>

In den Fokus rücken, möchte ich dabei das End-Tag. Also z.B „?>“. In Dateien, die ausschließlich PHP-Code enthalten, ist dieses nicht nötig. Wo immer auch möglich, sollte man daher auf das schließende PHP-Tag verzichten, denn dieses hat einen gravierenden Nachteil. Stellen wir uns folgenden Code vor:

<?php
  require_once('test.php');
  echo a();

und die dazugehörige test.php:

<?php

  function a() { /* ... */ }

?>...

Die Punkte am Ende der Datei test.php sollen exemplarisch für Leerzeichen stehen. Genau jene können zum Problem werden, wenn die Ausgabe exakt dem Rückgabewert der Funktion a() entsprechen muss. Beispielsweise, wenn diese durch andere Software eingelesen und interpretiert wird. Die Leerzeichen am Ende werden nämlich eins zu eins bereits bei der Ausführung von „require_once(test.php);“ mit ausgegeben und sind somit Teil der Gesamtausgabe des Skripts. Dieses Verhalten nutzt man sogar oftmals bei der Ausgabe von HTML, wenn PHP und HTML gemischt in einer Datei stehen (z.B. in einem Template). Bei Leerzeichen am Ende einer PHP-Datei kann dieses Verhalten jedoch schnell zu einer langwierigen Fehlersuche führen (gerade wenn sich viele Includes durch mehrere Dateien ziehen). Fehlt das PHP End-Tag, erkennt PHP automatisch, dass der Sourcecode am Dateiende abgeschlossen ist. Leerzeichen am Ende sind dann kein Thema mehr. Sie werden bereits vor dem Syntax-Check vom Interpreter entfernt.

Fazit:

Der bewusste Verzicht auf PHP-Ende-Tags ist nützlich und hilft Fehlern vorzubeugen.

Zend-Framework: Layout im Admin-Modul umstellen

Falls man das Layout im Admin-Modul seiner Anwendung, die mit dem Zend-Framework entwickelt wurde, umstellen möchte, kann man bei Google einige Lösungsansätze finden. Ich habe nach einer möglichst einfachen gesucht. Herausgekommen ist Folgendes:

namespace A\B\C;

class Layout extends
  \Zend_Layout_Controller_Plugin_Layout {

  public function preDispatch(
    \Zend_Controller_Request_Abstract $request) {

    if('admin' === $request->getModuleName()) {
      $this->getLayout()->setLayout(
        $request->getModuleName()
      );
    }
}

Die Namespaces kann man bei Bedarf natürlich weglassen, wenn man diese nicht benötigt. Um das Ganze dann noch zu komplettieren, muss man in der Bootstrap der startMVC-Methode noch den Parameter „pluginClass“ mitgeben. Das kann dann beispielweise so aussehen:

$view->layout = Zend_Layout::startMvc(array(
  'layout' => 'site', //change to yours
  'viewSuffix' => 'tpl',
  'ViewBasePath' => APPLICATION_PATH . '/tpl/layouts/',
  'pluginClass' => 'A\B\Plugin\Layout'
))->setView($view);