Libevent Woes in PHP

Mai 20th, 2012 — 1:44pm

Seit ein paar Wochen arbeite ich zum Spaß an einem STOMP Client, der libevent benutzt um dann Listener einer Queue per Symfony Event Dispatcher zu benachrichtigen sobald neue Messages verfügbar sind. Dazu benutze ich die PHP bindings für libevent. Wer libevent nicht kennt: das ist eine Bibliothek in C geschrieben, die es einfacher macht mit Streams (z. B. Sockets) zu arbeiten, in dem man sich auf bestimmte Events bindet die auf dem Stream passieren (z. B. read/write/timeout) und dann von libevent das gewünschte callback aufgerufen wird. Intern beutzt libevent die für die Umgebung performanteste Polling Implementierung um nachzuschauen ob neue Daten verfügbar sind im Stream.

Grunsätzlich funktioniert die Extension super, ich musste nur (damals) die neueste Variante aus dem pecl svn installieren (kann man sich holen mit svn checkout http://svn.php.net/repository/pecl/libevent/trunk libevent-trunk), ansonsten wollte die Extension nicht mit PHP 5.4. kompilieren. Ist aber in der neueren Version gefixed.

Da der STOMP Client noch nicht fertig ist, gibts dazu später einen extra Artikel, aber ein paar Dinge sind mir beim arbeiten mit libevent aufgefallen, die ich gerne festhalten möchte.

Event loops are hard to kill

In diesem Beispiel pollen wir STDIN nach neuen Daten, nur um dann im read callback eine Exception zu werfen:

$base = event_base_new();
$eb = event_buffer_new(
    STDIN,
    function ($buf, $arg) use(&$base) {
        print 'read callback' . PHP_EOL;
        throw new Exception();
    },
    NULL,
    function ($buf, $what, $arg) {
        print 'error callback' . PHP_EOL;
    },
    $base
);

event_buffer_base_set($eb, $base);
event_buffer_enable($eb, EV_READ);

event_base_loop($base);

Da ich event loops ja aus node.js schon kenne, hätte ich gedacht, die Exception würde die event loop beenden und das script somit auch und den Stacktrace auf der Konsole anzeigen. Falsch. Es passiert nämlich gar nix:

$ php loop.php
test
read callback triggered
(hängt für immer)

Die Exception wird nirgends ausgegeben, gar nix. Möglicherweise ändert sich das in künfigen Versionen der PHP Extension, aber bis dahin hat mir nur geholfen die event loop explizit zu beenden und danach die Exception zu werfen (hier der geänderte Aufruf für einen neuen buffered event):

$eb = event_buffer_new(
    STDIN,
    function ($buf, $arg) use(&$base, &$eb) {
        print 'read callback' . PHP_EOL;
        // loop beenden, cleanup macht die extension zum Glück für uns
        event_base_loopbreak($base);
        throw new Exception();
    },
    NULL,
    function ($buf, $what, $arg) {
        print 'error callback' . PHP_EOL;
    },
    $base
);

Error handling

Die PHP Extension gleicht vom Abstraktionslevel der C Implementierung von libevent ziemlich genau, nur das cleanup/freigeben von Resourcen bleibt einem erspart. Ansonsten kann man sich genauso wie in C mit Fehlercodes rumplagen:

$some_remote_socket = fsockopen(...);

$base = event_base_new();
$eb = event_buffer_new(
    $some_remote_socket,
    function ($buf, $arg) {
        // ...
    },
    NULL,
    function ($buf, $what, $arg) {
        // $what enthält den Fehlercode
       if ($what & EVBUFFER_EOF) print 'unerwartetes EOF' . PHP_EOL;
       else if ($what & EVBUFFER_ERROR) print 'allgemeiner fehler (sehr hilfreich, ja)' . PHP_EOL;
       else if ($what & EVBUFFER_TIMEOUT) print 'timeout' . PHP_EOL;
    },
    $base
);

event_buffer_timeout_set($eb, 10, 10);
event_buffer_base_set($eb, $base);
event_buffer_enable($eb, EV_READ);

event_base_loop($base);

Exceptions kann die Extension ja aber auch schlecht werfen, siehe vorheriger Punkt.

Event loop unterbrechen

In meinem STOMP Client muss ich ab und an die event loop unterbrechen, was zwischendrin machen und dann die event loop wieder starten. Der Zeitraum zwischen unterbrechen und neustarten ist SEHR kurz, aber: wenn nach der Unterbrechung und VOR dem erneuten Starten der event loop Daten in den Socket geschrieben wurden vom Server, dann habe ich diese Daten verpasst und mich gewundert wieso. Folgendes Pattern hat sich dabei bewährt:

  1. Event loop stoppen mit event_base_loopbreak($base)
  2. Schon mal neues event base erzeugen mit $base = event_base_new()
  3. Read event erzeugen, event base zuordnen, event enablen event_buffer_new() usw.
  4. Event loop NOCH NICHT wieder starten
  5. Das tun, was zwischendrin getan werden muss in der app
  6. Jetzt event loop wieder starten mit event_base_loop($base)
  7. Sollten von einem sehr schnellen Server Daten zwischendrin in den Socket geschrieben worden sein, wird der read event dispatched von libevent

Schön ist was anderes, aber hat geholfen einen fiesen Bug zu fixen.

Unit Testing

Unit Testing ist generell ein schwieriges Unterfangen wenn PHP Funktionen im Spiel sind, dazu kommt noch die Abhängigkeit von externe Resourcen wie Sockets. Ich hab das Problem größtenteils umgangen in dem ich auf Unit Tests für die Teile, die libevent Funktionen benutzen, verzichtet habe und setze lieber eine Integration Test Suite auf. In meinem Fall des STOMP Clients verwende ich dazu einem (Dummy) STOMP Server in node.js geschrieben. Ich teste lieber den Output den ich vom Server bekomme anstatt zu prüfen ob eine Methode event_base_loop aufgerufen hat.

Aber sonst…

Insgesamt bin ich mit libevent mehr als zufrieden und bin schon sehr gespannt auf den STOMP client, sodann ich dessen TODO Liste endlich mal runtergearbeitet habe…

Comment » | Allgemein, Development, PHP

AOP in PHP durch weaven des source codes

März 28th, 2012 — 8:49am

Mein heimliches Steckenpferd ist ja immer noch AOP. Seit ich damit bei Spring.NET in Berührung gekommen bin fasziniert mich die Idee source code dynmaisch Funktionalität hinzufügen zu können. Ich schwanke meistens zwischen “find ich total super” und “intransparent, mag kein Mensch haben”. In PHP gibt es nur ein Projekt, dass sich ernsthaft daran gemacht hat, AOP zu implementieren, und das ist natürlich FLOW3 (was mittlerweile zu einem sehr spannenden Projekt herangereift ist).

Proxy-Childclasses

Vor einigen Wochen habe ich mir gewohnheitsmäßig mal wieder die FLOW3 Dokumentation aus Interesse angeschaut und war positiv überrascht, dass ich Objekte einfach mit new MyStuff(); erstellen kann, das $this->objectManager->create('MyStuff'); ist verschwunden. Und trotzdem kann ich Dependency Injection und AOP haben? Obwohl ich die Objekte selbst erstelle? Wie geht das denn?

Der von FLOW3 angewendete “Trick” kann sich sehen lassen. Beim Aufruf der Seite (im development context) wird die Klasse, die geweaved werden soll umbenannt (_Original suffix), es wird eine Proxy-Klasse erstellt, welche die originale Klasse extended und den Namen der original Klasse bekommt und in den überschriebenen Methoden des Proxies werden die dafür vorgesehenen Aspects eingefügt. Die Klassen werden dann im cache Verzeichnis abgelegt, damit die original Dateien unberührt bleiben. Selbstverständlich ist der Prozess wie das genau abläuft um einiges aufwendiger und komplizierter (Stichwort: change detection) aber das Prinzip welches mich fasziniert hat wird glaube ich deutlich. Im Prinzip ist dieses Vorgehen nicht unähnlich zu Doctrine2 – auch dort werden Proxy Klassen erstellt und die original Methoden in der abgeleiteten Klasse überschrieben um lazy loading zu ermöglichen.

Ein Nachteil dieses Ansatzes ist, dass ich, auch wenn ich den Klassennamen beibehalte auf einmal eine weitere Ebene in der Klassenhierarchie habe, die mir vom Framework eingefügt wurde. So wird zum Beispiel aus:

MyController extends ActionController

Die Hierarchie:

MyController_Original extends ActionController
MyController extends MyController_Original

Proxying mit source code Manipulation

In Java oder C# hingegen gibt es ein paar AOP Frameworks welche die Aufrufe direkt in den generierten Bytecode weaven nachdem das Projekt kompiliert wurde. Sowas funktioniert natürlich in PHP nicht so recht.

Meine Idee war zu sagen ich parse die source files die geweaved werden sollen (also den PHP code der Klassendefinition), benenne die Methoden um, füge Proxy Methoden mit dem original Namen ein, speichere die PHP Datei in ein TMP Verzeichnis und sage dem autoloader, dass er die geweavte Klasse aus dem TMP Verzeichnis laden soll wenn die Klasse instanziert wird. Symfony2 zum Beispiel dumped (im development context) auch jedes mal den DI container bevor die eigentliche Anwendung läuft. Einfach, oder? Dachte ich auch und hab mich sofort mit PHPs tokenizer Extension auf den Weg gemacht. Leider ist PHP source code parsen doch unangenehmer als ich mir das vorgestellt hatte. Auch wenn mein naiver Ansatz mit tokenizer funktioniert hat – der Code wurde unmaintainable.

PHPParser to the rescue

Zu ungefähr der gleichen Zeit bin ich auf HackerNews über einen in PHP geschriebeben PHP Parser gestolpert, der genau das Level an Abstraktion bietet, das ich für meine Idee brauche. Mit dem PHPParser kann man recht bequem PHP source code einlesen und manipulieren in dem man sich einen eigenen NodeVisitor registriert. Mit etwas Ausprobieren konnte ich damit bei größeren/komplexeren Projekten wie z. B. symfony components die Klassen weaven.

Proof of concept

Auch wenn die Bibliothek natürlich noch weit (sehr weit) von fertig entfernt ist, habe ich meine bisherigen Fortschritte zusammenfassend ein Repository unter dem Namen ClassWaver auf github angelegt. Viel passiert nicht, außer dass die symfony HttpKernel Komponente geweaved wird und Methodenaufrufe geproxied werden. An der Stelle an der momentan ein einfaches print ausgegeben wird, werden Methodenargumente abgefangen, der return value der originalen Methode abgefangen und an den Client zurückgegeben etc. pp. Das wäre also die Stelle, an der AOP magic oder Dependency Injection in mit new instanzierten Objekte passieren kann.

Damit der ClassWeaver überhaupt mal für production Projekte in Frage kommt, muss noch einiges passieren. Das größte Problem ist sicher die Performance. Source trees, wie z. B. ein ausgewachsenes Symfony2 Projekt dauern sehr, sehr lange zu weaven. Change detection wie FLOW3 das macht wäre evtl. eine Lösung. Über die Syntax, wie AOP Definitionen angelegt werden damit diese vom Weaver berücksichtigt werden habe ich mir selbstverständlich auch noch keine Gedanken gemacht und noch vieles mehr.

Im Grunde ist der ClassWeaver eher ein Gedankenexperiment als etwas anderes. Trotzdem finde ich den Ansatz direkt über einen Parser Aufrufe im Source Code zu platzieren auf eine gewisse Art sehr interessant – und auch bedenklich. So wie ich AOP insgesamt sehe.

Comment » | AOP, Development, PHP

Der LAMP Stack ist nicht tot, aber riecht… anderst

Mai 25th, 2011 — 4:48pm

Was den aktuellen Stand des LAMP Stacks angeht habe ich mir schon länger Gedanken gemacht. Dieser Tage hatte ich dann eine entsprechende Unterhaltung über genau das Thema mit meinem Kollegen, dem Karsten.

Gehen wir doch die Bestandteile mal durch:

L für Linux

Ich glaube, darüber brauchen wir uns nicht lange unterhalten. Die ein oder andere Linux Distribution wird die Grundlage für fast alle erfolgreichen Web Projekte sein.

Allerdings gibt es auch Bespiele für High Traffic Webseiten, welche auf dem Microsoft Stack basieren, wie etwa Stackoverflow. Allerdings kommt man auch bei Stackoverflow nicht ganz ohne das L aus – sei es zum Beispiel ein Caching Server auf dem Redis läuft. Auch wenn es natürlich schwer fällt, etwas gegen die sehr erfolgreiche Website zu sagen, habe ich meine Bedenken, was so eine Mischform angeht. Und sei es nur, weil man Developer und DBA braucht, die in beiden doch sehr unterschiedlichen Welten, Microsft und Unix, zu Hause sind. Als Hipster-Startup hat man da natürlich nochmal andere Möglichkeiten. Auch ist mein Eindruck, dass das horizontale Scaling mit dem klassischen LAMP Stack einfacher fällt. Stackoverflow scheint tendenziell eher vertikal skalieren zu wollen (größere/teurere statt “mehr” Hardware), zumindest wenn man sich die Entwicklung momentan ansieht.

Für mich allerdings, obwohl oder gerade weil ich auch Projekte mit IIS/C# gemacht habe, scheint ein ein UNIX basierendes System aber die bessere Wahl zu sein. Das L ist also nicht wegzudenken aus LAMP.

A für Apache

Die Wahl des Webservers war eingentlich lange Zeit keine. Apache oder… Apache. Ganz so sieht es mittlerweile nicht mehr aus.

Nginx – ein leichtgewichtiger, high-Performance http Server, welcher in den Features, die man so braucht für seine Anwendung dem Apache in wenig nachsteht. Das Modell, wie Requests im Nginx abgearbeitet werden, nämlich über eine Event Loop, unterscheidet sich dabei erheblich von dem des Apache (soviel Threads aufmachen wie nötig). Mittlerweile ist Nginx auch in der Version 1.0 stable verfügbar, wird aber schon seit längerem von diversen high Traffic Websites eingesetzt.

node.js – serverseitiges Javascript, basierend auf Goolges V8 Engine. Auch wenn das Projekt noch recht jung ist, hat node.js bereits viel Aufmerksamkeit bekommen in letzter Zeit. Zusammen mit den Pub/Sub Funktionalitäten eines Redis beispielsweise lassen sich sehr interessante Problemlösungen mit node.js finden für spezifische technische Probleme. Ich glaube nicht, dass ich meine Domain-Logik in node implementieren möchte, aber aufgrund des hohen Durchsatzes bieten sich zum Beispiel File-Upload und File-Auslieferung an, oder wie vorher erwähnt mit Redis Pub/Sub lässt sich Realtime Updating einer Website realisieren. Oder auch sehr Javascript lastige Anwendungen könnten von node als Backend profitieren.

Das A hat also Gesellschaft (node.js) oder Ersatz (Nginx) bekommen.

M für mySQL

Im Bereich der Storage Engine hat sich wohl am meisten getan in den letzten Jahren/Monaten. Eine klassische relationale Datenbank wie mySQL war lange Zeit alternativlos, obwohl sich gewisse Daten schlecht im Paradigma von RDBMS speichern lassen bzw. gewisse Funktionen wie komplexere Suchabfragen zumindest in mySQL schwer von der Hand gehen.

Für mich ist mySQL als Haupt-Storage Engine weitestgehend alternativlos, immer noch. Da es mit Doctrine 2 endlich einen brauchbaren ORM für PHP gibt, können auch komplexere Objekt-Graphen in einem RDBMS abgelegt und abgefragt werden.

Trotzdem bieten sich eben eine Vielzahl an Projekten an, um etwa gewisse Schwachstellen von RDBMS auszubügeln:

Redis – ein high-performance Datastructure Server. Man kann es sich vorstellen wie memcache on steroids. Daten können nicht nur als key-value pairs abgelegt werden, in Redis ist es auch möglich sortierte Sets, Listen mit push/pop Funktion z. B. für Queues und Hashes mit Zugriff auf die einzelnen Felder abzulegen. Sehr interessant sind auch die Pub/Sub Funktionen von Redis, die beispielsweise mit node.js in Kombination mit Websockets sehr interessante Möglichkeiten für realtime Updates in einer Website bieten. Als Bonus oben drauf gibt es seit neuestem die Möglichkeit Lua Scripte in Redis ausführen zu lassen. Da Redis auch als persistenter Daten Storage konfiguriert werden kann, ist das Projekt nicht nur als Caching Server interessant.

Solr – Apache Solr ist ein in Java geschriebener Such-Server basierend auf Lucene. Die mySQL Fulltext Search ist ja ab einer gewissen Anzahl an rows nicht mehr zu gebrauchen. Als Alternative bietet sich hier z. B. Solr an. Millionen von Dokumenten können mit hervorragender Performance durchsucht werden. Komplexe Suchabfragen werden mit der Lucene Query Language formuliert. Solr wird bequemerweise über eine REST Schnittstelle angefragt. Durch einen automatisch laufenden Daten-Import aus mySQL mit Solrs dataimport-Handler lassen sich die Dokumente im Suchindex recht einfach auf dem aktuellen Stand halten.

Riak – Riak ist eine noch recht junge noSQL Datenbank, welche auf Amazons Dynamo Paper basiert. Daten werden unter einem key in Buckets gespeichert. Abgefragt werden die Daten genau wie bei Solr über eine REST API. Die interessante Idee an Riak bzw. an der in dem Amazon Dynamo Paper beschrieben Technik: es gibt keinen Master Server. In einem Riak Cluster, das aus mindestens 3 Nodes bestehen sollte, hat jede Node alle Daten bzw. weiß genau bei welcher Node die angefragen Daten liegen. Zur Laufzeit können problemlos Nodes aus dem Cluster entfernt werden oder bei Traffic Peaks zum Cluster hinzugefügt werden. Darüber hinaus kann man über die REST API Map/Reduce Jobs über die gespeicherten Daten laufen lassen. Die Performance der Map/Reduce Jobs konnte mich zwar noch nicht überzeugen, weswegen ich Riak weniger als Daten-Store sehe, auf den Realtime Abfragen gemacht werden. Allerdings kann ich mir Riak sehr gut als eine Art verteiltes Dateisystem vorstellen, in dem Daten auf mehreren Nodes abgelegt werden und z. B. über node.js dann wieder ausgeliefert.

Vor allem auch den noSQL Trend hat sich viel getan im Bereich Datenbanken. mySQL hat denke ich immer noch seinen festen Platz in den allermeisten Projekten, und das auch zu Recht. Aber man darf die oben genannten Projekte nicht vergessen, die meistens eine sinnvolle Ergänzung darstellen können und in manchen Bereichen mySQL tatsächlich ersetzen können.

P für PHP

Im Bereich der tatsächlichen Programming Language in der Webapplikationen geschrieben werden haben sich aus meiner Sicht die wenigsten Änderungen ergeben. PHP ist für mich nach wie vor die Sprache der Wahl, wenn es um dynamische Web Anwendungen geht. PHP bietet nicht die beste Performance, nicht die stringenteste API und nicht die allerbesten Bibliotheken – aber es funktioniert einfach und es gibt genügend Leute, die damit auch sehr gut programmieren können. Das kann man ja nicht von allen Sprachen behaupten. Eine mögliche Alternative könnte vielleicht ein anderes P darstellen – Python. Die Zeit wird zeigen ob Python einen ähnlichen Verbreitungsgrad wie PHP finden kann, auch gibt es gewisse Ansätze in Python, über die sich diskutieren lässt. Ducktyping ist nur eins davon.

Zusammenfassung

Diese kleine Übersicht zeigt, dass Webdevelopment mittlerweile weit über den bis vor gar nicht allzu langer Zeit klar definierten LAMP Stack hinausgeht. Viele Technologien bieten sich als Ergänzung zu Bewährtem an (Redis als Caching Server, mySQL immer noch als Haupt-Storage Engine) oder auch als Ersatz (nginx statt Apache).

Es ist auf jeden Fall eine aufregende Zeit für Developer und viele Applikationen, die bisher nicht denkbar waren im Webumfeld sind mit den erwähnten oder ähnlichen Technologien machbar – wenn man die Alternativen kennt und sich genau überlegt, wie man seinen LAMP Stack sinnvoll ergänzen kann.

Comment » | Allgemein, Development

FLOW3 Alpha 14 mit mysql backend auf ubuntu installieren

Dezember 29th, 2010 — 12:34pm

Erst kürzlich habe ich mich mal wieder mit FLOW3 auseinandergesetzt. Da ich keine gute Anleitung im Web gefunden habe wie ich die (momentan) aktuellste Alpha 14 mit mySQL als Backend Storage ans Laufen bekomme, habe ich das hier mal kurz zusammengeschrieben.

Ich weiß, es gibt extra ein Getting Started Package, das ist allerdings für Alpha 6 oder 7 ausgelegt und nimmt außerdem sqlLite als Backend Storage. Ich wollte aber sowohl die aktuellste Version als auch mein favorite RDBMS ausprobieren.

Grundkenntnisse im Umgang mit der Shell und PHP/mySQL sind von Vorteil, aber es sind alle wesentlichen Schritte beschrieben. Los gehts:

Server einrichten

Zuerst habe ich mir eine neue vmware mit ubuntu 10.10 server edition aufgesetzt.

Danach zunächst mal den ganzen LAMP Stack (PHP 5.3, MYSQL 5.1 und Apache 2.2) mit folgendem Command installiert:

$ sudo tasksel install lamp-server

Anschließend noch git installieren:

$ sudo apt-get install git

Und danach können wir auch schon das FLOW3 Repository klonen:

$ cd /var/www/
$ sudo chmod -f -R 777 * ./
$ git clone --recursive git://git.typo3.org/FLOW3/Distributions/Base.git FLOW3
$ sudo chmod -f -R 777 * ./

Apache konfigurieren

Danach müssen wir uns noch einen virtual host anlegen für FLOW3, ähnlich wie beim Zend Framework:

$ sudo nano /etc/apache2/sites-enabled/000-default

Danach den Inhalt der Datei so anpassen (Document Root wird auf FLOW3/Web/ gesetzt und AllowOverride auf All gestellt)

<VirtualHost *:80>
        ServerAdmin webmaster@localhost

        DocumentRoot /var/www/FLOW3/Web
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
        <Directory /var/www/FLOW3/Web>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride All
                Order allow,deny
                allow from all
        </Directory>

        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
                AllowOverride None
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        CustomLog ${APACHE_LOG_DIR}/access.log combined

    Alias /doc/ "/usr/share/doc/"
    <Directory "/usr/share/doc/">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride None
        Order deny,allow
        Deny from all
        Allow from 127.0.0.0/255.0.0.0 ::1/128
    </Directory>

</VirtualHost>

Und Speichern.

Als nächstes noch mod_rewrite enablen:

$ sudo nano /etc/apache2/mods-enabled/rewrite.load

Folgendes reinkopieren:

LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so

Und zum Schluß den apache neu starten:

$ sudo apachectl restart

FLOW3 konfigurieren

Zuerst die .htaccess anpassen, um den Application Context auf development zu setzen (ähnlich wie beim Zend Framework):

$ nano /var/www/FLOW3/Web/.htaccess

Das # vor folgender Zeile wegmachen:

#SetEnv FLOW3_CONTEXT development 

Danach die Routes.yaml editieren:

$ nano /var/www/FLOW3/Configuration/Routes.yaml

Und folgenden Inhalt einfügen:

##
# TYPO3CR subroutes

-
  name: 'TYPO3CR'
  uriPattern: 'typo3cr'
  defaults:
    '@format': 'html'
  subRoutes:
    TYPO3CRSubroutes:
      package: TYPO3CR

##
# Subroutes from the Welcome package.
#
# If the package "Welcome" is installed, its fallback route will point to an
# informative welcome screen.

-
  name: 'Welcome'
  uriPattern: ''
  subRoutes:
    WelcomeSubroutes:
      package: Welcome

##
# Subroutes from the DocumentationBrowser package.
#

-
  name: 'Documentation Browser'
  uriPattern: ''
  subRoutes:
    DocumentationBrowserSubroutes:
      package: DocumentationBrowser

##
# FLOW3 subroutes

-
  name: 'FLOW3'
  uriPattern: ''
  defaults:
    '@format': 'html'
  subRoutes:
    FLOW3Subroutes:
      package: FLOW3

Und danach die Settings.yaml bearbeiten:

$ nano /var/www/FLOW3/Configuration/Settings.yaml

Und diesen Inhalt einfügen:

FLOW3:
  persistence:
    backend: 'F3\FLOW3\Persistence\Backend\GenericPDO\Backend'
    backendOptions:
      dataSourceName: 'mysql:host=localhost;dbname=flow3_test'
      username: root
      password: password

mySQL DB aufsetzen

Damit FLOW3 mysql auch als Storage Backend nehmen kann, müssen wir die in der Settings.yaml eingetragene DB natürlich auch noch anlegen:

$ mysql -u root -p
CREATE DATABASE flow3_test CHARACTER SET utf8 COLLATE utf8_general_ci;
exit;

Anschließend muss noch ein dump file eingespielt werden, in dem FLOW3 seine default Datenbank Tabellen anlegt. Der Dump liegt unter /var/www/FLOW3/Packages/Framework/FLOW3/Resources/Private/Persistence/SQL/DDL.sql, hat bei mir aber nicht auf Anhieb funktioniert den einzuspielen.

Ich habe die Datei wie folgt geändert:

BEGIN;

CREATE TABLE entities (
  identifier CHAR(36) PRIMARY KEY NOT NULL,
  type VARCHAR(250) NOT NULL,
  parent CHAR(36)
) TYPE=innodb;
CREATE INDEX elookup ON entities (identifier, type);

CREATE TABLE valueobjects (
  identifier CHAR(40) PRIMARY KEY NOT NULL,
  type VARCHAR(250) NOT NULL
) TYPE=innodb;
CREATE INDEX vlookup ON valueobjects (identifier, type);

CREATE TABLE properties (
  parent CHAR(40) NOT NULL,
  name VARCHAR(250) NOT NULL,
  multivalue INTEGER NOT NULL DEFAULT '0',
  type VARCHAR(250) NOT NULL,
  PRIMARY KEY (parent, name)
) TYPE=innodb;

CREATE TABLE properties_data (
  parent CHAR(40) NOT NULL,
  name VARCHAR(250) NOT NULL,
  `index` VARCHAR(250),
  type VARCHAR(250) NOT NULL,
  array CHAR(24),
  string TEXT,
  `integer` INTEGER,
  `float` DECIMAL,
  `datetime` INTEGER,
  `boolean` CHAR(1),
  object VARCHAR(40)
) TYPE=innodb;
CREATE UNIQUE INDEX id ON properties_data (parent, name, `index`);
CREATE INDEX id1 ON properties_data (parent, name);

COMMIT;

Und danach per

$ mysql -u root -p flow3_test < /var/www/FLOW3/Packages/Framework/FLOW3/Resources/Private/Persistence/SQL/DDL.sql

eingespielt. Möglicherweise geht das dann auch irgendwie besser, hat aber zumindest funktioniert.

Finishing Notes

Das wars dann auch schon, wenn man nun die vmware mit ihrer IP Adresse aufruft im Browser sollte der Welcome Screen von FLOW3 zu sehen sein. Danach kann man im Getting Started Tutorial weiter machen. Was mir noch aufgefallen ist, in Kapitel 8 ist von

$this->objectFactory->create('...')

die Rede, welches im Controller aufgerufen wird. Das funktioniert aber nicht mehr, in dieser Alpha Version funktionierte das Erstellen eines neuen Objekts über:

$this->objectManager->create('...');


Mein erster Eindruck

... ist natürlich nur sehr vorläufig, und nur basierend auf dem bißchen was ich von dieser Alpha (!) 14 gesehen habe. Trotzdem, here it comes:

Losgelöst von jeglichen Komponenten nutzt FLOW3 natürlich auch sehr gut die neuen PHP 5.3 Features, vornehmlich eben Namespaces. Der Code, den ich bisher gesehen habe gefällt mir gut, durchdachte und nachvollziehbare Struktur - ich denke, trotz vieler nicht-trivialer Konzepte in FLOW3 sollte man dort einen guten Einstieg finden können.

Was mir bisher gut gefallen hat: die Dependency Injection Komponente. DI per Annotations funktioniert sehr gut und sehr "seamingless", ohne großen XML Konfigurationsaufwand. Allerdings gebe ich auch zu, dass was ich momentan bei den meisten meiner Projekte verwende (Symfony Dependency Injection) scheint mir zumindest vom Featureset her nicht unterlegen zu sein.

Worauf ich mich sehr gefreut habe, was ich allerdings noch nicht ausprobieren konnte, ist die AOP Komponente. Möglicherweise sind die Beispiele, die ich mir kopiert habe veraltet. AOP im Allgemeinen interessiert mich sehr und könnte einige Probleme lösen, ich bin allerdings auch gespannt, wie sich AOP im Praxis-Einsatz bewährt und ob das nicht alles zu undurchsichtig wird durch zu viel "Magic" im Hintergrund. Nützlich, aber auch herausfordernd - das war zumindest mein Eindruck wie ich das letzte Mal mit AOP in Spring.NET zu tun hatte. Mal sehen, wie es in FLOW3 implementiert ist...

Zur MVC Komponente: Es ist halt eine ActionController Implementierung. Ok, kann nie schaden. Zumindest ist der Aufbau der Verzeichnisstruktur wesentlich sinnvoller als z. B. im Zend Framework. Man hat auch out-of-the-box Dependency Injection in den Controllern, das ist ja beim Zend Framework auch nicht gerade selbstverständlich... Mit der Fluid Template Engine konnte ich mich schon recht früh nicht anfreunden, die Syntax und die Denke dieser Komponente geht mir zu sehr in Richtung JSP Templates. Hier würde ich definitiv eine Zend_View artige Lösung mit plain-old .phtml Templates vorziehen. Das ist natürlich auch eine persönliche Präferenz.

Die für mich wichtigste Frage in allen Frameworks ist für mich grundsätzlich: wie ist das Problem des DB Zugriffs gelöst? Also: Persistence. Das Zend Framework hat sich da insofern aus der Affäre gezogen in dem sie einen vor die Wahl gestellt haben; nimm das crappy Zend_DB_Table Zeug oder lass dir selbst was einfallen. Glücklicherweise ist Doctrine 2 einfach zu integrieren in das Zend Framework und stellt einen sehr mächtigen ORM zur Verfügung (zwar Stand heute nicht gerade frei von Bugs, aber gut...). Die Persistence Komponente in FLOW3 hat mich deswegen sehr interessiert. Wie ich dann festgestellt habe, besteht das Storage Backend im wesentlichen aus 4 Tabellen. Von der Struktur her kommts mir so ein bißchen vor wie eine "poor mans object database". Überzeugt mich nicht 100%ig im Moment, keine Ahnung, was da noch kommt bis zur 1.0.0 final, deswegen will ich da auch nicht zu tief einsteigen in dieses Thema. Bezogen auf die aktuelle Tabellenstruktur fasse ich mal eine meiner Bedenken auf ein Wort zusammen: Reporting? Anyone?

Was mich abseits von Komponenten und Techie-Zeug verwundert momentan (Stand 29.12.10): wie wenig ich zu FLOW3 finden konnte im Internet. Ich finde ein paar Blogs und Websites von Agenturen, auf denen mir erzählt wird "FLOW3 wird toll" mit dem Nachsatz "Aber ich habs noch nicht ausprobiert". Hiermit sei gesagt: ich habs etwas ausprobiert und FLOW3 könnte toll werden. Was aber schade wäre: wenn der Release von FLOW3 aufgrund der einfach deutlich größeren Präsenz von Konkurrenten wie Zend Framework oder Symfony (die ja auch beide neue Versionen auflegen) nicht genügend beachtet werden würde. Ich hoffe einfach mal, diese Beachtung stellt sich aber der Beta Phase dann ein.

Ich bin sehr gespannt welche Richtung die weitere Entwicklung dieses ambitionierten Frameworks nimmt. Wenn die technisch hoch gesteckten Ziele so erreicht werden können wie gedacht und FLOW3 auch nochmal einen Performance Schub bekommt, dann wird es eine Bereicherung für die PHP Welt sein. Aber bis dahin ist es auch noch ein weiter Weg.

Comment » | FLOW3, PHP

Idee für einen einfachen, abstrakten Custom Validator mit PHP 5.3 Closures

Oktober 27th, 2010 — 11:45am

Gerade eben hatte ich Spaß daran mit der neuen Möglichkeit Closures in PHP 5.3 zu verwenden herumzuspielen. Mir kam die Idee, das Ganze mit Zend_Validate und Zend_Form zu einem Custom Validator zu kombinieren.

Folgende Klasse kam dabei heraus:

namespace Application\Validators;

class ClosureValidator extends \Zend_Validate_Abstract
{
    const GENERIC_ERROR_KEY = 'genericErrorKey';

    protected $_messageTemplates = array();

    /**
     * @var \Closure
     */
    private $predicate;

    public function __construct(\Closure $predicate)
    {
        $this->predicate = $predicate;
    }

    public function isValid($value, $context = null)
    {
        $predicate = $this->predicate;
        list($result, $errorMessage) = $predicate($value, $context);

        if(!is_bool($result))
            throw new \RuntimeException('First return value of predicate must be boolean value.');

        if(!is_string($errorMessage))
            throw new \RuntimeException('Second return value of predicate must be string value (error message).');

        if($result) return true;

        $this->_messageTemplates[self::GENERIC_ERROR_KEY] = $errorMessage;
        $this->_error(self::GENERIC_ERROR_KEY);

        return false;
    }
}

Wie habe ich die Klasse verwendet?

Die Klasse kam bei mir zum Einsatz bei einem Zend_Form mit dem ein User sein Passwort aktualisieren kann.

Die Bedingung war, dass der User sein Passwort nur aktualisieren kann, wenn er sein aktuelles Passwort richtig eingibt. Der Einfachheit halber gehe ich jetzt mal davon aus, dass die Passwörter in der DB im Klartext gespeichert sind und nicht gehashed (natürlich sollte man NIE die original Passwörter speichern, sondern immer nur als Hash!).

So wird zunächst mal das Formular verwendet (bekommt also eine User Instanz im Konstruktor mit übergeben):

$user = new \Application\Models\User();
$user->setPassword('CURRENT_PASSWORD');

$form = new \Application\Forms\UserPasswordChangeForm($user);

Im Formular wird der ClosureValidator dann wie folgt hinzugefügt:

namespace Application\Forms;

class UserPasswordChangeForm extends \Zend_Form
{
    /**
     * @var \Application\Models\User
     */
    private $user;

    public function __construct(\Application\Models\User $user, $options = null)
    {
        $this->user = $user;
        parent::__construct($options);
    }

    public function init()
    {
        $user = $this->user;

        $this->setMethod('post');

        $this->addElement(new \Zend_Form_Element_Password('currentPassword', array(
            'label'         => 'Current Password:',
            'required'      => true,
            'filters'       => array('StringTrim'),

            // Validator mit Closure hier hinzufügen
            'validators'    => array(new \Application\Validators\ClosureValidator(function($value) use(&$user) {
                return array(
                    $user->getPassword() == $value,
                    'Invalid Password'
                );
             })),
        )));

        // ... weitere Formularelemente weggelassen

        $this->addElement(new \Zend_Form_Element_Submit('submit', array(
            'ignore'   => true,
            'label'    => 'Save',
        )));
    }
}

Der Validator bekommt eine Closure Funktion mitübergeben, welche ein Array mit zwei Elementen zurückgeben muss: zum einen das Validation Ergebnis (boolean true oder false) und zum anderen einen String mit einer Error-Message, die im Fehler-Fall verwendet werden soll.

Über die Implementierung kann man sicher reden und obs das ganze einfacher oder komplizierter macht ist sicher auch diskussionswürdig, aber als Idee finde ich es doch irgendwie ganz nett. Mal schauen, ob man das so brauchen kann.

Comment » | PHP, Zend Framework

Zend Framework Screencasts

Oktober 27th, 2010 — 11:29am

Kurzer Tip für zwischendurch: wers nicht schon kennt, unter http://www.zendcasts.com/ sind sehr interessante Sceencasts verfügbar, welche verschiedene Themen rund um die Entwicklung mit dem Zend Framework beschreiben. Themen sind unter anderen Unit Testing, Doctrine 2 Integration, Access Control und noch einige andere. Die Screencasts erklären die Vorgehensweise wirklich gut, manchmal kann man über den Coding Stil zumindest diskutieren (wie meistens eigentlich), aber oft bringen einen die Screencasts auf neue Ideen wie man das ein oder andere Problem angehen könnte.

Comment » | PHP, Zend Framework

Doctrine 2 ins Zend Framework integrieren

Oktober 3rd, 2010 — 7:25pm

Ich verwende Doctrine in der Version 2.0.0RC1-DEV und das ZF 1.10. Die Doctrine Dateien kommen ins /library/ Verzeichnis (wo auch die Dateien des Zend Frameworks abgelegt sind), so dass dieses wie folgt aussieht:

library/
library/Zend/
library/Zend/Acl
...
library/Doctrine/
library/Doctrine/Common
library/Doctrine/DBAL
library/Doctrine/ORM
library/Doctrine/Symfony

Folgendes hat bei mir funktioniert, um Doctrine mit dem Zend Framework zum Laufen zu bringen:

Zuerst folgende Einstellungen in die application.ini (sind die Verbindungsdaten für die Datenbank):

db.host = localhost
db.username = root
db.password = password
db.dbname = my_db
db.driver = Pdo_Mysql

Danach diese Methode in die Bootstrap.php:

    protected function _initDb()
    {
        $dbConfig = $this->getOption('db');

        $db = Zend_Db::factory($dbConfig['driver'], array(
            'host'      => $dbConfig['host'],
            'username'  => $dbConfig['username'],
            'password'  => $dbConfig['password'],
            'dbname'    => $dbConfig['dbname'],
        ));

        return $db;
    }

Hier werden die DB Zugangsdaten ausgelesen und eine neue Datenbankverbindung per Zend_DB erstellt. Doctrine hat nämlich auch die Möglichkeit, bestehende Verbindungen, welche per PDO erstellt wurden, zu verwenden.

Jetzt kommen wir zum Doctrine Setup, ebenfalls in die Bootstrap.php NACH _initDb():

    protected function _initEntityManager()
    {
        require_once('Doctrine/Common/ClassLoader.php');

        $classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
        $classLoader->setIncludePath(realpath(APPLICATION_PATH . '/../library/'));
        $classLoader->register();

        $config = new \Doctrine\ORM\Configuration();

        $cache = new \Doctrine\Common\Cache\ArrayCache();

        $config->setMetadataCacheImpl($cache);
        $config->setProxyDir(realpath(APPLICATION_PATH . '/proxies'));
        $config->setProxyNamespace('App\Proxies');
        $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(realpath(APPLICATION_PATH . '/models')));
        $config->setAutoGenerateProxyClasses(true);
        // (01)
        $config->setSQLLogger(new Doctrine\DBAL\Logging\EchoSqlLogger());

        $connectionOptions = array(
           // (02)
            'pdo' => $this->getResource('db')->getConnection(),
        );

        $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
        // (03)
        $em->getConnection()->setCharset('UTF8');

        // 04
        Zend_Registry::set('entityManager', $em);
    }

