Quersumme berechnen

Mitunter trifft man auf den Fall eine Quersumme, beispielsweise in Form einer Prüfziffer, berechnen zu müssen. In PHP ist das ein einfacher Vorgang, der ungefähr so aussehen kann:

var_dump(array_sum(str_split('123')));

Ein ziemlich übersichtlicher Einzeiler, der hinsichtlich Parameterprüfung sicher noch verfeinert und in eine Funktion gepackt werden kann. In MySQL ist eine Umsetzung schwieriger. Die String-Funktionen bieten keinen Pendant zu PHP str_split() an. Wer kurz  im Netz sucht, erhält in der Regel Lösungsansätze, die auf Stored Procedures und Schleifen aufbauen. Diese möchte ich an dieser Stelle zwar nicht schlecht reden. Dennoch wäre eine  Lösung wünschenswert, die einfach in einen Select eingebaut werden kann. Continue reading

Treffen der PHP UserGroup Erlangen-Nürnberg

Nach langer Zeit es ist wieder soweit: Ein Treffen unserer UserGroup Erlangen-Nürnberg steht wieder an. Das Thema diesmal:

PHP Code Quality – Vom Code-Chaos zu ordentlichen Sources

Wann?

Am 21.02.2013 um 19:00 Uhr.

Wo?

Veranstaltungsort ist wie immer der Coworking Space in Nürnberg; siehe auch http://coworking-nuernberg.de/

Beleuchtet werden sollen dabei Code smells bis hin zu den jeweiligen Tools, die beim Beseitigen jener helfen können.

Weitere Informationen zur Gruppe und zum Event finden sich unter: http://www.phpugen.de

Tückische Autoincrement-Werte in MySQL

Autoinkrementwerte gehören für Primärschlüsselfelder in Tabellen wie der Deckel zum Topf. Es geht zwar auch ohne – aber i.d.R. ist die Nutzung sinnvoll. Eindeutige Identifier sind das A und O jeglicher Referenzen zwischen Tabellen. Deshalb ist es auch wenig verwunderlich, wenn man eben auf diese Eindeutigkeit großen Wert legt.

Leider scheint MySQL die Wichtigkeit der Autoinkrementwerte nicht allzu ernst zu nehmen, denn jene Werte werden nicht etwa zusammen mit der Tabellendefinition auf der Festplatte abgelegt, sondern im Arbeitsspeicher. Damit lässt sich leicht verständlich nachvollziehen, was geschehen mag, wenn der Arbeitsspeicher entweder durch einen plötzlichen Systemausfall, wie beispielsweise bei einer Kernelpanic, oder aber auch bei einem geplanten Neustart aufgrund einer Software-Aktualisierung, plötzlich geleert wird:

Der letzte Autoinkrement-Wert kann verloren gehen!

Genauer gesagt wird bei einer gefüllten Tabelle (mind. ein Datensatz) ein

SELECT MAX(auto_increment_col) FROM tableXY FOR UPDATE;

ausgeführt und bei einer leeren Tabelle, der Autoinkrement-Wert auf 1 gesetzt (siehe [1]). Unabhängig davon, ob bereits einmal Datensätze und somit eindeutige Schlüssel in der Tabelle existiert haben.
“Was soll’s?” wird sich mancheiner denken. Nun, wie sieht es aber aus, wenn vorher Daten aus der Tabelle gelöscht wurden? Plötzlich entstehen nach dem Neustart die gleichen IDs noch einmal, die bereits vor dem Neustart generiert wurden. Das kann zu Problemen führen, wie wir im weiteren Textverlauf noch genauer analysieren werden.

Ein Beispiel zum Nachvollziehen:

mysql> CREATE TABLE `A` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO A VALUES();
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO A VALUES();
Query OK, 1 row affected (0.01 sec)

mysql> SHOW CREATE TABLE A\G
*************************** 1. row ******************
       Table: A
Create Table: CREATE TABLE `A` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

Es wurde eine Tabelle mit einem Autoinkrementfeld namens “id” erstellt und zwei Datensätze wurden eingefügt. Die Abfrage des nächsten Autoinkrement-Wertes über SHOW CREATE TABLE liefert demnach den Wert drei. Nun löschen wir den letzten Datensatz:

mysql> DELETE FROM A WHERE id = 2;
Query OK, 1 row affected (0.01 sec)

mysql> show create table A\G
*************************** 1. row ******************
       Table: A
Create Table: CREATE TABLE `A` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

Der Autoinkrement-Wert bleibt nach dieser Aktion stabil und verharrt auf dem Wert drei. Führt man jedoch einen Neustart des Servers durch, indem man beispielsweise das Kommando

