Mehrsprachige Unternehmens-Anwendungen mit Zend Framework

Parlez-vous Zend?
Kommentare

In vielen Unternehmen gibt es PHP-Anwendungen, die aus Zeiten stammen, in denen Mehrsprachigkeit nicht wichtig erschien oder schwer zu implementieren war. Anhand eines Fallbeispiels soll gezeigt werden, wie eine Neuentwicklung mit modernen Technologien ablaufen kann und was es zu beachten gibt.

In einem deutschen Unternehmen mit 14 Standorten weltweit wurden über die Jahre zahlreiche Skripte zur Mitarbeiter-Verwaltung im Intranet entwickelt. Diese sollten durch eine neue, mehrsprachige Anwendung auf Basis des Zend Framework ersetzt werden.

Zusätzlich gibt es verschiedene andere Datenbanken, insbesondere die MySQL-Datenbank auf dem Web-Server, die im Rahmen von Export- und Import-Workflows berücksichtigt werden müssen. Die besonderen Herausforderungen lagen bei der notwendigen Datenmigration, der Integration in die heterogene Server-Umgebung, sowie der Anpassung an die komplexen Unternehmensstrukturen. Die bisherige Datenbank konnte die Beziehungen der Mitarbeiter zu den Standorten und den verschiedenen Tätigkeiten beispielsweise nur unzureichend abbilden. Daher konnte kein existierender Code übernommen werden, zumal nur ein Teil der Skripte in PHP vorlagen. Als Live-Server dient ein Microsoft IIS mit SQL Server 2005 und ADS zur Benutzerverwaltung. Die Entwicklung wurde ebenfalls unter Windows mit Apache 2 und Zend Studio durchgeführt. Unicode = UTF-8? Eines der Ziele von Unicode ist es die Implementierung von mehrsprachigen Anwendungen zu vereinfachen, indem man nur noch einen einzigen Zeichensatz berücksichtigen muss und nicht für jede Sprache einen eigenen, wie es bei ISO 8859 der Fall ist. Zudem gibt es schlicht Sprachen, deren Zeichen nicht alle mit einem Byte abgebildet werden können. Die Lösung ist zunächst einfach: Man verwendet mehrere Bytes und fasst sie zu einem Zeichen zusammen. Wie man bereits ahnt, gibt es dazu jedoch verschiedene Möglichkeiten. Das im Web sicher populärste Encoding ist UTF-8 [1], bei dem Zeichen eine variable Byte-Länge haben können. UTF steht für Unicode Transformation Format. Werden die zusätzlichen Bytes nicht benötigt, fallen sie einfach weg. Nachteil ist, dass man neue String-Funktionen mit entsprechend anderen Algorithmen benötigt (in PHP die Multibyte-Extension mit dem Funktions-Präfix „mb_“), damit Zeichen beispielsweise nicht versehentlich in der Mitte getrennt werden und von strlen() die korrekte Zeichen-Anzahl zurückgeliefert wird. Vorteil ist, dass die damit verwendeten Funktionen und Protokolle nicht binärsicher sein müssen und man einen relativ geringen Speicherverbrauch hat, wenn hauptsächlich normale ASCII-Zeichen [2] vorkommen – in diesem Fall bleibt es bei einem Byte pro Zeichen. Außerdem hat man Zugriff auf den vollen Unicode-Zeichensatz. Microsoft hingegen verwendet in der eigenen Software inkl. SQL Server das UCS-2 bzw. UTF-16 Encoding [3]. In dieser Untermenge von Unicode haben alle Zeichen einheitlich 2 Bytes, was die Programmierung entsprechender String-Funktionen vereinfacht, aber auf der anderen Seite dazu führt, dass in UCS-2-Strings in der Regel auch Null-Bytes auftauchen, z.B. als zweites Byte eines normalen ASCII-Zeichens. Unglücklicherweise gibt es in der Sprache C, die auch für den PHP-Interpreter Verwendung findet, keine richtigen Strings, sondern ein String ist schlicht als Array von Zeichen die mit Null enden definiert. In der Tat muss PHP intern für jeden String zusätzlich auch einen Integer-Wert mit seiner Länge verwalten, damit in Variablen problemlos binäre Daten mit Null-Bytes gespeichert werden können. Als wäre das nicht schon unangenehm genug, gibt es UTF-16 auch noch in einer Big Endian (BE) und einer Little Endian (LE) Variante [4]. Selbstverständlich sind im Standard-Byte-Order Markierungen vorgesehen, die – wie sollte es anders sein – selten genutzt werden. Um nicht noch tiefer in die Materie einzutauchen, kann an dieser Stelle nur der Rat erteilt werden, bei den eigenen Projekten durchgehend auf UTF-8 zu setzen, um möglichen Problemen aus dem Weg zu gehen und auf einen breit akzeptierten Standard zu setzen. Datenmigration Im ersten Schritt wurde eine neue Datenbankstruktur entworfen und dem Kunden vorgestellt. Da die alten Tabellen nur ein- oder zweisprachig vorlagen und nicht konsequent relational gestaltet waren, konnte keine triviale Konvertierung mittels SQL durchgeführt werden. Die Aufgabe konnte jedoch sehr gut durch ein PHP-Skript gelöst werden, welches über eine XML-Konfigurations-Datei gesteuert wird. Im Listing 1 wird ein einfaches Beispiel zur Veranschaulichung gegeben. Auf diese Weise konnten die zahlreichen Tabellen übersichtlich transformiert und normalisiert werden. Der notwendige PHP Code beschränkt sich dank der komfortablen SimpleXML-Erweiterung auf weniger als 1000 Zeilen. Das Konvertierungs-Skript konnte anschließend auch für eine Wiederherstellung der alten Tabellen und für den Export in die MySQL-Datenbank des Web-Servers genutzt werden, was die Integration in die IT-Umgebung deutlich beschleunigt hat.