Wichtig ist, das im application Verzeichnis die Ordner /proxies (hier speichert Doctrine generierte Proxy Klassen ab) und /models (hier werden die DB Models abgelegt) existieren.

Folgende Anmerkungen noch zu den Fußnoten im Code:

  • 01: Diese Zeile reinnehmen wenn die von Doctrine generierten SQL Abfragen ausgegeben werden sollen
  • 02: Hier die in _initDb() erstellte DB Verbindung nutzen – getConnection() gibt das PDO Connection Objekt zurück
  • 03: Reinnehmen wenn die Daten in der DB UTF-8 encoded sind
  • 04: Ganz auf simpel und Ugly: den entityManager in der ZendRegistry ablegen – man könnte den entityManager ($em) auch aus der _initEntityManager Methode zurückgeben, dann würde dieser in einem Controller via $this->getInvokeArg('bootstrap')->getResource('entityManager') zur Verfügung stehen.

Comment » | Doctrine, PHP, Zend Framework

Action Helper in Zend Framework und PHP 5.3

September 15th, 2010 — 7:01pm

Gehen wir von folgendem ActionHelper aus (da ich PHP 5.3 verwende mit namespace):

namespace MyStuff\Controller\Action\Helper;

class Foo extends \Zend_Controller_Action_Helper_Abstract {
    public function direct() {
        print 'direct() called';
    }
}