sudo service mysql restart

unter Ubuntu nutzt, sieht das Resultant anders aus:

mysql> show create table A\G
*************************** 1. row ******************
       Table: A
Create Table: CREATE TABLE `A` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

Urplötzlich ist von zwei die Rede. Gleiches gilt übrigens, wenn der Truncate-Befehl genutzt wird:

mysql> truncate A;
Query OK, 0 rows affected (0.01 sec)

mysql> show create table A\G
*************************** 1. row ******
       Table: A
Create Table: CREATE TABLE `A` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

Für MyISAM-Tabellen hingegen scheint das Problem nicht zu existieren.

Die Gefahren

Wann kann das besagte Verhalten zum Problem werden? I.d.R. kommt dies beim Archivieren von Daten vor. Man kopiert die Datensätze inkl. der Schlüssel in eine andere Tabelle. Wird eine ID nach einem Neustart erneut vergeben, schlägt die nächste Archivierung z.B. mit einem “Duplicate Key Entry” fehl.

Die Lösung

Man kann hier sicherlich nicht von der einen Lösung sprechen. Es gibt hierzu verschiedene Ansätze und jeder Betroffene mag es auf seine eigene Weise lösen. Eine Variante wäre sicher, statt die Daten in eine andere Tabelle zu verschieben, sie einfach über ein zusätzliches Feld als “deleted” zu markieren. Damit ist die Archivierung aber im Prinzip dahin und der Vorteil kleiner, schneller Tabellen nicht mehr gegeben.
Eine andere Variante ist, den Primärschlüssel beim Archivieren einfach nicht mit zu kopieren, sondern in der Archivtabelle neue zu vergeben. Dies ist jedoch keine besonders elegante Lösung, insbesondere dann, wenn irgendwo in der Datenbank doch noch “weiche” Verweise (also ohne DB-Foreign-Key) auf die Schlüssel existieren.

Vielversprechender scheint es zu sein, den Autoinkrement-Wert automatisch durch eine Stored-Procedure neu setzen zu lassen:

DELIMITER $$
DROP PROCEDURE IF EXISTS resetAutoIncrement;
CREATE PROCEDURE resetAutoIncrement()
BEGIN
  SELECT @autoIncrementValue:=MAX(id) FROM B;
  SET @query = CONCAT("ALTER TABLE A AUTO_INCREMENT = ", 
                      @autoIncrementValue);
  PREPARE stmt from @query;
  EXECUTE stmt;
  DEALLOCATE prepare stmt; 
END $$
DELIMITER ;

Die Prozedur könnte beim Systemstart beispielsweise über den “init-file”-Parameter in einer SQL-Datei ausgeführt werden (siehe [3]). Das Query auf Tabelle B (beispielsweise die Archivtabelle) kann bei Bedarf natürlich beliebig getauscht oder angepasst werden.

Quellen

[1] http://dev.mysql.com/doc/refman/5.0/en/innodb-auto-increment-handling.html

[2] http://dev.mysql.com/doc/refman/5.0/en/stored-programs-defining.html

[3] http://dev.mysql.com/doc/refman/5.0/en/server-options.html#option_mysqld_init-file

[4] http://stackoverflow.com/questions/2361982/create-table-new-table-name-like-old-table-name-with-old-table-names-auto-incre/2362599#2362599

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.
Continue reading

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.
Continue reading

Große Tabellen

Heutzutage werden die zu verwaltenden Datenmenge unbestritten immer größer. Viele datenverarbeitende Systeme sammeln und sammeln und sammeln… Außerdem müssen mittlerweile sehr oft auch Veränderungen von Daten aufgezeichnet werden. Dadurch wird die Datenmenge noch einmal deutlich größer. D.h. soll beispielsweise die alte Adresse eines Kunden nach einer Adressänderung weiterhin zur Verfügung stehen, muss sie zwangsläufig separat gespeichert werden. Die nachfolgenden Beispiele beziehen sich ausschließlich auf MySQL.
Continue reading

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.

Putty-Key in SSH-Key umwandeln

Nicht jeder will Putty nutzen. Manchmal erhält man aber nur einen Putty-Key für die Server. Also muss man diesen wohl umwandeln. Anleitungen für den umgekehrten Fall findet man häufig im Netz, diesen jedoch scheinbar nicht so häufig. Daher eine kurze Anleitung wie es funktioniert. Ihr benötigt dazu aber auf jeden Fall das Tool “puttygen.exe”. Soweit ich gesehen habe, existiert hierfür kein Linux-Pendant. Ich lasse mich in diesem Punkt aber gerne korrigieren. Nachdem ich kein Windows zur Verfügung hatte, habe ich Wine benutzt, um das Tool auszuführen:

