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);

Jetzt habe ich schon lange nicht mehr gebloggt und hoffe ich bin noch nicht bei allen aus der RSS Reader Liste geflogen. Aber in diesem Fall habe ich eine ganz gute Ausrede. Momentan programmiere ich fleißig an einem neuen webbasierten RSS Reader.

Auch nach vielen Stunden Suche habe ich keinen guten RSS Reader gefunden, den man auch auf dem eigenen Webspace installieren kann und der dabei einfach und praktisch zu bedienen ist. Bisher nutze ich Gregarius, der leider nicht mehr weiter entwickelt wird. Der Google Reader konnte auch überzeugen, aber hier mache ich mir aus Sicht des Datenschutzes sorgen, da die eigene Feedauswahl doch so einige Rückschlüsse auf Interessen und Hobbys zulässt. Ein weiterer interessanter Reader ist Fever, dieser kostet aber $30. Das wäre ich schon bereit zu zahlen, wenn die Software dann auch gut ist. Aber mit einer domainbezogenen Lizenz wollte ich mich dann einfach nicht herumschlagen. tiny tiny RSS scheint auch solide zu funktionieren, ist mir aber zu überladen und irgendwie unpraktisch.

Da Meckern nicht hilft habe ich mich also selbst ans Werk gemacht. Dazu habe ich mir aus bestehenden Reader die Features herausgepickt, die mir besonders gefallen, oder die ich bereits in Gregarius regelmäßig benutze.

Mein Wunsch-RSS-Reader muss darüber hinaus folgende Features haben (und hat sie auch schon):

  • Neben normalen Feeds soll der Reader auch Medien lesen können. Ich verfolge viele Fotoblogs und Communitys und die sollen komfortabel als Thumbnailübersicht angezeigt werden (und nicht wie gewöhnliche Textnachrichten).
  • Der Reader soll Prioritäten unterstützen. Jedes Feed soll dabei eine Priorität bekommen und je nach gewählten Prioritätsbereich wird nur eine Teilmenge (oder alle) Feeds angezeigt. In stressigen Lebensphasen lassen sich so gezielt Feeds ausblenden und man kann sich auf das Wesentliche konzentrieren. Am Wochenende kann dann der Prioritätsbereich erhöht und der Rest gelesen werden.
  • Es soll ein offenes Pluginsystem für die Datenquellen vorhanden sein. Neben gewöhnlichen RSS Feeds soll man auch die Möglichkeit haben später neue Quellen zu definieren. So kann z.B. dann eine Seite manuell geparsed, verarbeitet und verfolgt werden, auch wenn diese kein RSS Feed anbietet (hierzu braucht man dann natürlich Programmierkenntnisse).
  • Eine komfortable ajaxbasierte Oberfläche, die schlicht und übersichtlich sein soll. Zudem soll sich alles intuitiv mit Drag n Drop bedienen lassen (z.B. die Feeds zwischen den Kategorien verschieben, oder die Reihenfolge der Kategorien verändern).
  • Es soll die Möglichkeit bestehen einen bestimmten Zeitraum vorzugeben, um nur Einträge dieser Periode zu sehen.
  • Feeds sollen wahlweise mit Cronjob oder ohne (also via Ajax Aufruf) aktualisiert werden können.

Als Namen habe ich rsslounge aggregator gewählt. Ich finde das ziemlich passend, denn ich lese immer abends oder morgens ganz gechillt, bei einer Tasse Tee meine Feeds. Zudem soll das Prioritätsfeature dabei helfen Stress zu vermeiden und immer nur die Menge an Feeds zu zeigen, für die man auch Zeit hat. Der Rest kann ruhig bis zum Wochenende warten (oder wird nur grob überflogen).

Was nun auf der Agenda steht, ist ein ausführlicher Test. Dazu werde ich den Reader erst selbst einige Monate testen und anschließend einen geschlossenen Betatest unterziehen. Erst dann werde ich den Reader unter GPL in Deutsch und Englisch jedem frei zur Verfügung stellen. Hinzu kommt dann natürlich auch eine Anleitung, wie man selbst Plugins schreiben kann.

Als ersten Einblick gibts erst einmal ein paar Screenshots. Ich hoffe, dass ich bei einigen Interesse wecken kann. Mal sehen was daraus wird.

rsslounge Screenshot

rsslounge Screenshot

rsslounge Screenshot

rsslounge Screenshot

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.

Achtung: Es gibt mittlerweile eine neue Version dieses Skriptes: hier klicken um zu dieser zu gelangen.