Einen ActionHelper registriert man im Zend Framework in etwa so (hier in der Bootstrap.php):

class Bootstrap {
    protected function _initHelper() {
        Zend_Controller_Action_HelperBroker::addHelper(new \MyStuff\Controller\Action\Helper\Foo());
    }
}

Nun sollte der ActionHelper in einer ControllerAction ja wie folgt verfügbar sein (da wir ja die direct() Methode implentiert haben):

class MyController extends Zend_Controller_Abstract() {
    public function indexAction() {
        $this->_helper->foo();
    }
}

… aber statt der erwarteten Ausgabe wird eine Exception geworfen mit folgender Message:

Action Helper by name Foo not found

Das Problem ist die Methode Zend_Controller_Action_Helper_Abstract::getName() welche im Zend Framework wie folgt implementiert ist:

    public function getName()
    {
        $full_class_name = get_class($this);

        if (strpos($full_class_name, '_') !== false) {
            $helper_name = strrchr($full_class_name, '_');
            return ltrim($helper_name, '_');
        } else {
            return $full_class_name;
        }
    }

Der ActionHelper wird also im PriorityStack des HelperBrokers unter seinem vollen Klassennamen INKLUSIVE namespace registriert und kann daher nicht einfach per $this->_helper->foo(); (=Klassenname ohne namespace) angesprochen werden.

