Jetzt ist es schon wieder einige Zeit her, dass ich hier etwas geschrieben habe und ich denke ich gebe mal ein Lebenszeichen von mir. Natürlich gibt es zahlreiche interessante Themen in meiner Queue zum bloggen, aber wie immer findet man nicht die Zeit oder nötige Ruhe dazu.

Die Problemstellung

Nun aber zum Thema: für ein aktuelles Projekt benötige ich, von meiner PHP Webapplikation heraus, den Zugriff auf einige Hardwarekomponenten. Zudem will ich einige Fremdbibliotheken verwenden, die sehr rechenintensive Aufgaben übernehmen und auch nur in C++ zur Verfügung stehen. Die Frage ist also: wie kann ich diese Komponenten an mein PHP Programm ankoppeln?

Für Java gibt es eine ganz gute Server Bridge, die innerhalb der Java Virtual Machine einfach einen kleinen Server startet, der XML Anfragen entgegen nimmt, stellvertretend ausführt und das Ergebnis, auch wieder über eine XML Kommunikationsschnittstelle, zur Verfügung stellt. Die nötige PHP Klasse wird ebenfalls durch den Java Server direkt zur Verfügung gestellt. Eine ähnliche Bridge Lösung bringt auch der Zend Server bereits out of the box mit.

Für C++ habe ich so eine Lösung nicht gefunden und als weiteren Ansatz überlegt, eine PHP Extension zu entwickeln. Sowohl für Linux, als auch für Windows, gibt es hier Tutorials (siehe “Extension-Entwicklung unter WAP“, “Wrapping C++ Classes in a PHP Extension” oder “Extension Writing Part I: Introduction to PHP and Zend“). Allerdings ist, neben den Tutorials, der ganze Vorgang nicht gut dokumentiert und scheinbar muss das Kompilat der Extension exakt dem des verwendeten PHP Kompilats entsprechen. Insgesamt also eher eine wackelige Angelegenheit.

Lösungsansatz

Meine Lösung ist hingegen pragmatisch und verfolgt eine eher losere Kopplung: Meine C++ Komponenten werden in ein eigenständiges Programm ausgelagert, das selbst, über einen gewöhnlichen HTTP Server, die benötigten Dienste zur Verfügung stellt. Dabei orientiere ich mich an dem REST Architektur Prinzip und binde jeweils eine Teil-Funktionalität an eine feste URL. Die Parameter und Rückgabewerte werden mittels JSON kodiert. Von PHP Seite aus, wird der Aufruf der C++ Komponenten sauber gekapselt, so dass für das PHP Programm der Eindruck entsteht, dass eine gewöhnliche Funktion aufgerufen wird.

Welche Vor- und Nachteile hat das Vorgehen? Zum einen ist die in C++ implementierte Funktionalität sauber gekapselt und von der Webapplikation getrennt. Dadurch wird die Software leichter wartbar. Durch eine saubere Schnittstellendefinition, kann die C++ Serverkomponente auch unabhängig vom Wissen über die PHP Applikation gepflegt werden. Zum anderen entsteht aber ein zusätzlicher Aufwand (mit zusätzlichen Fehlerquellen). Die Schnittstelle muss definiert und implementiert werden, ein Fehlerhandling muss durchgeführt werden und eine zusätzliche Serverapplikation muss gestartet werden, was hinsichtlich der Sicherheit berücksichtigt werden muss. Dennoch halte ich die Lösung für die eleganteste, denn sie nutzt genau die Vorteile der Vernetzung und der losen Kopplung aus. So könnte die C++ Komponente bei höherer Last auch leicht auf einen eigenen Server umgezogen oder unabhängig von der Webapplikation betrieben werden.

Implementierung

In der Theorie hört sich natürlich immer alles ganz nett an, aber wie sieht das mit dem HTTP Server aus? Hier gibt es eine hervorragende C++ Bibliothek, mit der sich ohne großen Aufwand eine solcher HTTP Server realisieren lässt. mongoose heißt das Wunderding, steht unter der MIT Lizenz unter Google Code zur Verfügung. Es unterstützt Windows, Linux und Mac OS X, bietet eine CGI Schnittstelle, SSL Verschlüsselung, ACL, eine saubere API und ist nicht größer als 60 kB. Die Dokumentation ist zwar recht knapp, aber dank Beispiele findet man sich schnell zurecht und auch die API Beschreibung ist vollkommen ausreichend.