enISO-8859-15127.0.0.1mitarbeiterfoobarzend_ODBTP_MSSQLzf_exp_...
    

SQL-Server Im Gegensatz zu MySQL gibt es bei der Verwendung von MS SQL eine Schwierigkeit: Der in C geschriebene Standard-Treiber arbeitet mit nullterminierten Strings, d.h. der von Microsoft geforderte Einsatz von UCS-2 für Unicode-Zeichen ist gar nicht möglich. Eine Alternative ist das freie „Open Database Transport Protocol“ (ODBTP) [5], das als Extension in PHP eingebunden wird. Dazu besorgt man sich die aktuelle Version von der Projekt-Homepage und kopiert die Datei php_odbtp_mssql.dll in das lokale Extension-Verzeichnis (z.B. C:ProgrammePHPext). Verwendet man PHP 5.2, muss die DLL aus dem CVS bezogen werden [6]. Anschließend wird der php.ini folgende Zeile hinzugefügt: extension=php_odbtp_mssql.dll Um den eigentlichen Server zu installieren, kopiert man die Dateien aus dem winservice-Verzeichnis in ein beliebiges lokales Verzeichnis und führt dort diese beiden Befehle aus: odbtpctl install odbtpctl start Eine Linux-Version und der Source-Code sind selbstverständlich auch verfügbar. ODBTP arbeitet als Schicht zwischen PHP und SQL-Server und akzeptiert Unicode-Strings im UTF-8 Encoding. Für die Integration von ODBTP in Zend Framework wurde ein entsprechender Datenbank-Adapter entwickelt. Fallstricke Inzwischen unterstützen alle verbreiteten Browser UTF-8. Eine Umwandlung zu ISO 8859, wie es früher bei Browsern wie dem Netscape 4 notwendig war, kann daher entfallen. Zur korrekten Darstellung ist lediglich folgender HTTP-Header [7] notwendig: Content-Type: text/html; charset=utf-8 Dies kann entweder in der php.ini über die Option default_charset = „utf-8“ konfiguriert werden oder man gibt den Header mit der Funktion header() direkt am Anfang eines PHP-Skripts aus. Eine weitere verbreitete Möglichkeit, die andere Anwendungen auf demselben Server nicht beeinflusst, ist die Verwendung eines Meta-Tags im (X)HTML-Source: Anders liegt der Fall beim Export in andere Anwendungen, beispielsweise per CSV. Hier ist oft noch eine Konvertierung in das jeweilige Ziel-Encoding (in Deutschland ISO-8859-15) notwendig: $iso_string = mb_convert_encoding ($utf8_string, ‚ISO-8859-15‘, ‚UTF-8‘); MySQL bringt man mit der Anweisung SET NAMES utf8 dazu, in UTF-8 zu sprechen, falls UTF-8 nicht bereits die Standard-Einstellung ist. Dazu kann man in der MySQL-Konfiguration in der Sektion [mysqld] folgende Zeile einfügen [8]: init-connect=’SET NAMES utf8′ Diese Einstellung darf nicht mit dem tatsächlichen Encoding der einzelnen Daten-Felder verwechselt werden – diese können weiterhin in anderen Zeichensätzen vorliegen. Der SQL Server besitzt die speziellen Datentypen „nvarchar“ und „ntext“ für Unicode-Strings und konvertiert ggf. transparent nach Unicode. Will man von Unicode auf ISO 8859 zurück wechseln, wird vor einem potentiellen Datenverlust gewarnt. Vorsicht ist auch beim Ex- und Import mittels Tools wie mysqldump oder phpMyAdmin geboten, damit man das UTF-8-Encoding nicht versehentlich zweimal vornimmt und verstümmelte Daten erhält. Bis auf die bereits angesprochene Verwendung der Multibyte-Funktionen mit dem Präfix „mb_“ anstelle der gewöhnlichen String-Funktionen, ist im Zusammenhang mit PHP nichts weiter zu beachten. Falls die Erweiterung installiert, aber nicht aktiv ist, kann man sie unter Windows mit folgender Zeile in der php.ini einbinden: extension=php_mbstring.dll Mittels der mbstring.func_overload Option [9] in der php.ini kann man die normalen String-Funktionen transparent durch ihre Multibyte-Gegenstücke ersetzen lassen. So kann auch Code, der auf die normalen Funktionen zugreift, weiterhin verwendet werden. Will man ansonsten externe Klassen – beispielsweise zum Mail-Versand oder zur String-Manipulation – einbinden, sollte stets geprüft werden, ob diese mit UTF-8 umgehen können. In PHP 6 wird diese Hürde dann fallen. Model-View-Controller Architektur Die Idee hinter der gut dokumentierten [10] MVC-Architektur von Zend Framework ist eine klare Trennung zwischen Daten (Models), der Anwendungs-Logik (Controller) und der Präsentation (Views). Da das Framework ohne eine Template-Engine ausgeliefert wird, hat man die Freiheit eine passende Engine einzubinden (die Wahl wird häufig auf Smarty fallen) oder die Templates direkt in PHP zu erstellen, wie dies auch in der Dokumentation vorgeführt wird. Generell kann man über Vererbung alle Framework-Klassen um eigene Funktionalitäten erweitern.

