Hallo, ich heiße Matthias Zeis - schön, dass Sie den Weg zu meiner Übersetzung von ZFSTDE gefunden haben!
Falls Sie mehr über Magento 2 oder meine Arbeit als Technical Lead bei LimeSoda erfahren möchten, kommen Sie doch auf meiner Website vorbei.
Inhaltsverzeichnis
Bis vor kurzem war die
Erstellung und Verwaltung des Bootstrap einer Zend-Framework-Anwendung
allein der Phantasie der Entwickler überlassen. Dadurch entstanden
verschiedenste Ansätze vom Einsatz eines monolithischen prozeduralen
Skripts bis hin zu Klassen verschiedenster Struktur oder Komplexität -
ganz zu schweigen davon, dass es zweifellos sehr schwer wurde, ein
Kommandozeilen-Tool zu schreiben. Mit der Einführung von
Zend_Application
aber existiert nun für die
Vorgehensweise beim Bootstraping eine gemeinsame Basis, die in jeder
Anwendung von jedem Entwickler verwendet werden kann. Dadurch wird die
Standardisierung dieses in jeder Anwendung erforderlichen Teils gefördert.
Hätten sie das ganze bloß Zend_Bootstrap
genannt,
um das auch offensichtlich zu machen...
Zend_Application
wurde so konzipiert, dass es modular, anpassbar und möglichst einfach
konfigurierbar ist. Da wir Zend_Application
über
das ganze Buch hinweg verwenden wenden (und das intensiv!), bringen wir
die Sache ins Laufen, indem wir uns das vorherige "Hallo Welt"-Beispiel
noch einmal ansehen und die Klasse ZFExt_Bootstrap
überarbeiten. Eines werde ich aber beibelassen, und zwar wird der
Bootstrap weiterhin unter
/library/ZFExt/Bootstrap.php
zu finden sein. Es gibt
keinen Grund, die Datei woanders abzuspeichern, da der Namespace ZFExt die
Basis unserer allgemeinen Anwendungsbibliothek bildet, in der wir alle
eigenen Zend-Framework-spezifischen Klassen aufbewahren, um sie eventuell
in Zukunft in anderen Anwendungen wiederzuverwenden.
Die nötigen Änderungen an unserer früheren Bootstrap-Klasse beginnen ganz einfach:
class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
}
Wir werden einige
Änderungen vornehmen müssen, um das nachzubilden, was wir mit der
ursprünglichen Klasse ZFExt_Bootstrap
erreicht
haben. Falls Sie aber einmal keine Änderungen brauchen, können Sie mit der
Bearbeitung des Bootstrap schon wieder
aufhören.Zend_Application
kann alles ohne
zusätzlichen Code korrekt aufsetzen, indem die Standardwerte der
benötigten Komponenten verwendet werden.
Wir erweitern hier
Zend_Application_Bootstrap_Bootstrap
, das wiederum
Zend_Application_Bootstrap_BootstrapAbstract
erweitert und alles bietet, was man typischerweise für das Aufsetzen eines
Bootstrap braucht - unter anderem das Laden von Ressourcen, das Dispatchen
durch den Front-Controller und einige Kontrollen, um sicherzugehen, dass
der Betriebsmodus (z.B. Produktivsystem oder Entwicklung) funktionsfähig
ist..
Wie zuvor binden wir den
Bootstrap am Beginn in unserer index.php
in
/public
ein und führen ihn dort aus. Etwas mehr
Komplexität müssen wir dem Code aber noch hinzufügen. Dazu zählt das
Setzen von Konstanten, die einige Anwendungspfade enthalten, das Laden
einer Konfiguration, um das Bootstrapping der Einrichtung der Umgebung zu
steuern, und das Starten des Bootstrapping-Vorgangs selbst.
<?php
if (!defined('APPLICATION_PATH')) {
define('APPLICATION_PATH',
realpath(dirname(__FILE__) . '/../application'));
}
if (!defined('APPLICATION_ROOT')) {
define('APPLICATION_ROOT', realpath(dirname(__FILE__) . '/..'));
}
if (!defined('APPLICATION_ENV')) {
if (getenv('APPLICATION_ENV')) {
$env = getenv('APPLICATION_ENV');
} else {
$env = 'production';
}
define('APPLICATION_ENV', $env);
}
set_include_path(
APPLICATION_ROOT . '/library' . PATH_SEPARATOR
. APPLICATION_ROOT . '/vendor' . PATH_SEPARATOR
. get_include_path()
);
require_once 'Zend/Application.php';
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_ROOT . '/config/application.ini'
);
$application->bootstrap()->run();
Wir können hier
erkennen, dass die umgebungsspezifische Konfiguration ständig in
index.php
vorgenommen wird. In
index.php
erstellen wir eine Zahl von Konstanten,
definieren unseren include_path und starten den Bootstrapping-Prozess.
Intern lädt Zend_Application
unsere Klasse
ZFExt_Bootstrap
, die wir in der
Anwendungskonfigurationsdatei /config/application.ini
konfigurieren. Unsere Klasse ist zwar leer, doch sie erweitert eine
abstrakte Klasse, die alle benötigten Methoden enthält.
Ich muss darauf
hinweisen, dass mit diesen Konstanten verantwortlich umgegangen werden
muss. Konstante sind globale Werte, die überall in der Anwendung
zugänglich sind, und wie jede andere globale Variable sollte ihr Gebrauch
minimiert werden und mit Vorsicht erfolgen, da man immer versucht ist, sie
überall zu verwenden. Andere Anwendungen erzeugen diese Konstanten
vielleicht nicht einmal, weswegen jeder Code, der sich auf sie verlässt,
eventuell nicht wiederverwendbar ist. Eine weitaus bessere lösung ist,
außerhalb von index.php
weiterhin
dirname()
und realpath()
zu
verwenden und/oder diese Variablen als Registry-Objekt oder
Front-Controller-Parameter für Controller-Aktionen verfügbar zu
machen.
Etwas anders als im
Referenzhandbuch behandle ich index.php
entsprechend
dem Zend-Framework-Coding-Standard. Es ist empfehlenswert, nicht die
Kurzformen der Befehle zu verwenden, da diese nicht sehr leserlich
sind.
Ihnen wird auffallen,
dass unsere index.php
-Datei nun standardmäßig die
Konfiguration für "production" verwendet, falls der Konstante
APPLICATION_ENV kein Umgebungswert zugewiesen wird. Wir wollen klarerweise
nicht mit einer Produktionskonfiguration entwickeln (außer wir haben Spaß
daran, Anwendungen zu debuggen, die es mysteriöserweise nicht schaffen,
ihre Fehler anzuzeigen). Wir lösen das Problem, indem wir den notwendigen
Umgebungswert zu unserer .htaccess
-Datei
hinzufügen:
SetEnv APPLICATION_ENV development RewriteEngine On RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^.*$ - [NC,L] RewriteRule ^.*$ /index.php [NC,L]
Bei Apache wird das mit diesem Befehl erreicht, für andere Webserver konsultieren Sie bitte die jeweilige Dokumentation.
Einer der Hauptvorteile
bei der Verwendung von Zend_Application
ist, dass
man sich (da eigener Code für das Bootstraping nicht mehr nötig ist) auf
das Setzen von Standardwerten für Komponenten konzentrieren kann und den
Rest über die Konfiguration regelt. Wie der Inhalt unserer
index.php
oben suggeriert, sollten wir eine
Konfigurationsdatei unter /config/application.ini
mit
folgendem Inhalt erstellen:
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1
Die Konfigurationsdatei
ist in vier individuelle Modi aufgeteilt: production, staging, testing und
development. Alle Modi erben alle Werte von production, können allerdings
Ausnahmen hinzufügen. Zum Beispiel deaktivieren die Einstellungen von
production display_errors, während die Modi testing und development
sichergehen, dass display_errors aktiviert ist. Sie können sicher sein,
dass Zend_Application
den richtigen Modus anhand
des Wertes lädt, den wir in index.php
in der
Konstante APPLICATION_ENV gesetzt haben.
Die verwendeten
INI-Werte erscheinen vielleicht etwas seltsam, da sie unsere
PHP-Konstanten enthalten, gefolgt von einem Leerzeichen und einem in
doppelten Anführungszeichen umfassten relativen Pfad, der vom absoluten
Pfad ausgeht, der in der Konstante definiert wurde.
Zend_Application
kümmert sich intern um das
Zusammensetzen dieser zwei Teile - behalten Sie einfach in Erinnerung,
dass Sie auf diese Weise Pfade für die Komponenten festlegen können.
Tatsächlich können Sie sogar Arrays über INI-Konfigurationsdateien
definieren, wie wir in späteren Kapiteln sehen werden.
Zend_Application
unterteilt alle Konfigurationen in einige Kategorien, die in den
Konfigurationsdateien als Präfixe aufscheinen. "phpSettings" bezieht sich
auf alles, was in PHP mittels ini_set()
eingestellt
werden kann. "includePaths" enthält Pfade, die Sie (neben jenen aus der
index.php
) dem include_path hinzufügen wollen. Zum
Beispiel könnte ich Folgendes angeben:
includePaths.foolib = APPLICATION_ROOT "/foolib/lib"
Ich setze den
Include-Pfad stattdessen direkt innerhalb der
index.php
für das Standard-Verzeichnis
/library
sowie /vendor
, aber Sie
haben vielleicht noch weitere Pfade, die hinzugefügt werden müssen.
"bootstrap" teilt Zend_Application
mit, wo unsere
Bootstrap-Klasse zu finden ist und wie ihr Klassenname lautet (erinnern
Sie sich daran: wir brauchen unsere Klasse
ZFExt_Bootstrap
weiterhin, um zu modifizieren, wie
das Bootstrapping einige Komponenten einrichtet).
Schließlich haben wir
noch "resource", das sich auf
Zend_Application
-Ressourcen bezieht.
Eine
Zend_Application
-Ressource ist im Prinzip eine
beliebige Klasse, die Zend_Application
bekannt ist
und die Zend_Application
während des Bootstrappings
für die weitere Verwendung konfiguriert. Oben setzen wir Optionen für
einen FrontController (der erste Buchstabe des Optionsnamens wird klein
geschrieben), um die Zend_Controller_Front
-Instanz
zu konfigurieren, die Zend_Application
für unsere
Anwendung verwenden wird. Vorerst teilen wir
Zend_Controller_Front
nur mit, wo unser
Standardverzeichnis zu finden ist, in welchem die Controller-Klassen
liegen, und ob Ausnahmen geworfen werden sollen, wenn wir uns im
"development"- oder "testing"-Modus befinden. Falls die Konvention der
Konfiguration verwirrend ist: "controllerDirectory" entspricht
Zend_Controller_Front::setControllerDirectory()
.
Wir folgen der gleichen Konvention wie bei der Verwendung eines Arrays von
Konfigurationswerten, um die Schlüssel von Arrays auf Setter-Methoden
umzulegen, die jeweils dem Schlüssel entsprechen.
In unserer
ursprünglichen Klasse ZFExt_Bootstrap
habe ich
darauf hingewiesen, dass die Standardkonfiguration einiger Komponenten für
uns möglicherweise nicht passend gesetzt ist. Zum Beispiel verwendet
Zend_View
als Standard-Zeichen-Encoding ISO-8859-1,
was auch großartig ist, solange man Multibyte-Zeichen oder solche mit
Akzenten vermeidet. Sie können die standardmäßige
Zend_View
-Instanz mit
Zend_Application
aber ganz ähnlich modifizieren,
wie wir es vorhin schon getan haben!
<?php
class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initView()
{
$view = new Zend_View();
$view->setEncoding('UTF-8');
$view->doctype('XHTML1_STRICT');
$view->headMeta()->appendHttpEquiv(
'Content-Type', 'text/html;charset=utf-8'
);
$viewRenderer =
Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
return $view;
}
}
Wie wir sehen, kann
unsere überarbeitete Bootstrap-Klasse eine Instanz von
Zend_View
mittels des Aufrufs von
_initView()
erzeugen. Man spricht dabei von einer
Ressource-Methode, da sie eine Ressource für die Verwendung vorbereitet
oder modifiziert. Hier setzen wir eine Zend_View-Instanz für die
Verwendung durch den ViewRenderer-Action-Helfer auf (wir werden
Action-Helfer später kennenlernen), wobei sie für die Anwendung als
Standard-Zend_View
-Instanz agiert, um Templates zu
rendern. Alle Ressource-Methoden, die aufgerufen werden sollen, müssen dem
Format "_initResourceName()
" entsprechen, falls
es sich um eine als "protected" deklarierte Methode handelt. Sie können
genauso öffentliche Methoden in Form von
"bootstrapResourceName()
" definieren. Ob Sie
Methoden mit "protected" oder "public" verwenden, bleibt Ihnen
überlassen.
Innerhalb dieser
Ressource-Methoden können wir noch etwas mehr tun, indem wir das
Ressourcen-System von Zend_Application
verwenden.
Prinzipiell definiert Zend_Application
eine Menge
von Klassen, die Ressource-Plugins genannt werden und die sie alternativ
zu den einfachen Ressource-Methoden verwendet, um Ressourcen aufzusetzen
und vorzubereiten.
Was ist denn nun eine Ressource? Ressource ist eigentlich ein verwirrender Begriff, da er nicht die ganze Geschichte erzählt. Man kommt der Wirklichkeit sehr nahe, wenn man sich das Ganze als Konfiguration eines einzigartigen ("unique") Objekts vorstellt. Unsere Anwendung braucht gewöhnlicherweise beim Bootstrapping von jedem Objekt immer nur ein einzelnes Exemplar. Sie benötigt eine Instanz von Zend_View, eine Instanz von Zend_Controller_Front, eine Instanz des Routers etc. Eine Ressource ist also einzigartig - die einzige ihrer Art. Die Aktionen, die nötig sind, um diese einzigartigen Ressourcen zu erzeugen, zu konfigurieren und bei Zend_Application (und der Bootstrap-Klasse) zu registrieren, können in zwei verschiedenen, alternativen Formen zusammengefasst werden: als Ressource-Methoden und als Ressource-Plugins. _initView() erzeugt also eine Ressource mit Namen "View". Aber Moment, es gibt doch auch ein Ressource-Plugin (Zend_Application_Resource_View), das ebenfalls eine Ressource namens "View" erzeugen kann. Können wir zwei View-Ressourcen haben? Die Antwort lautet nein - wir können nur eine einzige haben. Wenn wir eine Ressource-Methode definieren, setzen wir Ressource-Plugins außer Kraft, die auf die gleiche Ressource angewendet werden könnten.
Als Ressource-Plugin
kann jede Klasse fungieren, die
Zend_Application_Resource_ResourceAbstract
(oder
Zend_Application_Resource_Resource
) erweitert,
welches die eigentliche Intialisierung, Konfiguration und Übergabe solcher
Klassen als Objekte an den Front-Controller übernimmt. Eine
Ressource-Methode ist wie ein Ressource-Plugin, aber sie wird in der
Bootstrap-Klasse definiert und nicht in eine separate Klasse ausgelagert.
Um neue Objekte im bzw. für den Bootstraping-Prozess zu erzeugen, können
Sie zu diesem Zweck also entweder eigene Ressource-Plugin-Klassen oder als
kürzere Alternative Ressource-Methoden hinzufügen.
Natürlich kann sich
Zend_Application
selbstständig um einige für Ihre
Anwendung nötige Standardklassen kümmern, ohne dass Sie sich damit
beschäftigen müssen. Zu den Ressource-Plugins, die bereits mit
Zend_Application
ausgeliefert werden,
zählen:
Zend_Application_Resource_Db |
Zend_Application_Resource_FrontController |
Zend_Application_Resource_Router |
Zend_Application_Resource_Modules |
Zend_Application_Resource_Navigation |
Zend_Application_Resource_Session |
Zend_Application_Resource_View |
Zend_Application_Resource_Layout |
Zend_Application_Resource_Locale |
Zend_Application_Resource_Translate |
In unserer Methode
_initView()
haben wir ein Ersatzobjekt vom Typ
Zend_View
erzeugt, da
Zend_Application_Resource_View
nur eine
Standardinstanz mit der Zeichenkodierung ISO-8859-1 und anderen
Standardoptionen erzeugen würde. Wir geben in
_initView()
die neue
Zend_View
-Instanz zurück, die von
Zend_Application
nun als Ersatz akzeptiert wird.
Zend_Application wird somit nicht versuchen, unseren Änderungen zu
überschreiben, indem es die Ressource
Zend_Application_Resource_View
aufruft, welche eine
Standard-Zend_View
-Instanz erzeugt, deren Probleme
wir gerade erst beseitigt haben.
Wenn wir eine neue
Ressource erstellen, um eine andere zu ersetzen, müssen wir nicht die
originale Ressource heranziehen, die vom standardmäßigen Ressource-Plugin
in Zend_Application
erzeugt wird - außer wir
benötigen sie. Lassen Sie uns nun eine weiter Ressource-Methode
_initFrontController()
hinzufügen, die nur einige
Änderungen an der bestehenden Ressource vornimmt, die von
Zend_Application_Resource_Frontcontroller
vorbereitet wurde.
<?php
class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initView()
{
$view = new Zend_View();
$view->setEncoding('UTF-8');
$view->doctype('XHTML1_STRICT');
$view->headMeta()->appendHttpEquiv(
'Content-Type', 'text/html;charset=utf-8'
);
$viewRenderer =
Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
return $view;
}
protected function _initFrontController()
{
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
$response = new Zend_Controller_Response_Http;
$response->setHeader('Content-Type',
'text/html; charset=UTF-8', true);
$front->setResponse($response);
}
}
In unserer originalen
Bootstrap-Klasse wollten wir sichergehen, dass alle Ausgaben standardmäßig
einen Content-Type-Header mit dem Wert "text/html; charset=UTF-8"
verwenden. Hier tun wir das gleiche, indem wir mittels der Methode
getResource()
eine Instanz von
Zend_Controller_Front
beziehen, die von
Zend_Application_Resource_Frontcontroller
erzeugt
und konfiguriert wurde. Wir sorgen nur dafür, dass der FrontController
unser eigenes Response-Objekt verwendet, anstatt dass er ein
Standard-Respone-Objekt erzeugt, wenn er eines braucht. Bevor wir die
FrontController-Ressource, also ein Objekt des Typs
Zend_Controller_Front
beziehen, müssen wir erst
dafür sorgen, dass Zend_Application alles Nötige tut, um es erst einmal zu
erstellen.
Das machen wir, indem
wir der Methode bootstrap()
den Ressource-Namen
(also den letzten Teil des korrespondierenden
Ressourcen-Plugin-Klassennamens) übergeben. Dadurch wird
Zend_Application
dazu gezwungen, den Bootstrap für
eben diese Ressource auszuführen. Die Methode
bootstrap()
ist ziemlich flexibel und akzeptiert
auch ein Array mit Ressource-Namen, falls man sofort mehrere Ressourcen
benötigt, bevor der Bootstrap allgemein ausgeführt wurde.
Sie können noch einen Schritt weitergehen, indem Sie einfach die Standard-Ressource-Plugins ersetzen. Ich werde das jetzt nicht behandeln, da wir später in diesem Buch eigene Ressource-Plugins für andere Objekte in unserer Anwendung hinzufügen werden.
Vergleichen Sie nun
einmal die originale Klasse ZFExt_Bootstrap
mit der
überarbeiteten Version. Sie besteht aus weniger Code, wird durch die
Konfiguration gesteuert, man kann leicht damit arbeiten und sie ist durch
Ressource-Plugins erweiterbar. Die neue Version ist eine klare
Verbesserung. Natürlich stellt das Verstehen der Funktionsweise dieser
Komponente eine weitere Hürde auf dem Weg zum Zend-Framework-Guru dar,
aber diesen Preis muss man immer bezahlen, wenn man abstraktere Klassen
verwendet, die dafür besser erweiterbar und wiederverwendbar als ihre
monolithischen Alternativen sind.
Wenn Sie jetzt noch
einmal versuchen, auf http://helloworld.tld
zuzugreifen, wird
Ihnen etwas Merkwürdiges auffallen. Es wird eine relativ nichtssagende
Ausnahme geworfen, die uns mitteilt: "Circular resource dependency
detected". Das passiert, weil wir eine Ressource-Methode
_initFrontController() definiert haben, die mit
Zend_Application_Resource_Frontcontroller
in
Konflikt tritt, wenn wir bootstrap('FrontController') aufrufen. Wir haben
die Regel gebrochen, nach der Ressourcen einzigartig sind. Durch die
Vorgehensweise, die Ressource-Methode aufzurufen und zugleich (durch den
Aufruf von bootstrap()) die Verwendung des Ressource-Plugins zu erzwingen,
hat Zend_Application das als Versuch interpretiert, zwei ähnliche Objekte
zu erzeugen, obwohl wir nur eines benötigen. Bis wir uns an den Gedanken
gewöhnen, dass der Bootstrap nur mit einzigartigen Ressourcen arbeitet,
wird der Fehler häufiger auftreten.
Daher muss ich die
Bedeutung des Themas Ressource-Methoden vs. Ressource-Plugins so
ausdrücklich betonen (und damit ich dieses kleine Schmuckstück erklären
kann, wenn wir mit Zend_Application
anfangen). Sie
können das eine oder das andere verwenden, aber niemals beide mit
demselben Ressourcen-Namen zu derselben Zeit. Unsere Methode
_initView()
vorhin hat funktioniert, da keine
Referenz zu Zend_Application_Resource_View
verwendet wurde - unsere Ressource-Methode hat sie ersetzt. Wenn wir das
von Zend_Application_Resource_View
erzeugte Objekt
ändern wollen, indem wir die Methoden bootstrap()
und getResource()
aufrufen, um das Objekt zu
generieren und zu holen, dann müssen wir die Ressource-Methode umbenennen,
da wir auf der vom Plugin initialisierten Ressource aufbauen und eine
modifizierte Version davon erzeugen (tatsächilch handelt es sich um eine
andere Ressource, auch wenn der Unterschied nur in der Konfiguration
liegt).
Das veranschaulicht einen wichtigen Punkt: auch wenn Zend_Application von Ressourcen spricht, kann ein einzelnes Objekt durch zwei oder mehr benannte Ressourcen repräsentiert werden. Das wirkt etwas dumm, da schlussendlich nur das eine Objekt verwendet wird, egal wieviele Ressource-Namen darauf verweisen. Die Lösung dafür ist aber wirklich einfach - um Objekte nach ihrer initialen Einrichtung durch ein Plugin oder eine Methode zu modifizieren (der Weg über die Methode ist nicht zu empfehlen: es ist knifflig, dieReihenfolge der Methoden zu umgehen), müssen wir einen anderen Ressourcen-Namen verwenden. Es muss keinen Sinn machen, es ist einfach so.
Unser Fehler war, dass
wir in _initFrontController()
geglaubt haben, wir
könnten einen existierenden Front-Controller modifizieren, indem wir ihn
von Zend_Application_Resource_FrontController
beziehen. Wir haben nicht realisiert, dass wir das in einer
Ressourcen-Methode mit demselben Ressourcen-Namen getan haben. In dem
Moment, in dem eine Ressourcen-Einheit versucht, eine andere
Ressourcen-Einheit mit ähnlichem Namen zu verwenden, herrscht Verwirrung,
da wir nicht zwei Objekte haben können, die dieselbe Ressource
repräsentieren. Daher wirft Zend_Application
eine
Ausnahme und beschwert sich über das Chaos von Abhängigkeiten, dass wir zu
erzeugen versucht haben. Es handelt sich dabei um ein bewusst
eingerichtetes Sicherheitsnetz.
Um diesen Konflikt zu lösen, sollten wir allen Ressourcen-Methoden, die nicht ein Ressourcen-Plugin ersetzen sollen, einen einzigartigen Namen geben. In diesem Fall habe ich beschlossen, den existierenden Ressourcen-Namen "FrontController" um den Präfix "Modified" zu ergänzen, um so anzudeuten, dass unsere Ressource-Methode nicht ein Ressource-Plugin überschreibt, sondern wir z.B. das Ergebnis dieses Ressource-Plugins verwenden, wenn wir die Methode ablaufen lassen, anstatt dass wir selbst etwas erzeugen. Wir geben das Objekt auch nicht zurück, da das Ressource-Plugin das originale Objekt sowieso für die weitere Verwendung registrieren wird.
<?php
class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initView()
{
$view = new Zend_View();
$view->setEncoding('UTF-8');
$view->doctype('XHTML1_STRICT');
$view->headMeta()->appendHttpEquiv(
'Content-Type', 'text/html;charset=utf-8'
);
$viewRenderer =
Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
return $view;
}
protected function _initModifiedFrontController()
{
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
$response = new Zend_Controller_Response_Http;
$response->setHeader('Content-Type',
'text/html; charset=UTF-8', true);
$front->setResponse($response);
}
}
Nun können wir http://helloworld.tld
aufrufen,
ohne auf Fehlermeldungen oder Ausnahmen zu treffen.
Wir haben schon
festgestellt, dass wir in unserer Anwendungskonfigurationsdatei
application.ini die Konfiguration von Ressourcen vornehmen können, indem
wir den Präfix "resources" verwenden. Diese Werte werden automatisch von
den Zend_Application
-Ressource-Plugins verwendet,
um den Objekten, die sie erzeugen, Werte zu übergeben. Für unsere
FrontController-Ressource können wir also Einträge wie diesen
hinzufügen:
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
Der Name der Ressource
wird hier in CamelCase-Schreibweise angegeben und der erste Buchstabe
kleingeschrieben. Aus FrontController wird also frontController. Es wäre
jetzt noch nicht dringend notwendig, aber wir können unsere
Ersatz-Resource-Methode für Zend_View
etwas
aufräumen, indem wir sicherstellen, dass Konfigurationsoptionen aus
application.ini an die neue Instanz übergeben werden. Nicht zuletzt können
wir so die Einstellung für die Zeichenkodierung zurück in die
Konfigurationsdatei verlegen.
<?php
class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
public static function autoload($class) {
include str_replace('_', '/', $class) . '.php';
return $class;
}
protected function _initView()
{
$options = $this->getOptions();
if (isset($options['resources']['view'])) {
$view = new Zend_View($options['resources']['view']);
} else {
$view = new Zend_View;
}
if (isset($options['resources']['view']['doctype'])) {
$view->doctype($options['resources']['view']['doctype']);
}
if (isset($options['resources']['view']['contentType'])) {
$view->headMeta()->appendHttpEquiv('Content-Type',
$options['resources']['view']['contentType']);
}
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
'ViewRenderer'
);
$viewRenderer->setView($view);
return $view;
}
protected function _initModifiedFrontController()
{
$options = $this->getOptions();
if (!isset($options['resources']['modifiedFrontController']['contentType'])) {
return;
}
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
$response = new Zend_Controller_Response_Http;
$response->setHeader('Content-Type',
$options['resources']['modifiedFrontController']['contentType'], true);
$front->setResponse($response);
}
}
Nun können wir die
Einstellung für die Zeichenkodierung und andere Konfigurationsoptionen,
die wir benötigen, in unsere application.ini
verlegen.
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.view.doctype = "XHTML1_STRICT" resources.view.contentType = "text/html;charset=utf-8" resources.modifiedFrontController.contentType = "text/html;charset=utf-8" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1
In unserer
vorangegangenen Bootstrap-Klasse haben wir eine eigene Autoload-Methode
erstellt, welche keine Kontrolle der Datei durchführt und einfach
versucht, eine include()
-Operation unter der Annahme
durchzuführen, dass die Klassennamen entsprechend der PEAR-Konvention auf
ihre Dateinamen umgelegt werden können. Dadurch sollte der Einsatz der
Methode Zend_Loader::loadClass()
vermieden
werden, die in Zend-Framework-Anwendungen üblicherweise Verwendung findet
und viele großteils unnötige Fehlerüberprüfungen durchführt.
Zend_Application
ist so konzipiert, dass es das
Autoloading mittels einer neuen Komponente,
Zend_Loader_Autoloader
, implementiert.
Unglücklicherweise verwendet Zend_Loader_Autoloader
(das die Autoloading-Fähigkeiten für Klassen erheblich erweitert) eben
jene Methode Zend_Loader::loadClass()
, die wir
vermeiden möchten.
Wir können unsere
leichtgewichtigere Methode zwar immer noch implementieren, jedoch nicht
mehr innerhalb der Bootstrap-Klasse. Stattdessen müssen wir die
Standard-Methode von Zend_Loader_Autoloader
für das
Laden von Dateien umstellen, bevor Zend_Application
initialisiert wird. Das kann nur in index.php
erreicht werden.
<?php
if (!defined('APPLICATION_PATH')) {
define('APPLICATION_PATH',
realpath(dirname(__FILE__) . '/../application'));
}
if (!defined('APPLICATION_ROOT')) {
define('APPLICATION_ROOT', realpath(dirname(__FILE__) . '/..'));
}
if (!defined('APPLICATION_ENV')) {
if (getenv('APPLICATION_ENV')) {
$env = getenv('APPLICATION_ENV');
} else {
$env = 'production';
}
define('APPLICATION_ENV', $env);
}
set_include_path(
APPLICATION_ROOT . '/library' . PATH_SEPARATOR
. APPLICATION_ROOT . '/vendor' . PATH_SEPARATOR
. get_include_path()
);
require_once 'Zend/Loader/Autoloader.php';
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->setDefaultAutoloader(create_function('$class',
"include str_replace('_', '/', \$class) . '.php';"
));
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_ROOT . '/config/application.ini'
);
$application->bootstrap()->run();
Da wir den Bootstrap vor
der Initialisierung von Zend_Application
nicht
referenzieren können, übergeben wir
setDefaultAutoloader()
stattdessen eine anonyme
Funktion, die wir direkt an diesem Ort erstellen.
In PHP tragen alle Klassen von Bibliotheken, die der PEAR-Konvention folgen, einen gemeinsamen Präfix, der allgemein als "namespace" bezeichnet wird (das ist nicht das echte Namespacing, das mit PHP 5.3 eingeführt wurde). Unser Autoloader ist so konfiguriert, dass er Klassennamen erkennt, die mit den Namespaces "Zend_" und "ZendX_" beginnen. Um andere Klassen mit Namespace zu laden, müssen wir dem Autoloader erst mitteilen, dass sie existieren. Bevor wir das tun, können wir hinterfragen, warum das überhaupt notwendig ist.
Das Problem mit der originalen Autoloading-Methode war klar. Sie hat alles geladen. Auf den ersten Blick erscheint das großartig: die Funktionsweise ist simpel, es ist einfach zu verstehen und man benötigt keine Konfiguration. Warum also ist das keine gute Sache? Wenn alles geladen wird, entsteht das Problem, dass man keine Kontrolle ausüben kann. Manchmal wollen Sie sicherstellen, dass nur bestimmte Bibliotheken geladen werden können. Ein zusätzlicher Vorteil ist, dass die Einschränkung zur schnelleren Suche der Datei führt.
Die neue Namespacing-Funktionalität ist nicht perfekt, aber das
liegt daran, dass einige Bibliotheken keinen gemeinsamen
Top-Level-Namespace-Präfix haben. PEAR-Komponenten beginnen zum Beispiel
nicht einheitlich mit PEAR_
. Daher können Sie auf
HTTP_Client
, Crypt_RSA
, etc. treffen, die keinen
gemeinsamen Präfix teilen. Ein ähnliches Problem existiert mit
"Root"-Klassen. Zum Beispiel verwendet HTMLPurifier das Namespace-Präfix
HTMLPurifer_
für alle Klassen außer für eine Root-Klase in
HTMLPurifier.php
. Unter diesen Umständen müssen Sie
eventuell zum ursprünglichen Verhalten zurückkehren, in dem Namespaces
nicht berücksichtigt werden und die Verwendung nicht eingeschränkt wird.
Das bewerkstelligen Sie mit:
Zend_Loader_Autoloader::getInstance()->setFallbackAutoloader(true);
Unter Umständen verwendet eine Bibliothek vielleicht auch einen
eigenen Autoloader, den man als Alternative zu der Standardimplementierung
in Zend_Loader_Autoloader
registrieren kann.
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->pushAutoloader('HTMLPurifier_Bootstrap', 'autoload');
Beachten Sie, dass aufgrund des fehlenden Namespaces für HTMLPurifier somit ein neuer globaler Autoloader etabliert wird, der den Standard-Autoloader ähnlich wie die Fallback-Option global und ohne Restriktionen arbeiten lässt. Sie können den Autoloader auch auf einen Namespace beschränken, damit er nur verwendet wird, wenn ein bestimmter Klassen-Namespace erkannt wird.
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->pushAutoloader('XLib_Autoloader', 'autoload', 'XLib_');
Wie auch immer: gehen wir vorerst davon aus, dass dieser Fallback
und andere Autoloader nicht notwendig sind. Das Ergebnis des neuen
Autoloaders ist, dass wir alle Klassen-Namespaces registrieren müssen, die
wir verwenden wollen. Wir könnten das zwar im Code von index.php
erledigen, doch wenn wir die Wartung in
application.ini vornehmen, können wir leichter den
Überblick behalten und nach Bedarf Namespaces hinzufügen. Hier haben wir
ein Beispiel, in dem wir eine Bibliothek (eigentlich unsere zukünftige
Anwendungsbibliothek) verwenden wollen, deren Klassennamen alle einen
Präfix "ZFExt_" besitzen. Wie Sie wahrscheinlich erkennen, können Sie
weitere Namespaces hinzufügen, indem Sie eckige Klammern [] verwenden.
Dadurch werden sie zu einem Array hinzugefügt, sobald die INI-Datei von
Zend_Config
geparst wird (welches das gesamte
Parsing und Laden der Konfiguration innerhalb des Frameworks
bewerkstelligt).
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.view.doctype = "XHTML1_STRICT" resources.view.contentType = "text/html;charset=utf-8" resources.modifiedFrontController.contentType = "text/html;charset=utf-8" autoloaderNamespaces[] = "ZFExt_" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1
Das war eine schnelle
Einführung (nun ja, Sie könnten ja noch zusätzlich Schnelllesen lernen) in
Zend_Application
. Als jemand, der bisher sein
eigenes Ding durchgezogen hat, passe ich den Code gerne an diesen Standard
an. So ist gesichert, dass andere Entwickler meinem Bootstrapping-Prozess
folgen können, ohne einen komplett neuen Ansatz (von den wahrscheinlich
hunderten da draußen existierenden) zu lernen. Das ist der Vorteil dabei,
dass endlich ein Standardansatz existiert, wie man das Bootstrapping von
Anwendungen abwickelt.
Wir werden im Verlauf
des Buches mehr von Zend_Application
zu sehen
bekommen, da wir in unseren Anwendungen das Verhalten vieler Objekte und
Anwendungsteile modifizieren werden müssen und der Bootstrap der geeignete
Ort ist, um die Initialisierung dafür vorzunehmen. Als abschließende
Bemerkung: Zend_Tool
, das in diesem Buch noch nicht
abgedeckt wird, verwendet Zend_Application
, um
Anwendungen zu verwalten.