Will man einen eigenen Server starten, so muss zuerst ein neuer Context erzeugt werden:

struct mg_context* ctx;
ctx = mg_start();

Mit mg_set_options, werden die Servereinstellungen geändert. Folgende zwei Zeilen deaktivieren das Directory Listing und setzten 8080 als Port.

mg_set_option(ctx, "dir_list", "no");
mg_set_option(ctx, "ports", "8080");

Aus Sicherheitsgründen erlaube ich nur einen Zugriff vom selben Rechner aus, also erlaube nur die IP Adresse 127.0.0.1:

mg_set_option(ctx, "acl", "-0.0.0.0,+127.0.0.1");

Mit mg_set_uri_callback kann ein Pointer auf eine eigene Funktion an eine URL gebunden werden. Diese wird aufgerufen, wenn der Client die URL aufruft, wobei die URL auch Wildcards enthalten kann.

mg_set_uri_callback(ctx, 'dosomething', &Server::dosomething, NULL);

Als dritter Parameter darf ein Pointer (void*) auf ein beliebiges Objekt übergeben werden. Die Callback Methode bekommt dazu alle notwendigen Parameter, wie die aktuelle Verbindung, den aktuellen Request und das zuvor übergebene Objekt (hier NULL) übergeben.

void Server::capture(struct mg_connection *conn,
 const struct mg_request_info *request_info,
 void *user_data) {
char* param = mg_get_var(conn, "param");
mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n");
}

Mit mg_printf kann dann eine Antwort an den Client gesendet werden. Hier als einfaches Beispiel einfach nur ein OK (HTTP 200). In meinem Szenario wird hier der generierte JSON Text zurück gegeben. Mit mg_get_var können übergebene Parameter (POST, GET) ausgelesen werden. Wichtig: um memory leaks zu vermeiden, müssen diese mit mg_free wieder freigegeben werden.

Natürlich ist das nur die eine Hälfte. PHP muss jetzt auch Funktionen, auf dem Server, aufrufen können. Dazu kommt der Zend_Http_Client zum Einsatz. Dieser ist im Zend Framework enthalten, mit etwas Frickelei kann er aber auch aus dem Gesamtpaket heraus gepickt werden. Da ich aber das Zend Framework für die ganze Anwendung nutze, ist das Aufrufen der URL und das Decodieren des JSON Strings in einen Action Helper ausgelagert, der gleichzeitig eine einfache Fehlerbehandlung durchführt:

class JsonHttpHelper extends Zend_Controller_Action_Helper_Abstract {
   public function get($url, $params = array()) {
      try {
         $client = new Zend_Http_Client($url);
         foreach($params as $param=>$value)
            $client->setParameterPost($param, $value);
            $response = $client->request('POST');
            $res = Zend_Json::decode($response->getBody());
            if($response->isSuccessful()) {
               return $res;
            } else {
               return array("error" => "request not successfully: ".$res["error"]);
            }
      } catch(Exception $e) {
         return array("error" => $e->getMessage());
      }
 }
}

Bisher bin ich mit der Lösung sehr zufrieden. Besonders mongoose kann ich nur wärmstens empfehlen. Auch von der Verarbeitungsgeschwindigkeit ist das Ergebnis zufriedenstellend und kann sich sehen lassen. Um den Rahmen nicht zu sprengen, habe ich die serverseitige Generierung und Verarbeitung von JSON außen vor gelassen. Hier gibt es aber auch zahlreiche Bibliotheken, so dass die Suche nicht schwer fallen dürfte.

Jede sauber programmierte PHP Applikation macht es notwendig, dass Klassen dynamisch  nachgeladen werden müssen. Dazu bietet PHP die __autoload() Funktion. Diese wird im Zend Framework natürlich in einem Objekt gekapselt. Nachdem ich den einfachen Weg immer vorziehe, nutze ich natürlich diesen gewöhnlichen Autoloader Mechanismus durch die Klasse Zend_Loader_Autoloader:

$autoloader =  Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('application_');