Besonders wenn man immer wieder den Rechner wechselt (zwischen Arbeit, Uni, Zuhause, Notebook etc.) ist es praktisch einen zentralen Notizzettel zu haben, der von überall erreichbar ist. Dazu hab ich mir ein kleines und sehr simples Skript geschrieben, welches ein einfaches Eingabefeld zur Verfügung stellt. Dort können dann Informationen, Links und Notizen eingetragen werden. Ein Klick auf “speichern” schreibt dann die Änderungen zurück (sprich: schreibt diese direkt in eine einfache Textdatei). Also nichts Besonders, aber nachdem ich das nun täglich nutze könnte ich mir vorstellen das es auch für meine Blogleser ganz interessant sein könnte. Hier mal ein Screenshot:

Vorschau Notizen

Das Ganze basiert auf TinyMCE. Meine Entwicklungsarbeit lag also bei ganzen 5 Minuten, daher ist es lizenzfrei verfügbar (mit Ausnahme von den Lizenzbestimmungen von TinyMCE natürlich, die beachtet werden müssen).

Bitte kommentiert mir, wenn ihr etwas Besseres benutzt oder das Skript hier verbessert habt.

Download Skript (489 kb)

Wie mittlerweile in mehreren Quellen erwähnt, haben bisherige Zend Framework Versionen (kleiner 1.7.7) eine Sicherheitslücke in der Klasse Zend_Filter_StripTags. Hier können bei Nutzung einer Whitelist trotz Filterung unerlaubte Tags eingeschleust werden. Besonders kritisch ist das bei Anwendungen wie diesen Blog. Es wird empfohlen auf die aktuellste Version zu patchen, oder zumindest die StripTags Datei mit einer gepatchten Version zu ersetzen.

Weitere Infos gibt es im Zend Framework Forum.

Vor kurzem bin ich in Thomas Weidners Blog auf einen Hinweis gestoßen, dass sich fehlende Übersetzungen problemlos loggen lassen. Dazu übergibt man dam Zend_Translate Objekt einfach einen Logger, in dem dann, im Falle einer fehlenden Übersetzung, eine Meldung “Untranslated message: xyz” geschrieben wird. Eine sehr praktische Möglichkeit, da bei größeren Projekten praktisch laufend neue Texte hinzugefügt und dann doch manchmal einzelne Übersetzungen vergessen werden.

In meiner zentralen Bootstrap Datei bereite ich in der Regel den Logger vor (oder hole mir den Logger aus dem Cache). Ebenfalls wird dort das Übersetzungsobjekt vorbereitet. Hier kann man also in einem Zug und ohne großen Aufwand den Logger für das Zend_Translate Objekt bekannt machen.

An dieser Stelle das Beispiel aus der Zend Framework Dokumentation:


$translate = new Zend_Translate('gettext', $path, 'de');

// Eine Log Instanz erstellen
$writer = new Zend_Log_Writer_Stream('/path/file.log');
$log = new Zend_Log($writer);

// Diese der Übersetzungs-Instanz hinzufügen
$translate->setOptions(array(
'log' => $log,
'logUntranslated' => true));

$translate->tranlate('unbekannter String');

Das Thema Benutzerrechte ist ja ein eher lästiges Thema, sollte aber schon möglichst früh in die Planung einer Applikation einfließen. Nach einigen Überlegungen und Diskussionen von verschiedenen Vorgehensweisen habe ich mich für einen recht einfachen Ansatz entschieden. Das Prinzip will ich hier kurz beschreiben, vielleicht ist es ja für den ein oder anderen interessant.

Die Lösung sollte möglichst wartungsfrei und einfach sein, zudem sollte natürlich die ACL Komponente vom Zend Framework zum Einsatz kommen. Folgende Rahmenbedingungen gelten für diese Lösung:

  • Es gibt Benutzer und Gruppen
  • Ein Benutzer/Gruppe hat ein Recht oder hat es nicht (keine eingeschränkten Rechte oder feinere Abstufungen)
  • Ein Benutzer ist in keiner, einer oder mehreren Gruppen
  • Ein Recht das einmal vergeben wurde kann nicht mehr wiederufen werden (z.B. durch eine Mitgliedschaft in einer weiteren Gruppe die das Recht explizit nicht hat)