In dem wir die Methode Zend_Controller_Action_Helper_Abstract::getName() in unserem ActionHelper überschreiben, können wir abhilfe schaffen, bis das Zend Framework in einer zukünftigen Version PHP 5.3 vollständig unterstüzt:

namespace MyStuff\Controller\Action\Helper;

class Foo extends \Zend_Controller_Action_Helper_Abstract {
    const HELPER_NAME = 'Foo';

    public function direct() {
        print 'direct() called';
    }

    /**
     * @override
     */
    public function getName()
    {
        return self::HELPER_NAME;
    }
}

… dann klappts auch mit dem Aufruf in einer Controller Action via $this->_helper->foo();

Comment » | PHP, Zend Framework

Kleiner PHP 5.3 Gotcha in Zusammenhang mit Exceptions

August 22nd, 2010 — 8:05pm

Gehen wir von folgendem PHP 5.3 Codesnippet aus:

namespace Foo\Bar;

class MyClass {
...
    public function test() {
        try {
            $this->irgendeine_methode_die_eine_exception_wirft();
        }
        catch(Exception $e) {
        }
    }
...
}

In dem Code Beispiel sollen alle Exceptions welche die Methode irgendeine_methode_die_eine_exception_wirft() werfen kann abgefangen werden. Und da alle Exceptions von der Klasse Exception ableiten, sollte es doch so funktionieren.