Abb. 1: Die Komponenten des Gesamtsystems

Im vorliegenden Fall verwenden die Models ein modifiziertes Active-Record-Pattern [11], mit einem Fallback auf die Default-Sprache, falls der Datensatz nicht in der gewünschten Sprache vorhanden ist, d.h. der Primary Key ist jeweils eine Kombination aus Datensatz-ID und Sprache. Alle Datenbank-Zugriffe wurden über Zend_Db mit Prepared-Statements ausgeführt, d.h. SQL-Code und Daten werden separat übergeben. Dies hat den Vorteil, dass man sich keine Gedanken über Unterschiede im Escaping und den Blob-Formaten der verschiedenen SQL-Implementationen machen muss. Davon abgesehen verhindert es auch effektiv SQL-Injection-Angriffe und verbessert so die Sicherheit. Bei komplexen Abfragen wurden in der Datenbank entsprechende Views angelegt, die nun zentral gewartet werden können und auch in den Java-Applikationen des Unternehmens genutzt werden. Limits bei Select-Abfragen können, wie in Listing 2 gezeigt, mit Zend_Db ebenfalls datenbankunabhängig formuliert werden.

$select = $db->select();
$select->from('users', array('displayName', 'initial'));
$select->where('locale = ?', 'en');
$select->order('lastName');
$select->limit(20, 0);
$results = $db->fetchAll($select);