Wird die Zend_Application Klasse verwendet, so wird der Autoloader automatisch aktiviert (die Namespace’s müssen dabei in der Konfiguration gesetzt werden). Verwende ich dann eine Klasse, so wird der Name entsprechend aufgelöst, d.h. zu obigen Beispiel wird bei der Verwendung der Klasse “application_models_items” die Datei “application/models/items.php” eingebunden. Das Problem bei dieser Lösung: hat man ein sehr großes Projekt mit einer tiefen Hierachie, dann wächst der Klassenname entsprechend stark an. Bei meinem RSS Reader rsslounge hab ich das beispielsweise in Kauf genommen, da die Struktur nicht komplex ist. Zudem sieht man am Namen schon sofort, wo die Datei abgelegt ist und welche Funktion die Klasse übernimmt (application_models_items ist ein Model, dass sieht man sofort).

Seit der Version 1.8 bietet das Zend Framework einen eleganten Weg Klassen automatisch zu laden, dabei aber genau zu spezifizieren, wo die Klassen gesucht werden sollen. Dazu wird ein Namespace definiert und für verschiedene “Unter-Namespace” Pfade angegeben. Ein Beispiel veranschaulicht das Vorgehen:

$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
 'basePath'  => APPLICATION_PATH . '/../einordner',
 'namespace' => 'Myproject',
 ));
 $resourceLoader->addResourceType('models', 'models', 'Model');
 $var = new Myproject_Model_Xyz();

Zuerst wird die Klasse Zend_Loader_Autoloader_Resource instanziiert. Dabei wird ein Namespace angegeben (im Beispiel “Myproject”) und definiert, in welchem Ordner die zugehörigen Dateien zu finden sind. In einem zweiten Schritt wird dann mit addResourceType ein “Unter-Namespace” definiert und angegeben in welchem Unterverzeichnis die Klassen liegen. Der Aufbau von addResourceType ist wie folgt:

 $resourceLoader->addResourceType($name, $ordner, $namespace);

Damit ist der Autoloader für alle Klassen mit dem Präfix “Myproject_Model_” aktiv. Im Beispiel wird eine Klasse Xyz geladen. Dabei wird für die Klasse “Myproject_Model_Xyz” die Datei “APPLICATION_PATH . /’../einordner/models/xyz.php” eingebunden. Es können beliebig viele “Unter-Namespace” definiert werden, was für eigene Klassen wie Models, Forms oder Validatoren sinnvoll ist.

Weitere Informationen und Beispiele sind im Manual zu finden. Keith Pope beschreibt anhand eines Beispiels sehr schön, wie er Zend_Loader_Autoloader_Resource in seinem Projekt verwendet (siehe “Seite 129ff, Zend Framework 1.8 – Web Application Development” von Keith Pope).

Dieser Weg Klassen automatisch zu laden gefällt mir ganz gut, weil er einfach ist und keine spezielle Loader Klasse nötig ist (die z.B. mittels einer statischen Funktion eine Klasseninstanz liefert). Natürlich offenbaren die Namespaces, welche mit PHP 5.3 eingeführt wurden neue Möglichkeiten, die ich mir auch noch genauer ansehen möchte. Oder gibt es eine noch schönere und elegantere Lösung? Dann bitte unbedingt kommentieren!

Seit einigen Wochen ist das neues Zend Framework Buch “Zend Framework 1.8 Web Appliaction Development” auf den Markt:

Zend Framework 1.8

Wen das Buch interessiert, der kann in Ralf seinem Blog an einem Gewinnspiel teilnehmen. Der Blog ist allgemein zu empfehlen: es gibt regelmäßig Updates und Infos zum Zend Framework. Ich werde mein Glück mal versuchen. Bin noch unsicher, ob sich die Anschaffung des Buchs lohnt. Bisher bin ich mit Ralfs Buch sehr zufrieden, dort sind alle wichtigen Informationen zu finden. Das was zur neuen Version hin noch fehlt, kann man auch problemlos im Zend Framework Manual nachschlagen. Aber als Gewinn wärs natürlich nicht schlecht…

Die Firma Mayflower ist mir ja schon länger ein Begriff, da sie sehr im Open Source Bereich um PHP tätig ist. Um die Verbreitung des Zend Frameworks zu fördern, verschenkt Mayflower Poster mit den wichtigsten Methodenaufrufen, Argumenten und Beispielaufrufen. Einfach das Kontaktformular ausfüllen und das Poster wird einem versandkostenfrei zugeschickt.

Eine feine Sache, mein Poster ist heute angekommen und echt top:

Zend Framework Poster

Ein PHP Web Security Poster soll in Arbeit sein. Ich bin gespannt und werd jetzt einen passenden Platz für mein Poster suchen…

Die letzten Tage ist mir eine Verhaltensweise des Zend Frameworks aufgefallen, die doch sehr zweifelhaft ist. Es kam unerwartet zu der Exception

Could not determine temp directory,
please specify a cache_dir manually

Der Fehler war schnell gefunden: das temporäre Verzeichnis auf dem Server war nicht gesetzt bzw. war schreibgeschützt. Wie im Zend Framework Issue Tracker nachzulesen ist, wird bei der Verwendung von Zend_Locale und Zend_Translate automatisch ein Cache Objekt erzeugt, welches das temporäre Verzeichnis des Webservers verwendet. Ein aus meiner Sicht sehr zweifelhaftes Verhalten bzw. eine Vorgehensweise, die vielen Kritikern Recht gibt: ohne Caching geht es scheinbar nicht mehr. Laut Thomas Weidner ist das auch nicht mehr erwähnenswert:

Using a false cache will also decrease performance or negotate it completly… but I don’t think that this should be mentioned into this chapter [of the Zend Manual]. Anyone with default knowledge should know this already.
(siehe Issue Tracker ZF-6668, Thomas Weidner)

Ich sehe das an dieser Stelle etwas anders. Caching ist zwar ein wichtiges Mittel und muss hinsichtlich der Performance unbedingt im Auge behalten werden, es aber stillschweigend zu aktivieren, geht aus meiner Sicht doch etwas zu weit.

Um das Problem zu lösen, gibt es mehrere Möglichkeiten:

1. Man setzt die Umgebungsvariable für das temporäre Verzeichnis neu

Das Zend Framework durchsucht die Umgebungsvariablen ‘TMPDIR’, ‘TEMP’, ‘TMP’, ‘windir’, ‘SystemRoot’ (die letzten beiden für Unterverzeichnis ‘/temp’) und übernimmt diese, falls diese gesetzt sind. Abhilfe schafft es also, in der .htaccess


SetEnv TMPDIR data/cache

zu setzen.

2. Man deaktiviert das Caching (was gemäß Thomas Weidner nicht empfehlenswert ist und ich an dieser Stelle auch nicht empfehlen will).

Das Deaktivieren geht sehr einfach mit:


Zend_Translate::removeCache()
Zend_Locale::removeCache()

3. Die beste Methode ist es, ein eigenes Cache Objekt zu setzen und darin einen eigenen Pfad zu definieren. Dazu muss das Frontend Zend_Cache_Core verwendet werden. Dabei wird auf eine statische Methode setCache zurückgegriffen, das heißt die Einstellung gilt dann für alle Zend_Locale oder Zend_Translate Objekte.


$path = APPLICATION_PATH . '/data/cache';
$cache = Zend_Cache::factory(
         'Core',
         'File',
         array(),
         array(
            cache_dir => $path
         ));
Zend_Locale::setCache($cache);
Zend_Translate::setCache($cache);

Nun ist es soweit, endlich ist das Zend Framework in der Version 1.8.0 verfügbar. Ein erster Blick zeigt super Neuerungen, wie z.B. Zend_Tool, einem Command Line Skript zum Erstellen eines Programmrahmens. Zudem gibt es jetzt eine Zend_Application Komponente, die das Bootstrap File ablösen soll und zukünftig alle Programmkomponenten aufnehmen kann. Weiterführend unterstützt die neue Version auch den Cloud Computing Service Amazon S3. Darüber hinaus gibt es noch viele weitere neue Komponenten. Bin gespannt, werde aber so schnell keine Zeit zum Testen haben. Mal sehen, Erfahrungsberichte werden früher oder später aber sicher folgen.

Weitere Informationen gibt es hier.

Das Zend Framework CoverSeit ein paar Tagen habe ich nun endlich Ralfs neue Buch über das Zend Framework. Nach einem ersten Durchblättern habe ich einen sehr positiven Eindruck. Besonders das Kapitel über “Testgetriebene Entwicklung” scheint wirklich alle Informationen zu enthalten um hier praktisch loslegen zu können. Auch über das Kapitel zu Dojo und JavaScript freue ich mich besonders. Leider werde ich erst im Sommer dazu kommen es durchzuarbeiten. Deshalb dieser Post mit einem ersten Eindruck. Eine detailierte Buchbeschreibung werde ich also noch nachliefern.

(siehe auch: Buch auf Amazon, Blog zum Buch, Ralfs Blog)