Zend_Acl sieht Rollen und Ressourcen vor. Für Ressourcen können schließlich Rechte vergeben werden. Eine Rolle ist hier ein Benutzer oder eine Gruppe. Die Ressourcen ergeben sich aus den verfügbaren Modulen, Controller und Actions des MVC Patterns. Sollen also Rechte vergeben werden (z.B. in der Administration der Gruppen), so werden alle Module, Controller und Actions ausgelesen. Für diese können dann die Rechte gesetzt und in der Datenbank gespeichert werden. Der Benutzer hat erst einmal keine Rechte und für jede Berechtigung muss explizit ein Eintrag in einer Tabelle erzeugt werden. Hierfür wird eine Tabelle mit folgender Struktur angelegt:
Beispiel Tabelle

Gibt es z.B: für ein Blogsystem ein Modul admin mit dem Controller articles und den Actions write, delete und list, so sind verschiedene Berechtigungen denkbar. Oben stehender Auszug zeigt drei Beispieldatensätze in der Tabelle für Berechtigungen, die auf das Beispiel passen könnten:

  • Benutzer 1 darf alle Actions in allen Controller des Moduls admin ausführen
  • Die Gruppe 55 darf alle Actions im Controller articles des Moduls admin ausführen
  • Benutzer 12 darf nur die Action list im Controller articles des Moduls admin ausführen

Eine Prüfung der Rechte kann zentral an einer Stelle erfolgen. Hier kann ein Plugin beim Front Controller registriert werden. Ich bevorzuge es hier den Zend_Action_Controller in eine eigenen BasisController Klasse abzuleiten und dort in dem Hook init verschiedene Dinge, wie eben diese Berechtigungen zu prüfen. Alle eigenen Controller werden dann wiederum vom BasisController abgeleitet. So wird vor der Ausführung einer jeden Action zuerst die Berechtigung geprüft und ggf. mit einer Fehlerseite abgebrochen, falls diese fehlt.

Um eine Berechtigung zu prüfen, kann die Komponente Zend_Acl genutzt werden. Dies ist allerdings sehr aufwendig, deshalb ist an dieser Stelle unbedingt ein Caching notwendig. Mit der Zend_Cache Komponente ist das allerdings sehr einfach: es wird einmal das ACL Objekt erzeugt und dann für die Dauer der Session gecached (oder direkt in der Session des Benutzers gespeichert).

Ebenso müssen die überhaupt verfügbaren Rechte nicht extra konfiguriert werden, sondern ergeben sich automatisch aus den vorhandenen Modulen, Controller und Actions. Um diese für eine Rechtevergabe in der Administrationsoberfläche auszulesen, verwende ich das seit PHP 5 verfügbare Reflection. Um also alle Module, Controller und Actions zu erhalten kann folgende Funktion genutzt werden:

/**
 * returns all available modules, controllers and actions
 *
 * @param Zend_Controller_Front $fc the current front controller
 * @return array
 */
protected function getRessources($fc) {
	$ressources = array();

	// search all registered modules
	foreach($fc->getControllerDirectory() as $module => $dir) {

		$ressources[$module] = array();

		// search all controller files
		foreach(scandir(realpath(dirname(__FILE__)) . $dir) as $file) {
			$currentFile = realpath(dirname(__FILE__)) . $dir . "/" . $file;

			// search in all controller files for actions
			if(is_file($currentFile) && strpos($file,"Controller")!==false) {
				require_once($currentFile);
				$classname = $module . "_" . str_replace(".php","",$file);
				$controller = new ReflectionClass($classname);

				$methods = array();
				foreach($controller->getMethods() as $method) {
					// don't include error handler
					if($method->getName()=="errorAction")
						continue;

					$length = strpos($method->getName(),"Action");
					if($length!==false)
						$methods[] = substr($method->getName(),0,$length);
				}
				if(count($methods)>0) {
					$controllername = strtolower(substr($file,0,strpos($file,"Controller")));
					$ressources[$module][$controllername] = $methods;
				}
			}
		}
	}

	return $ressources;
}

Als Administrationsoberfläche kann ich den Tree von Extjs empfehlen, der auch Checkboxes unterstützt:

Admin Rechte Extjs Tree

An dieser Stelle möchte ich kein gesamtes Codebeispiel geben, da die Integration ins eigene Projekt und die eigene Umsetzung durchaus variieren kann. Was ich hier als Grundidee vermitteln will ist die Ausrichtung der ACL an der MVC Struktur. Einmal ins eigene Projekt eingebaut ist keine weitere Anpassung mehr notwendig.

Über Feedback, Bedenken und Anmerkungen freue ich mich, da diese Lösung pragmatisch, aber sicherlich auch verbesserungswürdig ist.