Die Standard-Controller-Klasse wurde so erweitert, dass die Actions, die in etwa den einzelnen Skripten oder Seiten einer herkömmlichen Anwendung entsprechen und als Methoden eines Controllers angelegt werden, String-Labels zurück liefern. Anhand dieser Labels kann ein Routing zu einer View oder zu anderen Actions stattfinden, vergleiche Listing 3.

class UsersController extends ZGS_Controller_Web  {
  protected $_route_index = array('success' => 'view:user', 'foo' => 'forward:foo');
  protected $_route_foo = array('success' => 'view:foo');

  public function indexAction() {
    return 'foo';
  }

  public function fooAction() {
    $v = $this->getView();
    $v->body = 'Hello World!';
    return 'success';
  }
}

Wenn Daten von der View übergeben werden, findet in der genutzten Template-Engine ein automatisches HTML-Escaping statt, falls nicht explizit der Datentyp „raw“ verwendet wird. Ein möglicherweise unerlaubtes Einschleusen von schädlichem JavaScript-Code wird damit auf die Fälle beschränkt, in denen der Entwickler aktiv die Sicherheitsvorrichtungen umgeht. AJAX darf nicht fehlen Besonders für mehrsprachige Anwendungen bietet AJAX einige Vorteile, weil man für Übersetzungen zusätzliche Sprachen bei Bedarf nachladen und wieder speichern kann. Als praktisch hat sich auch eine dynamische TreeView-Komponente herausgestellt, mit der man sich schnell durch verschachtelte Datenstrukturen bewegen kann. In diesem Projekt wurde für das Frontend auf XHTML mit CSS und die Yahoo! User Interface Library [12] als AJAX-Bibliothek gesetzt. Der Datenaustausch erfolgt über JSON [13]. Unter der Vielzahl an AJAX-Frameworks gibt es bestimmt auch andere, die gut mit Zend Framework kombiniert werden können, so dass keine explizite Empfehlung ausgesprochen wird. An dieser Stelle erwähnt werden sollte jedoch das in Entwicklung befindliche Zend Component Model, welches die Programmierung in Zukunft stark vereinfachen wird [14]. Fazit Wenn man die richtigen Kniffe kennt, ist die Verwendung von Unicode anstelle von ISO 8859 oder anderen Ein-Byte-Zeichensätzen in PHP ohne Hindernisse möglich. Wie gezeigt wurde gilt dies sogar, wenn man die gewohnte LAMP-Plattform verlässt. Mit dem Release von PHP 6 wird Unicode sicher noch einen weiteren Schub erhalten. Bei der inzwischen sehr guten Unterstützung von UTF-8 und den immer grösser werdenden Server-Festplatten (die sicher das eine oder andere mit ISO 8859 vermeidbare Byte verkraften können), gibt es kaum noch einen Grund, den Anwendungs-Overhead durch die Verwendung unterschiedlicher Zeichensätze auf sich zu nehmen. Der Kunde kann seine Mitarbeiter-Datenbank nun nicht nur in den vier Sprachen Englisch, Deutsch, Russisch und Mandarin pflegen und ins Internet exportieren, sondern profitiert – dank der im Vergleich zu losen Einzel-Skripten klaren Struktur – auch von einer sehr guten Wartbarkeit.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -