Ein Widget-System
Ein Widget-System wirft zu vorderst die Frage auf: “Hä? Wozu? Was ist das”, doch dahinter verbirgt sich eine einfache Logik. Das Zend Framework bietet bereits Action- und ViewHelper, die natürlich in ihren jeweiligen Bereichen sehr praktisch sind. Wenn es aber an ein CMS-System geht in dem man den Usern die Möglichkeit bieten möchte ebenso Helper in die Seiten einzubinden, dann kann man ihnen unmöglich erlauben einfach auf diese normalen Helper zuzugreifen. Für diesen Fall benötigt man ein eigenständiges System um die Stabilität des Gesamtsystems zu garantieren. Konsistenz ist nur einer der Vorteile, die natürlich wahrlich Subjektiv und Einsatzabhängig sind.
Zend_Translate Webcast
Es gibt bekannterweise viele Menschen im Bereich der Zend Framework Entwicklung, die noch nicht so richtig klarkommen mit den Fähigkeiten der Translate Komponente. Nun habe ich einen Webcast gefunden, der nicht schlecht ist und alles wirklich gut darstellt. Ein Grundvermögen für die englische Sprache ist Voraussetzung für diesen Webcast, aber auch verständlich (halbwegs) für jene, die sich nur das Video ansehen können ohne etwas zu verstehen.
Das Video wird euch die Komponenten Zend_Translate und Zend_Locale näher bringen. Über Plugins und View Helper kann man diese verwenden. IN dem Video wird gezeigt, wie man sprach-freundliche Routen erstellt, wie man einen Sprach-Wechsler erstellt und wie man .csv Sprach-Dateien ausliest und einbindet.
Ein einfaches ACL System über Zend_Config_Ini
Ich will hier ein knappes und einfaches ACL und Auth System vorstellen, das ich mir gebastelt habe. Natürlich ist es hier sehr viel vereinfachter dargestellt, aber es funktioniert ;)
Erstmal die, welche in meinem Beispiel in /application/configs/ lagert. Dort dürfte es nichts zu erklären geben.
; Die existierenden Rollen angeben role.0 = "guest" role.1 = "user" role.2 = "admin" ; Die existierenden Resourcen angeben res.0 = "index" res.1 = "error" res.2 = "user" res.3 = "test" ; Nun die genehmigungen für die einzelnen ROllen access.guest.0 = "index" access.guest.1 = "error" access.guest.2 = "user" access.user.0 = "index" access.user.1 = "error" access.user.2 = "user" access.user.3 = "test" access.admin.0 = "index" access.admin.1 = "error" access.admin.2 = "user" access.admin.3 = "test"
Dazu gibt es eine Model Datei. Man kann diese natürlich auch anderweitig lagern. Ich habe es aber vorerst so gelöst. Die Klasse erweitert Zend_Acl. Als Parameter kann man den Standort einer Konfigurationsdatei angeben. Standard ist aber der o.g. Pfad. Es wird dann die Konfiguration geladen und in ein Array umgewandelt. Dieses Array wird nun durchgegangen. Die Resourcen angelegt und die Rollen. Wenn es nun für eine Rolle auch Einstellungen gibt, dann werden diese ebenso angelegt. Da Zend_Acl grundsätzlich erstmal alle Resourcen verbietet werden wir auch all jene erlauben, die erlaubt sein dürfen.
Wenn keine passende Konfiguration vorliegt, dann wird eine Zend_Acl_Exception geworfen.
<?php
/**
* Festlegen der ACL Strukturen
*/
class Model_Acl extends Zend_Acl{
private $_config;
public function __construct($config = null){
if($config != null)
{
$this->_config = (string) $config;
}
else
{
$this->_config = (string) APPLICATION_PATH . "/configs/acl.ini";
}
# Ini Datei muss im Construct mitgegebenw erden
if(file_exists($this->_config))
{
# Konfig einlesen und in ein Array wandeln
$config = new Zend_Config_Ini($this->_config);
$config = $config->toArray();
# Die Ressourcen dem ACL Syste4m hinzugüen
foreach ($config["res"] as $res)
{
$this->add(new Zend_Acl_Resource($res));
}
# DIe Rollen dem ACL System hinzufügen
foreach($config["role"] as $role)
{
$this->addRole(new Zend_Acl_Role($role));
# Wenn es zu der Rolle auch was in der Accesslist gibt, dann füge das hinzu
if(isset($config["access"][$role]))
{
foreach($config["access"][$role] as $access)
{
$this->allow($role, $access);
}
}
}
}
else
{
throw Zend_Acl_Exception('Keine gültige Konfig angegeben!');
}
}
}
Irgendwo muss das Acl System aber auch initialisiert werden. Dafür habe ich ein Auth Plugin. In diesem Plugin nutze ich öfters ein Log, das ich mit “firelog” in der Registry benannt habe. Entweder man ändert dies in sein eigenes Log oder nimmt das Logging gänzlich raus. Die ganze Routine läuft nach dem Routing und vor dem Dispatch, damit Controller und Action noch beeinflusst werden können.
Eigentlich sollte das ganze Selbsterklärend sein für jemanden, der zumindest schon ein wenig gelesen hat. Ich empfehle hier das Kapitel Zend_Auth im Reference Guide.
Ansonsten sei nur gesagt, dass zu anlässen wie der nicht funktionierende Login oder das erfolgte einloggen Zielcontroller und Zielaction geändert werden, damit andere Seiten erscheinen. So braucht man sich in den Controllern selbst keine Gedanken mehr über das Auth System machen.
<?php
class Zorta_Plugin_Auth extends Zend_Controller_Plugin_Abstract {
/**
* Nachdem das Routing durchgeführt wurde geht es los mit dem Auth System
* @uses Zend_Controller_Request_Abstract
*/
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
# Lade eine Instanz von Zend_Auth
$auth = Zend_Auth::getInstance();
# Wenn der Request ein POST ist und die Action Login aufgerufen wurde, dann versuche den Login
if($request->isPost() && $request->getActionName() == "login"){
# POST Daten checken
$username = $request->getParam('username');
if($username == "")
$username = "Dummy";
$userpass = $request->getParam('userpass');
if($userpass == "")
$userpass = "Dummy";
# Zend_DB_Table aus der Registry holen als Datenbankadapter für die Authorisation
$db = Zend_Registry::get('Zend_Db_Table');
$adapter = new Zend_Auth_Adapter_DbTable($db,'user','user_name','user_pass','MD5(?)');
# Username und Passwort setzen
$adapter->setCredential($userpass);
$adapter->setIdentity($username);
# Wenn die Nutzerdaten Ok waren, dann schreibe das Auth in den Storage
if($auth->authenticate($adapter) && $auth->getIdentity() != false){
# Schreibe die Identität in die Session
$auth->getStorage()->write($adapter->getResultRowObject(null, 'password'));
# Log, dass der Login durchgeführt wurde
Zend_Registry::get('firelog')->log('Login accomplished!', Zend_Log::INFO);
# Ab zum Index (Willkommensseite)
$request->setControllerName('index');
$request->setActionName('index');
}
else
{
# Wenn es nicht ok war, log und ab zurück zum Login
Zend_Registry::get('firelog')->log('Login failed!', Zend_Log::INFO);
$request->setControllerName('user');
$request->setActionName('login');
}
}
# Wenn keine Identität vorliegt
if(!$auth->hasIdentity())
{
# Keine Identität, loggen und auf Zugriff prüfen mit der Gruppe "Gast"
Zend_Registry::get('firelog')->log('User is not logged in!', Zend_Log::INFO);
$this->testAcl($request, "guest");
}
else
{
# Authorisiert, log und Zugriffsprüfung
Zend_Registry::get('firelog')->log('User is logged in!', Zend_Log::INFO);
$this->testAcl($request, $auth->getIdentity()->user_group);
}
}
/**
* Methode zum prüfen der Authorisation mittels ACL
* @param Zend_Controller_Request_Abstract $request
* @param $group Benutzergruppe
*/
public function testAcl(Zend_Controller_Request_Abstract $request, $group = null)
{
# Laden der ACL Struktur und erstellen einer Instanz
require_once APPLICATION_PATH . '/model/Acl.php';
$acl = new Model_Acl();
# Prüfe den Status der Zugriffsberechtigung
if($acl->isAllowed($group,$request->getControllerName()))
{
# Wenn die Ressource erlaubt ist
Zend_Registry::get('firelog')->log('User has Auth for: '. $request->getControllerName(), Zend_Log::INFO);
}
else
{
# Wenn die Resource nicht erlaubt ist
Zend_Registry::get('firelog')->log('User has no Auth for: '. $request->getControllerName(), Zend_Log::ALERT);
$request->setControllerName('user');
$request->setActionName('noauth');
}
}
}
Zend_Mail Filestream
Um Zend_Mail zu testen benötigt man keinen Online Mailserver oder einen Webserver auf seinem lokalen Entwicklungssystem. Warum nicht mit einem Transporter arbeiten, der einem die versandten E-Mails in eine Datei schreibt? Und genau das stelle ich hier vor.
Man benötigt zuerst in seiner Library den Transporter
class Zorta_Mail_Transport_File extends Zend_Mail_Transport_Abstract
{
public function _sendMail()
{
$file = APPLICATION_PATH . "/../logs/mails.log";
$fp = fopen($file, 'a');
fwrite($fp, $this->header);
fwrite($fp, "\r\n");
fwrite($fp, $this->body);
fwrite($fp, "\r\n\r\n\r\n#############################################################\r\n\r\n\r\n");
fclose($fp);
}
}
Mit diesem Transporter ist es schon fast erledigt. Was wird hier getan? Ganz einfach. Es gibt ein Verzeichnis logs und darin eine Datei mail.log. Diese öffnet der Transporter und gibt den Header und den Body hinein. Mehr Daten benötigt man normalerweise nicht. Falls doch, so kann man den Transporter durchaus erweitern. Zum Beispiel um die Anhänge einer E-Mail usw.
Um nun automatisiert den Transporteu zuzuordnen kann man in der Bootstrap den folgenden Code verwenden.
protected function _initMail()
{
if(APPLICATION_ENV == "development")
{
$mail_transport = new Zorta_Mail_Transport_File();
Zend_Mail::setDefaultTransport($mail_transport);
}
else
{
$mail_conf = Zend_Registry::get('CONFIG')->mail;
$config = array('auth' => 'login',
'username' => $mail_conf->smtp->user,
'password' => $mail_conf->smtp->pass);
$mail_transport = new Zend_Mail_Transport_Smtp($mail_conf->smtp->host, $config);
Zend_Mail::setDefaultTransport($mail_transport);
}
}
Es wird geprüft, ob man im Entwicklungsstadium ist. Die Konstante “APPLICATION_ENV” beinhaltet dies. Wenn man sich in Entwicklung befindet, dann greift er auf den, so eben erstellten, Datei Transporter zu. Ansonsten wird auf den SMTP Transporter zugegriffen. Wie man die Konfiguration des SMTP Transporters initalisiert ist einem selbst überlassen, daher gehe ich hier nicht weiter auf das obige Beispiel ein. Mittels der Methode “setDefaultTransport” wird für das Zend_Mail global der Transporter gesetzt.
Wenn man nun eine E-Mail erstellt und versendet landet sie in angegebener Textdatei und sieht in etwa so aus:
From: Zorta Dateiemail <test@zorta.de> To: Alberto <denis_zunke@gmx.de> Subject: Testnachricht Date: Thu, 28 May 2009 21:51:40 +0200 Content-Type: multipart/alternative; charset="UTF-8"; boundary="=_5e42e31ef1a7fb8fe32142a32f672f11" MIME-Version: 1.0 --=_5e42e31ef1a7fb8fe32142a32f672f11 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Textinhalt --=_5e42e31ef1a7fb8fe32142a32f672f11 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable <b>HTML INHALT</b> --=_5e42e31ef1a7fb8fe32142a32f672f11--
Mehrsprachigkeit mit Zend_Application
Eine Multilinguale Seite mit Zend_Application aufzubauen ist gar nicht so schwer, wie man denkt. Ich gehe hier davon aus, dass man schon Erfahrung mit Zend_Application hat und werde es nicht in jeder Kleinigkeit erklären. Das ganze basiert auf dem Zend Framework 1.8.1
Wenn man am Ende alles richtig eingebaut hat anhang der Beispieldaten, dann sind die Sprachen Deutsch und Englisch verfügbar über die folgenden Adressen:
http://en.domain.tld -> Englisch
http://de.domain.tld -> Deutsch
http://fr.domain.tld -> Deutsch, da dies als Standard gesetzt wird in der Bootstrap
Zuerst einma benötigt man Translate Dateien. Ich habe dafür eine Verzeichnisstruktur angelegt, in der sich ein Ordner für jede Sprache findet. In meinem Fall sind dies “de” und “en”. In diesen liegen dann die verschiedenen PHP Dateien mit Arrays. Hier kurz Beispielhaft für Deutsch und Englisch jeweils die string.php
return array( 'WELCOME' => 'Willkommen zum Test' );
return array( 'WELCOME' => 'Welcome to the Test' );
Über die application.ini setzt man die nötigen Routen, Locale und Translate Einstellungen. Bitte die angegebene Domain im Router auch in die richtige ändern.
resources.router.routes.language.type = "Zend_Controller_Router_Route_Hostname" resources.router.routes.language.route = ":lang.domain.tld" resources.router.routes.language.defaults.controller = "index" resources.router.routes.language.defaults.action = "index" resources.router.routes.language.defaults.lang = "de" resources.router.routes.language.chains.index.type = "Zend_Controller_Router_Route" resources.router.routes.language.chains.index.route = ":controller/:action/*" resources.router.routes.language.chains.index.defaults.module = "default" resources.router.routes.language.chains.index.defaults.controller = "index" resources.router.routes.language.chains.index.defaults.controller = "index" resources.router.routes.language.chains.base.type = "Zend_Controller_Router_Route" resources.router.routes.language.chains.base.route = "/*" resources.router.routes.language.chains.base.defaults.module = "default" resources.router.routes.language.chains.base.defaults.controller = "index" resources.router.routes.language.chains.base.defaults.controller = "index" resources.translate.adapter = "array" resources.translate.data = APPLICATION_PATH "/languages" resources.translate.locale = "en" resources.translate.options.disableNotices = true resources.translate.options.scan = "directory"
Und zuletzt die Bootstrap Klasse
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRequest()
{
// Nötige Abhängigkeiten auflösen
$this->bootstrap('FrontController');
$this->bootstrap('router');
// Den Frontcontroller holen
$front = $this->getResource('FrontController');
// Ein Request Objekt in den Frontcontroller setzen
$request = new Zend_Controller_Request_Http();
$request->setBaseUrl('/');
$front->setRequest($request);
// Den Request in die Registry des Bootstrap Objekt setzen
return $request;
}
protected function _initLang()
{
// Die nötigen Abhhängigkeiten aufbauen
$this->bootstrap('FrontController');
$this->bootstrap('router');
$this->bootstrap('translate');
// Den Frontcontroller mit dem Request Objekt holen und den Router ausführen
$front = Zend_Controller_Front::getInstance();
$request = $front->getRequest();
$router = $front->getRouter();
$router->route($request);
// Das Translate Objekt holen
$lang = $this->getResource('translate');
// Prüfen, ob die ausgewählte Sprache zur verfügung steht, wenn nicht, dann Standard setzen.
if (!$lang->isAvailable($front->getRequest()->getParam('lang','de'))) {
$front->getRequest()->setParam('lang','de');
}
// Die ausgewählte Sprache in das Translate setzen
$lang->setLocale($front->getRequest()->getParam('lang'));
return;
}
}
Zum Test kann man sich einen String ausgeben lassen in der View
<?=$this->translate('WELCOME')?>
Das ganze ist natürlich noch um einige Features erweiterbar, aber mit diesen Schritten hat man eine gute Basis um weiter zu kommen. Interessant sind für den Einbau wohl noch Zend_Cache, entsprechende andere Router oder das speichern der Standardsprache in der Konfiguration für schnelle Änderungen.