Was ist daran falsch (mal ganz abgesehen davon, dass man nur spezifische Exceptions abfangen sollte und wenn man Exceptions abfängt, dann sollte man mit diesen auch was sinnvolles machen, wie z. B. loggen)?

Die Exception wird nicht abgefangen, es gibt einen Fatal Error.

So ists richtig, sofern man tatsächlich alle Exceptions abfangen möchte (man bermerke den\ Backslash vor dem Klassennamen):

...
        catch(\Exception $e) {
...

Ansonsten wird der Klassenname Exception relativ zum aktuellen namespace aufgelöst, d. h. im ersten Codebeispiel wird nur eine Exception der Klasse Foo\Bar\Exception abgefangen. Was auch wünschenswert sein könnte. Aber möglicherweise nicht Intention des Autors war (ja, war ein Bock den ich geschossen hab, deswegen schreib ichs auf, damit ichs nicht nochmal mach ;-) ).

Eigentlich ziemlich logisch, wenn man sichs genau überlegt.

Comment » | PHP

PHP 5.3 und TYPO3 4.2.8 – beware

Juli 18th, 2009 — 4:06pm

So eben habe ich Bekanntschaft mit einem weiteren Feature der neuen PHP 5.3 Version gemacht. Als Deprecated gekennzeichnete Funktionen (so zum Beispiel ereg* Funktionen) werden nun explizit als PHP Fehlermeldungen ausgegeben.