wine puttygen.exe

Zu meinem Erstaunen hat das auch ohne Probleme funktioniert. Nun muss man nur noch ein Paar Schritte ausführen:

File->Conversions->Export OpenSSH Key->Key auswählen->Speichern

Das wars dann auch schon. Den Key kann man dann beispielsweise unter “./ssh/id_dsa” ablegen, oder aber auch per “ssh -i <pathtokey> <servername>” nutzen.

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!

DOAG in Nürnberg

Zwar muss ich eingestehen, dass ich nicht der größte O..-Fan bin, jedoch hat sich die Firma in den letzten Jahren Sun und MySQL einverleibt. Diese Tatsache kann man hinsichtlich Java- und MySQL-Themen nicht einfach ignorieren.

Eine aufgrunddessen nicht uninteressante Messe findet dieses Jahr  ganz in meiner Nähe in Nürnberg statt: Die DOAG Konferenz (Deutsche Oracle Anwender Gruppe) Den zugehörigen Link findet Ihr hier: http://www.doag.org/konferenz/2011-konferenz-streamdetails.php

Dort gibt es in der Rubrik “MySQL” einige interessante Vorträge.

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.

MySQL: Temporäre Tabellen

Manchmal kommt man um temporäre Tabellen einfach nicht herum. So kann es geschehen, dass man ein und die selbe Tabelle in einem Update und einer Unterabfrage  benötigt. Beispiel:

UPDATE `user`
SET `firstname` = 'Vorname', `lastname` = 'Nachname'
WHERE `id` IN (
  SELECT `id` FROM `user` WHERE `id` > 5 AND `id` < 10
)

Zugegebenermaßen hinkt die Sinnhaftikeit dieser Abfrage, was jedoch nicht unser Thema sein soll. Natürlich fällt eine Unterabfrage i.d.R. etwas komplexer aus. Beim Ausführen des obigen Queries erhält man folgende Fehlermeldung:

Error Code : 1093
You can’t specify target table ‘user’ for update in FROM clause

Mit einer temporären Tabelle lässt sich das Problem schnell lösen. Man schreibt zunächst alle relevanten Daten in eine temporäre Tabelle, führt das Update aus und löscht die Temp-Tabelle wieder. Das Ganze könnte dann etwasso aussehen:

CREATE TEMPORARY TABLE tmp ENGINE=INNODB
SELECT `id` FROM `user` WHERE `id` > 5 AND `id` < 10;

UPDATE `user`
SET `firstname` = 'Vorname', `lastname` = 'Nachname'
WHERE `id` IN (
  SELECT `id` FROM `tmp`
);
DROP TABLE `tmp`;

MySQL bietet hierfür eine besondere Syntax “CREATE TEMPORARY”. Die Tabelle ist dadurch nur für die aktuelle Verbindung sichtbar und wird automatisch wieder entfernt, wenn man die Verbindung schließt.

Vorsicht beim DROP TABLE!

Nicht schlecht soweit. Eine Besonderheit ist mir allerdings im Beisein von Transaktionen noch aufgefallen. Lässt man alle Statements innerhalb einer Transaktion ausführen und macht am Ende ein Rollback, so hat letzterer bei obigem Code keine Wirkung mehr.

START TRANSACTION;
...
ROLLBACK;

Warum? Werfen wir einen Blick in MySQL-Dokumentation zu dem Stickwort “DROP TABLE”: http://dev.mysql.com/doc/refman/5.1/de/drop-table.html

Denn auch bei DROP TABLE gibt es das zusätzliche Schlüsselwort “TEMPORARY”. Dieses bewirkt unter anderem, dass keine laufende Transaktion wie etwa beim normalen “DROP TABLE” beendet wird. Also updaten wir nochmal unser Skript:

START TRANSACTION;
CREATE TEMPORARY TABLE tmp ENGINE=INNODB
SELECT `id` FROM `user` WHERE `id` > 5 AND `id` < 10;

UPDATE `user`
SET `firstname` = 'Vorname', `lastname` = 'Nachname'
WHERE `id` IN (
  SELECT `id` FROM `tmp`
);
DROP TEMPORARY TABLE `tmp`;
ROLLBACK;

Die Datensätze in der Tabelle “user” dürfen durch die oberen Statements nun nicht mehr beeinflusst worden sein.

Fazit:

Wer temporäre Tabellen nutzt und diese im gleichen Atemzug wieder löscht, sollte innerhalb von Transaktionen peinlichst genau auf das Schlüsselwort “TEMPORARY” achten – sonst kann das ganz schön in die Hose gehen ;-)

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.