Ob dies nun sinnvoll ist oder eher nicht, darüber kann man sich unterhalten. In TYPO3 4.2.8 werden allerdings noch ein paar deprecated Funktionen verwendet, und das hat entsprechende Fehlermeldungen zur Folge. Etwas blöd, denn bei PHP Fehlern zeigt der IIS (mein Dev Server) den Content der Seite nicht mehr an und gibt nur einen Http Status 500 / Internal Server Error zurück. Hm…

Merkwürdigerweise ließen sich die Deprecated Error nicht durch ein entsprechendes Error Reporting Setting verhindern. Was also machen? Entweder alle “alten” Methoden im TYPO3 Core ersetzen (nix gut, da ziemlich viele), oder eine Pfusch Lösung mit einem eigenen Error Handler. Ich hab mich auf die Schnelle einfach mal für zweiteres entschieden, Asche über mein Haupt. Aber als quick ´n dirty fix muss es für den Moment reichen.

Soweit meine Internet Recherche ergeben hat, war jemand fleißiger als ich und hat alle in 5.3 veralteten Funktionen im TYPO3 Core durch neue ersetzt und diesen Patch auch schon committed. Die Chancen stehen gut, dass in der nächsten TYPO3 Version keine Probleme mehr unter PHP 5.3 auftreten.

Comment » | PHP, TYPO3

Back to top