Services

1. Einleitung

Die Kapselung von Funktionalität in eigenständige Komponenten ist eine in der objektorientierten Welt sehr verbreitete Methodik. Der Vorteil dieser Aufteilung wird im Mehrschicht-Architektur-Pattern beschrieben. Dabei geht der Entwickler beim Erstellen des Designs der Software davon aus, dass unterschiedliche Schichten der Software jeweils typischen Aufgaben übernehmen.

Da Schichten oder Services üblicherweise durch ein oder mehrere Klassen repräsentiert werden, leitet sich daraus unmittelbar die Aufgabe ab, Objekte der jeweiligen Schichten zu erstellen. Gleichermaßen ist der Entwickler beim Entwurf von wiederverwendbaren Schichten - Services - gefordert, diese mit einer klaren und einfach zu konfigurierenden API auszustatten. Die Datenschicht einer Anwendung benötigt beispielsweise eine Komponente zur Anbindung an eine externe Datenquelle, eine Business-Komponente benötigt Kenntnis über das Umfeld, in dem die Applikation eingesetzt ist. Darüber hinaus bestehen Abhängigkeiten der verschiedenen Schichten untereinander.

Um die Kapselung von Funktionalität und damit die Austauschbarkeit innerhalb einer Anwendung sicherzustellen, soll die aufrufende Schicht die Interna der aufgerufenen Schicht möglichst nicht kennen. Um dies zu erreichen gilt es insbesondere die Art der Erzeugung und die Konfiguration der aufgerufenen Schicht zu verstecken.

Die folgenden Kapitel beschreiben basierend auf den im APF verfügbaren Methoden zur Erzeugung von Objekten Mechanismen und Tools um die Kapselung von Funktionalität einfach zu realisieren und dabei gleichzeitig klare Strukturen innerhalb einer Anwendung zu realisieren.

2. Erzeugung von Objekten

Das Adventure PHP Framework nutzt verschiedene Prinzipien zur Erzeugung von Objekte innerhalb des Front-Controller und Page-Controller. Insbesondere Front-Controller-Actions und Tags zur Kapselung von UI-Funktionaliäten müssen mit Informationen ihres Umfelds versorgt werden. Dies reicht von der Bekanntgabe des Eltern-Elements bis zur Weitergabe von Kontext und Sprache um in einer Komponente auf eine von beiden Werten abhängige Konfiguration zugreifen zu können. Das APF nutzt dazu das Factory- und Dependency Injection-Prinzip.

Bei der Erzeugung von Objekten geben die verschiedenen Komponenten des APF jeweils
  • Kontext (Context) und
  • Sprache (Language)
mit. Diese essentiellen Informationen werden an jedes Objekt weitergegeben um beispielsweise eine Konfiguration zu laden oder Sprach-abhängige Funktionen umzusetzen (z.B. Standard TagLibs).

Da das Framework seinen Schwerpunkt auf die Gestaltung der UI und die damit verbundenen Funktionaltäten legt, ist der Entwickler selbst dafür verantwortlich, Objekte bzw. Services ausserhalb dieses Bereichs zu erzeugen. Hierzu stehen geeignete Hilfsmittel wie der ServiceManager bzw. der DIServiceManager zur Verfügung.

Bitte erzeugen Sie alle Objekte, die auf Konfigurationen zugreifen oder weitere Objekte erzeugen, die Zugriff auf den aktuellen Kontext der Anwendung oder die Sprache benötigen über den ServiceManager oder DIServiceManager. Andernfalls kommt es zu Fehlern beim Laden von Konfigurationen oder dem Erzeugen von Kontext- oder Sprach-abhängigen Objekten.

Zugunsten einer generische Möglichkeit, beliebige Klasse als Singleton, SessionSingleton oder ApplicationSingleton zu erzeugen, verzichtet das APF auf die Unterstützung von Konstruktor-Argumenten. Bei der Initialisierung wird daher ausschließlich auf method injection bzw. setter injection gesetzt. Die erforderlichen Daten oder Abhängigkeiten werden dem Objekt also nach der Erzeugung zur Verfügung gestellt.

Möglichkeiten zur Ausführung von Initialisierungsmethoden finden Sie in Kapitel Kapitel 4.3.1 (Stichwort: setupmethod).

3. ServiceManager

Der ServiceManager ist eine Erweiterung der Singleton-, SessionSingleton- und ApplicationSingleton-Implementierungen, die unter Erzeugung von Objekten beschrieben werden. Der ServiceManager ist im Vergleich zu den genannten Implementierungen stärker im Framework verwoben und die Klasse APFObject bietet die Convenience-Methode APFObject::getServiceObject() an, die einfach zur Erstellung von Objekten genutzt werden kann.

Dabei werden die in der aktuellen Instanz vorhandenen Informationen wie Kontext und Sprache bereits automatisch an den ServiceManager übergeben und das erzeugte Objekt ist damit bereits vollständig initialisiert.

3.1. Service-Definition

Um Objekte mit dem ServiceManager erzeugen zu können, müssen diese dem Interface APFService genügen. Dieses definiert die Möglichkeit, Kontext und Sprache zu injizieren und ermöglicht so, beliebige Klassen als Service zu kennzeichnen.

Das Interface gestaltet sich wie folgt:

PHP-Code
interface APFService { const SERVICE_TYPE_NORMAL = 'NORMAL'; const SERVICE_TYPE_CACHED = 'CACHED'; const SERVICE_TYPE_SINGLETON = 'SINGLETON'; const SERVICE_TYPE_SESSION_SINGLETON = 'SESSIONSINGLETON'; const SERVICE_TYPE_APPLICATION_SINGLETON = 'APPLICATIONSINGLETON'; public function setContext($context); public function getContext(); public function setLanguage($lang); public function getLanguage(); public function setServiceType($serviceType); public function getServiceType(); }

Die oben aufgeführten Konstanten definieren die möglichen Erzeugungsmuster von Services, die nachfolgend beschriebenen Methoden ermöglichen die Injektion von Kontext und Sprache.

Implementiert das zu erzeugende Objekt das Interface APFService nicht, führt dies zu einer Exception.

3.2. Erzeugen von Services

Innerhalb Ihrer Anwendung können Sie den ServiceManager direkt (siehe 3.2.1. Native Nutzung) oder über die Methode APFObject::getServiceObject() (siehe 3.2.2. Nutzung des Wrappers) nutzen. Die folgenden Kapitel zeigen die Nutzung sowie Vor- und Nachteile auf.

3.2.1. Native Nutzung

Der ServiceManager kann über die statische Methode getServiceObject() aus beliebigen Code-Stellen angesprochen werden. Der Aufruf gestaltet sich wie folgt:

PHP-Code
use APF\core\service\ServiceManager; $instance = &ServiceManager::getServiceObject('VENDOR\..\Class', $context, $language);

Wie dem Aufruf zu entnehmen ist, müssen zum Zeitpunkt des Aufrufs der aktuelle Kontext und die aktuelle Sprache bekannt sein. Dies ist prinzipiell in jedem vom APF erzeugten Objekt der Fall, das das Framework für die Weitergabe der Informationen sorgt.

Erzeugen Sie Objekte selbst oder befinden Sie sich ausserhalb des Gültigkeitsbereichs von Objekten - z.B. in der index.php -, so können Sie z.B. auf die Instanz des Front-Controller zurückgreifen. Dieser wird in der Bootstrap-Datei üblicherweise mit dem aktuellen Kontext und der aktuellen Sprache ausgestattet. Der Aufruf gestaltet sich in diesem Fall wie folgt:

PHP-Code
use APF\core\frontcontroller\Frontcontroller; use APF\core\singleton\Singleton; $fC = &Singleton::getInstance('APF\core\frontcontroller\Frontcontroller'); $context = $fC->getContext(); $language = $fC->getLanguage(); use APF\core\service\ServiceManager; $instance = &ServiceManager::getServiceObject('VENDOR\..\Class', $context, $language);
Bitte beachten Sie, dass innerhalb des DOM-Baums des Page-Controller Kontext und Sprache für Kind-Strukturen neu definiert werden können. Aus diesem Grund und um Fehler bei der Weitergabe von Kontext und Sprache zu vermeiden, wird empfohlen Objekte über die entsprechende Wrapper-Methode (siehe Kapitel 3.2.2) zu erstellen.
3.2.2. Nutzung des Wrappers

Die in der Klasse APFObject definierte Methode getServiceObject() kapselt den Aufruf des ServiceManager und kümmert sich selbständig um die Weitergabe von Kontext und Sprache. Sie können daher ein Objekt einfach wie folgt erstellen:

PHP-Code
use APF\core\pagecontroller\APFObject; class GodObject extends APFObject { public function doSomething(){ $service = &$this->getServiceObject( $serviceClass, [$type = APFService::SERVICE_TYPE_SINGLETON], [$instanceId = null] ); $service->doSomethingElse(); } }

Die Methode APFObject::getServiceObject() besitzt folgende Parameter:

  • $serviceClass: Gibt den voll-qualifizierten Namen der Service-Implementierung an (Beispiel: VENDOR\..\MyServiceName).
  • $type: Der Parameter $type definiert die Art der Erzeugung und damit die Gültigkeit der Instanz. Mögliche Werte sind:
    • APFService::SERVICE_TYPE_NORMAL
    • APFService::SERVICE_TYPE_SINGLETON
    • APFService::SERVICE_TYPE_SESSION_SINGLETON
    • APFService::SERVICE_TYPE_APPLICATION_SINGLETON
    Dieser Parameter ist optional, als Standard wird APFService::SERVICE_TYPE_SINGLETON verwendet. Details zu den gelisteteten Gültigkeitebereichen kann unter Erzeugung von Objekten nachgelesen werden.
  • $instanceId: Die unter Erzeugung von Objekten beschriebenen Implementierungen verfügen über die Möglichkeit, ein Objekt mit einer eindeutigen Kennung zu versehen. So lassen sich von einer Implementierung mehrere Instanzen erstellen um beispielsweise basierend auf einer Verdindungs-Klasse mehrere Datenbank-Verbindungen zu nutzen. Mit diesem Parameter lässt sich dieses Feature auch mit dem ServiceManager nutzen.
Um die Implementierung von Services einfacher zu gestalten, können Sie von APFObject ableiten statt das Interface APFService zu implementieren. APFObject erledigt bereits die relevanten Dinge für Sie.

4. DIServiceManager

Der DIServiceManager ist ein Dependency Injection- und Inversion of Control-Container zur Erzeugung und Konfiguration von Services (siehe Inversion of Control Containers and the Dependency Injection pattern von Martin Fowler). Die Definition von Services basiert auf Konfigurationsdateien (Prinzip: wire by configuration), die sowohl die Service-Implementierung als auch Abhängigkeiten und Konfigurationsparameter definieren.

Zur Erzeugung der Services nutzt der DIServiceManager die Funktionalitäten des ServiceManager und bietet damit für alle Anwendungsfälle passende Gültigkeitsbereiche der erstellten Objekte an (Details siehe Erzeugung von Objekten).

Im Vergleich zum ServiceManager bietet der Dependency-Injection-Container eine weitere Abstraktionsschicht bei der Konfiguration und dem Bezug von Services. Sie referenzieren bei Bezug eines Service nicht mehr direkt die Implementierung, sondern seine Konfiguration. Dies erleichtert zum einen den Austausch von Implementierungen und erleichtert zum anderen auch bei Bedarf die Nutzung von MOCK-Implementierungen.

Zur Nutzung des Containers steht sowohl die statische Methode DIServiceManager::getServiceObject() als auch die Convenience-Methode APFObject::getDIServiceObject() zur Verfügung.

Bei der Nutzung von APFObject::getDIServiceObject() werden die in der aktuellen Instanz vorhandenen Informationen wie Kontext und Sprache bereits automatisch an den DIServiceManager übergeben und das erzeugte Objekt ist damit bereits vollständig initialisiert.

Services können dabei sowohl durch wiederum andere Services als auch durch statische Konfigurationsparameter für den Einsatz vorbereitet werden.

4.1. Service-Definition

Um Objekte mit dem DIServiceManager erzeugen zu können, müssen diese dem Interface APFDIService genügen. Dieses definiert basierend auf dem Interface APFService Strukturen um Objekte mit dem Dependency-Injection-Container zu erstellen und zu verwalten.

Das Interface gestaltet sich wie folgt:

PHP-Code
interface APFDIService extends APFService { public function markAsInitialized(); public function markAsPending(); public function isInitialized(); }

Die oben aufgeführten Methoden erlauben dem Container die Abfrage des aktuellen Zustands des Objektes - beispielsweise den Initialisierungszustand.

Da die Erzeugung der Objekte mit Hilfe des ServiceManager erfolgt, ist es erforderlich, dass die zu erzeugenden Objekt mindestens das Interface APFService implementieren müssen. Andenfalls wird der Aufruf mit einer Exception quittiert.

4.2. Erzeugen von Services

Innerhalb Ihrer Anwendung können Sie den DIServiceManager direkt (siehe 4.2.1. Native Nutzung) oder über die Methode APFObject::getDIServiceObject() (siehe 4.2.2. Nutzung des Wrappers) nutzen. Die folgenden Kapitel zeigen die Nutzung sowie Vor- und Nachteile auf.

4.2.1. Native Nutzung

Der DIServiceManager kann über die statische Methode getServiceObject() aus beliebigen Code-Stellen angesprochen werden. Der Aufruf gestaltet sich wie folgt:

PHP-Code
use APF\core\service\DIServiceManager; $instance = &DIServiceManager::getServiceObject('VENDOR\..', 'Service-Name', $context, $language);

Die beiden ersten Parameter definieren den Namespace und den Namen der Service-Definition. Diese referenzieren effektiv eine Konfigurations-Sektion, die den Service beschreibt (Details siehe Kapitel 4.3. Zum Zeitpunkt des Aufrufs müssen der aktuelle Kontext und die aktuelle Sprache bekannt sein. Dies ist prinzipiell in jedem vom APF erzeugten Objekt der Fall, das das Framework für die Weitergabe der Informationen sorgt.

Erzeugen Sie Objekte selbst oder befinden Sie sich ausserhalb des Gültigkeitsbereichs von Objekten - z.B. in der index.php, so können Sie z.B. auf die Instanz des Front-Controller zurückgreifen. Dieser wird in der Bootstrap-Datei üblicherweise mit dem aktuellen Kontext und der aktuellen Sprache ausgestattet. Der Aufruf gestaltet sich in diesem Fall wie folgt:

PHP-Code
use APF\core\frontcontroller\Frontcontroller; use APF\core\singleton\Singleton; $fC = &Singleton::getInstance('APF\core\frontcontroller\Frontcontroller'); $context = $fC->getContext(); $language = $fC->getLanguage(); use APF\core\service\DIServiceManager; $instance = &DIServiceManager::getServiceObject('VENDOR\..', 'Service-Name', $context, $language);
Bitte beachten Sie, dass innerhalb des DOM-Baums des Page-Controller Kontext und Sprache für Kind-Strukturen neu definiert werden können. Aus diesem Grund und um Fehler bei der Weitergabe von Kontext und Sprache zu vermeiden, wird empfohlen Objekte über die entsprechende Wrapper-Methode (siehe Kapitel 4.2.2) zu erstellen.
4.2.2. Nutzung des Wrappers

Die in der Klasse APFObject definierte Methode getDIServiceObject() kapselt den Aufruf des DIServiceManager und kümmert sich selbständig um die Weitergabe von Kontext und Sprache. Sie können daher ein Objekt einfach wie folgt erstellen:

PHP-Code
use APF\core\pagecontroller\APFObject; class GodObject extends APFObject { public function doSomething(){ $service = &$this->getDIServiceObject( $serviceNamespace $serviceName ); $service->doSomethingElse(); } }

Die Methode APFObject::getDIServiceObject() besitzt folgende Parameter:

  • $serviceNamespace: Gibt den Namespace der Service-Definition an (Beispiel: VENDOR\namespace\of\my\component).
  • $serviceName: Gibt den Referenz-Namen der Service-Definition innerhalb des zuvor angegebenen Namespace an (Beispiel: open-weather-map-service).
Um die Implementierung von Services einfacher zu gestalten, können Sie von APFObject ableiten statt das Interface APFDIService zu implementieren. APFObject erledigt bereits die relevanten Dinge für Sie.

4.3. Konfiguration

Die Erstellung und Konfiguration von Services mit dem DIServiceManager unterscheidet sich sehr deutlich von der Nutzung des ServiceManager. Jeder Service ist durch eine Konfiguration eindeutig beschrieben, da er nicht nur für sich steht, sondern auch zur Initialisierung eines anderen dienen kann. Dies ermöglicht Ihnen mit Hilfe des DIServiceManager Services mit Abhängigkeiten zu anderen Services zu definieren.

Die Definition eines Service erfolgt in einer Konfigurations-Sektion, die über den Namespace der Konfigurationsdatei und den Namen derselben adressiert wird. Der DIServiceManager nutzt den Konfigurationsmechanismus des APF zum Laden der Konfiguration.

Die Nutzung des ConfigurationManager bietet den Vorteil, Services in Abhängigkeit von Namespace, Kontext und Umgebung definieren zu können. Das bedeutet:

  • Services lassen sich durch den Namespace in logische Bereiche gruppieren. Dies erleichtert zum einen die Unterscheidung innerhalb einer Applikation als auch die Benennung.
  • Pro Einsatzgebiet (Kontext) können Services unterschiedlich definiert oder konfiguriert werden. Sie können damit beispielsweise zwei unterschiedliche Services für Anbieter von Wetter-Vorhersagen innerhalb einer Applikation und basierend auf einer Implementierung realisieren.
  • Mit Hilfe der Umgebung können Services auf unterschiedliche physikalische Plattformen (z.B. Entwicklung, Staging, Produktion) abstimmen und konfigurieren. Die gleiche Implementierung kann dann beispielsweise in einer Entwicklungs-Umgebung im Debug-Modus laufen wohingegen auf einer Produktions-Umgebung nur bestimmte Informationen in Log-Dateien geschrieben werden.

Die folgenden Kapitel beschreiben die Konfiguration von Services näher.

4.3.1. Konfigurations-Schema

Die Definition eines Services zeichnet sich wie im vorangegangenen Kapitel beschrieben durch eine Konfigurations-Sektion in einer Datei mit dem Namen {ENVIRONMENT}_serviceobjects.ini aus. Der Wert für die Umgebung ist jeweils mit der Konfiguration Ihrer Applikation zu ersetzen (Standard: DEFAULT).

Innerhalb dieser Datei steht jede Sektion für eine eigenständige und voneinander unabhängig nutzbare Service-Definition. Der Inhalt einer solchen Sektion genügt folgendem Schema:

APF-Konfiguration
[{service-name}] class = "" servicetype = "" [conf.{CONF_KEY}.method = "" conf.{CONF_KEY}.value = ""] [init.{INIT_KEY}.method = "" init.{INIT_KEY}.namespace = "" init.{INIT_KEY}.name = ""] [setupmethod = ""]

Die einzelnen Bestandteile haben folgende Bedeutung:

  • service-name: Der service-name bildet zusammen mit dem Namespace der Konfigurations-Datei den eindeutigen Bezeichner eines Services. Sie können diesen sowohl bei einem Aufruf von APFObject::getDIServiceObject() oder DIServiceManager::getServiceObject() als auch bei der Initialisierung von Services durch wiederum andere Services nutzen.
  • class: Voll-qualifizierter Klassen-Name der Service-Implementierung (z.B. VENDOR\..\Class). Der Inhalt des Parameters ist effektiv identisch zum ersten Parameter bei der Benutzung von ServiceManager::getServiceObject().
  • servicetype: Diese Direktive definiert die Art der Erzeugung des Service-Implementierung. Die hier einsetzbaren Werte entnehmen Sie bitte Kapitel 3.2.2. Zusätzlich dazu können Sie noch die Methode APFService::SERVICE_TYPE_CACHED bzw. CACHED nutzen. Dieser Wert sorgt dafür, dass die erzeugte Service-Implementierung zwar neu erstellt wird (wie auch bei APFService::SERVICE_TYPE_NORMAL), allerdings die Konfigurations-Datei nicht neu geladen wird.
  • conf.*: Der conf-Bereich dient dazu, einen Service mit einer statischen Konfiguration zu initialisieren.
    Da der der Service-Instanz mitgegebene Wert lediglich primitive Datentypen enthalten kann, eignet sich diese Art der Konfiguration (auch einfache Konfiguration genannt) für Werte wie Benutzer-Namen, Passwörter, URLs etc.
    Jeder Service kann mit einer beliebigen Anzahl von Attributen konfiguriert werden. Dies kann durch mehrfache Verwendung des oben aufgeführten conf-Blocks und geeigneter Wahl des {CONF_KEY}-Platzhalters erreicht werden.
    Bitte beachten Sie, dass der {CONF_KEY}-Platzhalter innerhalb einer Gruppe (für die Attribute method und value) gleich und für unterschiedliche Gruppen (Anordnung von je einem method- und value-Attribut) unterschiedlich gewählt werden muss. Der folgende Code-Block zeigt Ihnen ein Beispiel für die Initialisierung eines Services mit einem Benutzer, einem Passwort und einer URL:
    APF-Konfiguration
    conf.user.method = "setUser" conf.user.value = "John" conf.pass.method = "setPassword" conf.pass.value = "Doe" conf.url.method = "setUr" conf.url.value = "https://example.com/service/v1/soap"
    Für die Initialisierung des Benutzers wurde der Schlüssel user genutzt. Der Wert des Attributs conf.user.method beschreibt diejenige Methode der mit class referenzierten Service-Implementierung, die der DIServiceManager aufruft um den Wert des Attributes conf.user.value zu übergeben. Selbiges gilt für die beiden anderen Sektionen.
  • init.*: Der init-Bereich dient zur dynamischen oder komplexen Konfiguration von Services mit Hilfe anderer Services.
    Mit Hilfe dieser Sektion lassen sich komplexe Datentypen zur Initialisierung des Services nutzen. Dies ermöglicht Ihnen, einer Service-Instanz weitere Instanzen von referenzierten Services zu injizieren (dependency injection) und damit für ihren Anwendungsfall zu konfigurieren (z.B. Übergabe der zu verwendenden Datenbank-Verbindung).
    Jeder Service kann mit einer beliebigen Anzahl von weiteren Services konfiguriert werden. Dies kann durch mehrfache Verwendung des oben aufgeführten init-Blocks und geeigneter Wahl des {INIT_KEY}-Platzhalters erreicht werden.
    Bitte beachten Sie, dass der {INIT_KEY}-Platzhalter innerhalb einer Gruppe (für die Attribute method, namespace und name) gleich und für unterschiedliche Gruppen (Anordnung von je einem method-, value und name-Attribut) unterschiedlich gewählt werden muss. Der folgende Code-Block zeigt Ihnen ein Beispiel für die Initialisierung eines Services mit einer Datenbank-Verbindung und einem Wetter-Vorhersage-Service:
    APF-Konfiguration
    init.weather.method = "setWeatherService" init.weather.namespace = "VENDOR\namespace\of\service\definition" init.weather.name = "open-weather-map-service" init.db.method = "setDatabaseConnection" init.db.namespace = "VENDOR\namespace\of\database\connection\definition" init.db.name = "calendar-database-connection"
    Für die Initialisierung des Wetter-Vorhersage-Dienstes wurde der Schlüssel weather genutzt. Der Wert des Attributs init.weather.method beschreibt diejenige Methode der mit class referenzierten Service-Implementierung, die der DIServiceManager aufruft um den mit den Attributen init.weather.namespace und init.weather.name referenzierten Service zu übergeben. Selbiges gilt für die zweite Sektion.
  • setupmethod: Der optionale Parameter setupmethod erlaubt den Aufruf einer Methode am Ende der Konfiguration eines Objektes durch die in den conf.*- und init.*-Bereichen definierten Abhängigkeiten.
    Die setupmethod kann dazu genutzt werden die Objektinstanz mit den gewonnenen Informationen zu weiter zu initialisieren. Dies ist immer dann hilfreich, wenn zur Initialisierung des Services alle Abhängigkeiten aufgelöst sein müssen (z.B. Name der Datenbank-Verbindung).
    Um mehrfache Initialisierungsvorgänge zu vermeiden kann der Service bei der Implementierung der Methode isInitialized() des APFDIService-Interface den Wert true zurück geben, sobald er seine Initialisierung abgeschlossen hat. Diese Information nutzt der DIServiceManager um den Service bei bereits erfolgter Initialisierung den Aufruf der setupmethod auszusetzen.
4.3.2. Service-Definition

Ein Service definiert sich über eine in Kapitel 4.3.1 beschriebene Sektion und gegebenenfalls Referenzen auf weitere Sektionen. Eine Sektion ist innerhalb einer Konfigurations-Datei definiert, die über ihren Namespace und den Namen {ENVIRONMENT}_serviceobjects.ini adressiert wird.

Der Name der Konfigurations-Datei ist fest definiert und kann nicht geändert werden. Dies erspart die Angabe der Konfigurations-Datei und erleichtert damit die Nutzung des DIServiceManager. Innerhalb einer Datei können mehrere Service definiert werden, wobei der Name der Sektionen - und damit die Name der Services - eindeutig sein muss.

Fragen Sie per

PHP-Code
use APF\core\service\DIServiceManager; $service = DIServiceManager::getServiceObject( 'VENDOR\namespace\of\service\definition', 'open-weather-map-service', $context, $language );

einen Service an, so erwartet der DIServiceManager eine Service-Definition bzw. Konfigurations-Sektion mit dem Bezeichner open-weather-map-service in der Datei

Code
/path/to/VENDOR/config/namespace/of/service/definition/{CONTEXT}/{ENVIRONMENT}_serviceobjects.ini

Die Parameter /path/to/VENDOR - sprich der Basis-Pfad zu den Applikations- und Konfigurationsdateien des Herstellers VENDOR - sowie {CONTEXT} und {ENVIRONMENT} sind abhängig von der Konfiguration Ihrer Applikation. Details zum Aufbau und der Verwendung von Konfigurations-Dateien entnehmen Sie bitte dem Kapitel Konfiguration bzw. Laden von Klassen.

Durch die Adressierung von Service über den Namespace der Konfigurations-Datei und den darin definierten Service-Namen hat der Entwickler die Freiheit, Service-Definitionen frei zu gestalten. Dies umfasst die Möglichkeit, abhängige Services innerhalb der gleichen Konfigurations-Datei zu definieren als auch in einer Datei innerhalb eines anderen Namespace.

Dies kann beispielsweise dazu genutzt werden, Basis-Services oder mehrfach eingesetzte Abhängigkeiten (z.B. Datenbank-Verbindungen) auszulagern und in einem Basis-Namespace zu definiert, wohingegen die konkreten Ausprägungen in einem tieferen Abschnitt des Namespace abgelegt werden.

4.4. Anwendung

Die folgenden Kapitel beschreiben unterschiedliche Anwendungsfälle und die zugehörige Service-Implementierung und Konfiguration des Services.

4.4.1. Erzeugen eines einfachen Services

Der erste Anwendungsfall befasst sich mit der Erzeugung eines einfachen Services, der ein Liefer-Datum basierend auf dem Datum und der Uhzeit einer Bestellung berechnen soll. Um dem Kunden ein mögliches Liefer-Datum anzeigen zu können, soll der Service in einem (Document-)Controller erzeugt und genutzt werden.

4.4.1.1. Implementierung

Kümmern wir uns zunächst um die Struktur und den Aufbau des Service. Dieser soll folgendem Interface genügen:

PHP-Code
namespace ACME\shop\order; interface PreliminaryShipmentDateCalculator { /** * @param \DateTime $orderDate * @return \DateTime */ public function getShipmentDate(\DateTime $orderDate); }

Die Implementierung soll nun auf Basis des Eingabe-Datums eine Berechnung durchführen:

PHP-Code
namespace ACME\shop\order; class SimpleShipmentDateCalculator extends APFObject implements PreliminaryShipmentDateCalculator { private $shipmentPeriodInDays = 10; public function getShipmentDate(DateTime $orderDate) { return $orderDate->add(\DateInterval::createFromDateString('+' . $this->shipmentPeriodInDays . 'd')); } }
Bitte beachten Sie, dass die Klasse SimpleShipmentDateCalculator von APFObject erbt. Damit erfüllt sie bereits die Anforderungen des Interfaces APFDIService. Dies erleichtert Ihnen die Implementierung von eigenen Services. Möchten Sie die Abhängigkeit zu APFObject auflösen, lesen Sie zunächst die Hinweise in Kapitel 4.4.5..

Um den Sevice in einem Document-Controller zu erzeugen, ist es zunächt notwendig, eine Service-Konfiguration zu erstellen.

Bei der Wahl des Namespaces der Service-Definition wird empfohlen, den Namespace der Implementierung zu verwenden um die Zugehörigkeit zu verdeutlichen.
4.4.1.2. Nutzung

Dem Hinweis des letzten Kapitels folgend, soll die Service-Konfiguration unter dem Namespace ACME\shop\order abgelegt werden und shipment-date-calculator heißen. Der Service lässt sich damit im Controller wie folgt nutzen:

PHP-Code
namespace ACME\shop\ui\checkout; use ACME\shop\order\SimpleShipmentDateCalculator; use APF\core\pagecontroller\BaseDocumentController; class PreliminaryShipmentDateController extends BaseDocumentController { public function transformContent() { /* @var $service SimpleShipmentDateCalculator */ $service = & $this->getDIServiceObject('ACME\shop\order', 'shipment-date-calculator'); $this->setPlaceHolder( 'shipment-date', $service->getShipmentDate(new \DateTime())->format('Y-m-d') ); } }
4.4.1.3. Konfiguration

Die Konfiguration - bzw. die Konfigurations-Datei - ist von mehreren Parametern abhängig. Für diesen Anwendungsfall gehen wir von folgenden Annahmen aus:

  • Für den Hersteller ACME ist gemäß der Beschreibung unter Laden von Klassen ein StandardClassLoader registriert, der den Basis-Pfad /path/to/ACME definiert.
  • Als Kontext der Applikation wurde dem Front-Controller der Wert customer-one übergeben.
  • Die konfigurierte Umgebung, in der die Applikation eingesetzt wird wurde nicht verändert und lautet damit DEFAULT.

Unter den genannten Annahmen erwartet der DIServiceManager die Konfigurations-Datei

Code
/path/to/ACME/config/shop/order/customer-one/DEFAULT_serviceobjects.ini

mit dem Inhalt

APF-Konfiguration
[shipment-date-calculator] class="ACME\shop\order\SimpleShipmentDateCalculator" servicetype="SINGLETON"
4.4.2. Initialisierung eines einfachen Services

In Kapitel 4.4.1 wurde der SimpleShipmentDateCalculator statisch konfiguriert, sprich die Anzahl der durchschnittlich notwendigen Liefertage innerhalb des Codes der Klasse definiert. In diesem Kapitel erweitern wir die Definition des Interfaces und die Implementierung des Services so, dass eine konfigurierbare Anzahl von Tagen mitgegeben werden kann.

4.4.2.1. Implementierung

Das Interface PreliminaryShipmentDateCalculator erhält eine zusätzliche Methode setShipmentPeriod() um den Service konfigurieren zu können:

PHP-Code
namespace ACME\shop\order; interface PreliminaryShipmentDateCalculator { /** * @param int $shipmentPeriodInDays */ public function setShipmentPeriodInDays($shipmentPeriodInDays); /** * @param \DateTime $orderDate * @return \DateTime */ public function getShipmentDate(\DateTime $orderDate); }

Die Implementierung des Service erweitert sich damit wie folgt:

PHP-Code
namespace ACME\shop\order; class SimpleShipmentDateCalculator implements PreliminaryShipmentDateCalculator { /** * @var int */ private $shipmentPeriodInDays = 10; public function setShipmentPeriodInDays($shipmentPeriodInDays) { $this->shipmentPeriodInDays = $shipmentPeriodInDays; } public function getShipmentDate(\DateTime $orderDate) { return $orderDate->add(\DateInterval::createFromDateString('+' . $this->shipmentPeriodInDays . 'd')); } }
4.4.2.2. Konfiguration

Basierend auf den Annahmen in Kapitel 4.4.1.3 kann die Konfigurations-Sektion shipment-date-calculator wie folgt erweitert werden:

APF-Konfiguration
[shipment-date-calculator] class="ACME\shop\order\SimpleShipmentDateCalculator" servicetype="SINGLETON" conf.shipment-days.method="setShipmentPeriodInDays" conf.shipment-days.value="7"

Bei der Nutzung des Service wird nun gegenüber Kapitel 4.4.1.2 ein Datum von 7 Tagen als Lieferungs-Datum ausgegeben.

Da mit dieser Service-Definition eine saubere Trennung zwischen Code und Konfiguration eingeführt wurde, kann die Lieferzeit jederzeit und für unterschiedliche Applikationen und Umgebungen ohne Änderung von Quellcode angepasst werden.
4.4.3. Nutzung der Initialisierungs-Methode

Die Konstruktion bzw. die Konfiguration von Objekten und Services ist immer dann eine Herausforderung, wenn interne Zustände oder Ressourcen (z.B. Datenbank-Verbinungen) in Abhängigkeit zu mehreren Konfigurations-Parametern stehen. In diesem Fall ist es erforderlich, zunächst alle Abhängigkeiten aufzulösen, bzw. die benötigten Ressourcen zu injizieren und anschließend den "Betriebs-Zustand" der Instanz herzustellen.

Eine denkbare Lösung ist, die Parameter in einer definierten, gleichbleibenden Reihenfolge in der Konfiguration zu definieren und im Setter des letzten Parameters den gewünschten Objektzustand herzustellen. Dies birgt allerdings die Gefahr, dass bei fehlerhafter Konfiguration oder bei Session-übergreifender Nutzung der Status des Objekts nicht garantiert werden kann.

Um Fehler bei der Initialisierung zu vermeiden, bietet der DIServiceManager die Ausführung einer Initialisierungs-Methode an. Diese kann innerhalb der Konfiguration eines Service mit dem Attribut setupmethod definiert werden.

Der DIServiceManager ruft die Methode am Ende der Konfiguration eines Objektes durch die in den conf.*- und init.*-Bereichen definierten Abhängigkeiten aus. Damit ist sichergestellt, dass alle notwendigen Informationen zur Initialisierung vorliegen.
4.4.3.1. Implementierung

Im folgenden Beispiel soll der SimpleShipmentDateCalculator aus Kapitel 4.4.2 um die Möglichkeit erweitert werden ein potentielles Liefer-Datum aus einem Basis-Wert und einem Uhrzeit-abhängigen Faktor zu berechnen. Der Faktor soll aus zwei Konfigurations-Parametern berechnet werden und gilt innerhalb der durch die beiden Parameter definierten Uhrzeiten.

Die setupmethod dient nun dazu, die Berechnung des Faktors auszuführen, damit dieser bei der Berechnung des Liefer-Datums in der getShipmentDate()-Methode zur Verfügung steht. Hierzu erweitern wir zunächst das Interface PreliminaryShipmentDateCalculator, um die Start- und End-Uhrzeit zu definieren:

PHP-Code
namespace ACME\shop\order; interface PreliminaryShipmentDateCalculator { /** * @param int $shipmentPeriodInDays */ public function setShipmentPeriodInDays($shipmentPeriodInDays); /** * @param string $start */ public function setStartTime($start); /** * @param string $end */ public function setEndTime($end); /** * @param \DateTime $orderDate * @return \DateTime */ public function getShipmentDate(\DateTime $orderDate); }

Die Implementierung des Service erweitert sich damit wie folgt:

PHP-Code
namespace ACME\shop\order; class SimpleShipmentDateCalculator implements PreliminaryShipmentDateCalculator { /** * @var int */ private $shipmentPeriodInDays = 10; /** * @var \DateTime */ private $start; /** * @var \DateTime */ private $end; /** * @var int */ private $dynamicFactor; public function setShipmentPeriodInDays($shipmentPeriodInDays) { $this->shipmentPeriodInDays = $shipmentPeriodInDays; } public function setStartTime($start) { $this->start = new \DateTime($start); } public function setEndTime($end) { $this->end = new \DateTime($end); } ... }

Die Berechnung des $dynamicFactor soll nun in der Methode initialize() erfolgen. Die Implementierung der Klasse SimpleShipmentDateCalculator erweitert sich damit nochmals wie folgt:

PHP-Code
class SimpleShipmentDateCalculator implements PreliminaryShipmentDateCalculator { /** * @var int */ private $shipmentPeriodInDays = 10; /** * @var \DateTime */ private $start; /** * @var \DateTime */ private $end; /** * @var int */ private $dynamicFactor; public function setShipmentPeriodInDays($shipmentPeriodInDays) { $this->shipmentPeriodInDays = $shipmentPeriodInDays; } public function setStartTime($start) { $this->start = new \DateTime($start); } public function setEndTime($end) { $this->end = new \DateTime($end); } public function initialize() { $difference = $this->end->diff($this->start)->h; $this->dynamicFactor = $difference > 1 ? $difference : 1; } public function getShipmentDate(\DateTime $orderDate) { $period = $this->shipmentPeriodInDays; if ($orderDate->diff($this->start)->h >= 0 && $this->end->diff($orderDate)->h <= 0) { $period = $this->shipmentPeriodInDays; } return $orderDate->add( \DateInterval::createFromDateString( '+' . ($period) . 'd' ) ); } }
Die Methode initialize() wurde bewusst nicht zum Interface hinzugefügt, da diese aus Sicht des Anwendungs-Codes eine Besonderheit der Implementierung darstellt. Sofern Sie in jedem Fall beabsichtigen, die Instanz über den DIServiceManager zu erzeugen ist es sinnvoll das Interface um die Methode initialize() zu ergänzen.

Die Instanz der Klasse SimpleShipmentDateCalculator wird in der Methode initialize() nicht als initialisiert markiert. Dies führt dazu, dass der DIServiceManager die Methode initialize() bei einer erneuten Anfrage des Objekts nochmals ausführt.

Ist der Zustand des Objektes nach der Initialisierung über einen längeren Zeitraum (z.B. die gesamte Lebensdauer des Objektes) gültig, kann dieses als initialisiert markiert werden. Der DIServiceManager ruft die setupmethod danach nicht mehr auf. Dies wird insbesonders für aufwendige Initialisierungsvorgänge empfohlen.

Die Implementierung der Methode initialize() ändert sich dafür wie folgt:

PHP-Code
class SimpleShipmentDateCalculator implements PreliminaryShipmentDateCalculator { ... public function initialize() { $difference = $this->end->diff($this->start)->h; $this->dynamicFactor = $difference > 1 ? $difference : 1; $this->markAsInitialized(); } ... }

Nun sind alle Vorarbeiten erledigt um den Service initialisieren zu können. Das folgende Kapitel zeigt Ihnen, wie Sie den Service zur Nutzung konfigurieren.

4.4.3.2. Konfiguration

Die Implementierung des SimpleShipmentDateCalculator wurde im vorangegangenen Kapitel so angepasst, dass dieser mit den relevanten Konfigurations-Parametern ausgestattet und initialisiert werden kann.

Um die erweiterte Service-Implementierung nutzen zu können, ist folgende Konfiguration erforderlich:

APF-Konfiguration
[shipment-date-calculator] class="ACME\shop\order\SimpleShipmentDateCalculator" servicetype="SINGLETON" setupmethod="initialize" conf.shipment-days.method="setShipmentPeriodInDays" conf.shipment-days.value="7" conf.from.method="setStartTime" conf.from.value="18:00:00" conf.to.method="setEndTime" conf.to.value="23:59:59"

Bei der Nutzung des shipment-date-calculator wird nun zwischen 18Uhr und 0Uhr ein dynamischer Faktor zur Auslieferungsdauer hinzugefügt.

4.4.4. Initialisierung eines komplexen Service

In diesem Kapitel widmen wir uns der Initialisierung eines Services mit einem anderen. Dies wird immer dann der Fall sein, wenn einfache Datentypen in conf.*-Sektionen für die Repräsentation der Konfigurations-Daten nicht mehr ausreichend sind, oder ein Service einen anderen vollwertigen Service (beispielsweise eine Datenbank-Verbindung) für seine Arbeit benötigt.

Zur Initialierung eines Services lassen sich die init.*-Sektionen nutzen. Diese ermöglichen mit Hilfe einer Methode einen definierten Service an einen anderen Service zu übergeben. In diesem Kapitel entwerfen wir den DatabaseConfiguredShipmentDateCalculator, der die Lieferzeiten an Hand einer Datenbank-Tabelle evaluiert.

4.4.4.1. Implementierung

Zur Implementierung des DatabaseConfiguredShipmentDateCalculator nutzen wir die Interface-Definition PreliminaryShipmentDateCalculator aus Kapitel 4.4.1.1, die Implementierung der Methode getShipmentDate() vorschreibt.

Für den Aufbau der Datenbank-Verbindung nutzen wir den ConnectionManager bzw. die konkreten Treiber-Implementierungen - in unserem Fall den MySQLiHandler. Diese gestaltet sich wie folgt:

PHP-Code
namespace ACME\shop\ui\checkout; use APF\core\database\MySQLiHandler; class DatabaseConfiguredShipmentDateCalculator implements PreliminaryShipmentDateCalculator { /** * @var MySQLiHandler */ private $databaseConnection; /** * @param MySQLiHandler $databaseConnection */ public function setDatabaseConnection(MySQLiHandler $databaseConnection) { $this->databaseConnection = $databaseConnection; } public function getShipmentDate(\DateTime $orderDate) { $select = 'SELECT `shipment_days` FROM ... WHERE ... ' . $orderDate->format('Y-m-d H:i:s') . ';'; $result = $this->databaseConnection->executeTextStatement($select); $data = $this->databaseConnection->fetchData($result); return $data['shipment_days']; } }

Ähnlich wie in Kapitel 4.4.3 definiert der DatabaseConfiguredShipmentDateCalculator die Methode setDatabaseConnection() mit der die Konfiguration des Services vorgenommen werden kann. In diesem Fall nimmt die Methode keinen skalaren Wert, sondern eine Instanz der Klasse MySQLiHandler entgegen.

Innerhalb der Methode getShipmentDate() wird die Datenbank-Verbindung dann zur Evaluierung des Lieferzeitraums genutzt und erwartet, dass die Datenbank-Verbindung zu diesem Zeitpunkt aufgebaut ist.

4.4.4.2. Konfiguration

Um den Service DatabaseConfiguredShipmentDateCalculator nutzen können ist eine entsprechende Konfiguration notwendig. Diese definiert Service selbst und die abhängigen Strukturen (Datenbank-Verbindung über den MySQLiHandler) und dessen Konfiguration.

Die Definition des Service lautet wie folgt:

APF-Konfiguration
[shipment-date-calculator] class="ACME\shop\order\DatabaseConfiguredShipmentDateCalculator" servicetype="SINGLETON"

Zur Konfiguration der Datenbank-Verbindung - in diesem Fall ebenfalls eine Service-Definition, die später zur Initialisierung eingesetzt wird - kann die folgende Sektion verwendet werden:

APF-Konfiguration
[shipment-database] class = "APF\core\database\MySQLiHandler" servicetype = "SINGLETON" setupmethod = "setup" conf.host.method = "setHost" conf.host.value = "localhost" conf.name.method = "setDatabaseName" conf.name.value = "..." conf.user.method = "setUser" conf.user.value = "root" conf.pass.method = "setPass" conf.pass.value = "..." conf.charset.method = "setCharset" conf.charset.value = "utf8" conf.collation.method = "setCollation" conf.collation.value = "utf8_general_ci"

Die Sektion shipment-database definiert zunächst die Service-Implementierung, die in diesem Fall eine mit dem APF mitgelieferte Komponente ist - die Klasse MySQLiHandler. Da diese das DatabaseConnection-Interface implementiert ist es möglich, eine Instanz mit Hilfe des DIServiceManager zu erzeugen.

Nach der Konfiguration mit unterschiedlichen conf.*-Sektionen wird die Instanz mit der in Kapitel 4.4.3. beschriebenen setupmethod initialisiert und ist damit für die Verwendung bereit.

Um die Datenbank-Verbindung im DatabaseConfiguredShipmentDateCalculator nutzen zu können, muss diese noch in den Service injiziert werden. Dies lässt sich mit der folgenden Erweiterung der Konfigurations-Sektion shipment-date-calculator erreichen:

APF-Konfiguration
[shipment-date-calculator] class="ACME\shop\order\DatabaseConfiguredShipmentDateCalculator" servicetype="SINGLETON" init.db.method = "setConnection" init.db.namespace = "ACME\shop\order" init.db.name = "shipment-database"
Das in diesem Kapitel beschriebene Beispiel geht davon aus, dass die beiden Services in einer Konfigurations-Datei definiert sind. Trifft dies für Ihre Applikation nicht zu, muss der Namespace im Attribut init.db.namespace entsprechend ausgetauscht werden.

Bei der Nutzung des Service shipment-date-calculator steht Ihnen in der Methode getShipmentDate() nun der Zugriff auf eine initialisierte Datenbank-Verbindung zur Verfügung.

Ein weiteres Beispiel findest sich auf der Wiki-Seite Erzeugen des GORM mit dem DIServiceManager.
4.4.5. Implementierung von APFDIService

Die Klasse APFObject implementiert das APFDIService-Interface und stellt damit alle notwendigen Voraussetzungen zur Verfügung um Objekte dieses Typs mit dem DIServiceManager zu erzeugen. Ist dies nicht gewünscht - etwa um die von der Klasse APFObject ausgehenden Abhängigkeiten zu lösen -, so können Sie jederzeit die Abhängigkeit durch eine eigene Implementierung des APFDIService-Interface unterbrechen.

Für Ihre eigene Implementierung des APFDIService-Interface gilt es folgende Punkte zu beachten:

  • Die Verwaltung der Attribute Kontext, Sprache und Service-Typ muss von der Implementierung selbst übernommen werden. Konkret muss die Implementierung des Interfaces dafür sorgen, dass die Werte innerhalb einer Instanz gespeichert und bei Aufruf der entsprechenden Interface-Methoden (z.B. getContext()) zurückgegeben werden.
  • Die Initialisierung und Markierung des Services erfolgt mit Hilfe der Methoden markAsInitialized() und markAsPending(), die Abfrage nimmt der DIServiceManager mit Hilfe der Methode isInitialized() vor. Die Speicherung des Initialisierungszustandes und die entsprechende Rückgabe obliegt Ihrer Implementierung.
Bei der Implementierung von eigenen Services, die mit einer speziellen setupmethod initialisiert werden lässt sich die Markierung des Services als bereits initialisiert auch in der isInitialized()-Methode vornehmen. Nutzen Sie das APFObject als Basis, können Sie die Initialisierung lediglich in der setupmethod über die Methode markAsInitialized() abbilden.
4.4.6. Nutzung von Services

Die Nutzung von Services, die mit dem DIServiceManager erzeugt wurden kann auf unterschiedliche Arten erfolgen. Einige davon wurden bereits in den letzten Kapitel besprochen. Die folgende Tabelle fasst alle Möglichkeiten zusammen und gibt Ihnen weitere Hinweise und Tipps:

Beschreibung Einsatzgebiet
Die in den Kapiteln 4.4.1 bis 4.4.4 beschriebene Art der Nutzung sieht den direkten Bezug der gewünschten Instanzen vom DIServiceManager vor. Ihnen steht es dabei frei, die Convenience-Methode APFObject::getDIServiceObject() zu nutzen oder direkt auf DIServiceManager::getServiceObject() zuzugreifen. Diese Art der Anwendung wird für (Document-)Controller und die Implementierung von Tags empfohlen.

Kapitel 4.4.4 beschreibt die Initialisierung und Konfiguration von Services mit einfachen Werten und wiederum anderen Services. Der z.B. von einem Controller angefragte Service ist bei Bezug bereits fertig konfiguriert und der Anwender muss keine weiteren Code investieren (inversion of control).

Innerhalb eines Services kann ein über den DI-Container injizierter Service direkt genutzt werden ohne diesen aktiv vom DIServiceManager zu beziehen. Dies erleichtert die Implementierung und entfernt explizite Abhängigkeiten um Transparenz und Testbarkeit zu verbessern.

Diese Möglichkeit steht auch für Document-Controller zur Verfügung. Kapitel 4 der (Document-)Controller-Dokumentation beschreibt die notwendigen Maßnahmen um einen Controller über den DIServiceManager zu erzeugen.

Diese Art der Anwendung wird für (Document-)Controller empfohlen, die komplexe Services bezieht bzw. auf mehr als einen Service zurückgreift. Ferner ist diese Vorgehensweise nützlich um Code innerhalb von Controllern besser testbar zu gestalten in dem explizite Abhängigkeiten entfernt werden.

Analog zur Erzeugung von Document-Controllern ist es ebenfalls möglich, Front-Controller-Actions über den DI-Container zu erzeugen. Dies erleichtert ebenfalls die Implementierung und entfernt explizite Abhängigkeiten im Code um Transparenz und Testbarkeit zu verbessern.

Kapitel 3.3 der Front-Controller-Dokumentation beschreibt die notwendigen Maßnahmen um eine Action über den DIServiceManager zu erzeugen.

Diese Art der Anwendung wird für Front-Controller-Actions empfohlen, die komplexe Services bezieht bzw. auf mehr als einen Service zurückgreift. Ferner ist diese Vorgehensweise nützlich um Code innerhalb von Actions besser testbar zu gestalten.

4.5. Gültigkeitsbereiche

Das Adventure PHP Framework stellt mehrere Möglichkeiten zur Erzeugung von Objekten zur Verfügung. Mit diesen lassen sich Instanzen mit unterschiedlichen Gültigkeitsbereichen erstellen und innerhalb der Applikation verwenden. Erstellen Sie Services mit dem ServiceManager oder DIServiceManager stehen Ihnen diese Möglichkeiten ebenfalls zur Verfügung. Bei der Nutzung der Methode APFObject::getServiceObject() in Kapitel 3.2.2 und der Konfiguration von Services in Kapitel 4.3 lassen sich die Gültigkeitsbereiche jeweils programmatisch bzw. konfigurativ pro Instanz definieren.

Die folgende Tabelle führt die vorhandenen Gültigkeitsbereiche und deren empfohlenen Einsatz auf:

Gültigkeitsbereich Beschreibung Einsatzgebiete
NORMAL Objekt wird bei jeder Anfrage an den ServiceManager bzw. DIServiceManager neu erzeugt und ggf. initiaisiert. Soll ein neu erzeugtes Objekt mit Kontext und Sprache ausgestattet werden, jedoch bei jedem Anwendungsfall unterschiedlich sein, kann dieser Gültigkeitsbereich genutzt werden. Für Services wird i.d.R. jedoch der Gültigkeitsbereich SINGLETON empfohlen.
SINGLETON Objekt wird lediglich bei der ersten Anfrage an den ServiceManager bzw. DIServiceManager neu erzeugt und ggf. initialisiert und ist innerhalb einer HTTP-Anfrage gültig. Objekte mit diesem Gültigkeitsbereich können zum Austausch von Informationen zwischen unterschiedlichen HMVC-Elementen innerhalb einer Anfrage verwendet werden.

Ein weiterer Anwendungsfall ist die mehrfache Verwendung eines Service an unterschiedlichen Stellen innerhalb einer HTTP-Anfrage, dessen Initialisierung aufwendig ist und daher nur einmal durchgeführt werden soll.
SESSION_SINGLETON Objekt wird lediglich bei der ersten Anfrage an den ServiceManager bzw. DIServiceManager neu erzeugt und ggf. initialisiert und ist innerhalb einer HTTP-Session gültig. Objekte mit diesem Gültigkeitsbereich können als View-Model für z.B. mehrseitige Workflows genutzt werden. Darin lassen sich über mehrere HTTP-Anfragen innerhalb eines Besuchs die Daten zwischenspeichern und am Ende konsolidiert verarbeiten.

Ein weiterer Anwendungsfall ist die mehrfache Verwendung eines Service an unterschiedlichen Stellen innerhalb eines Besuchs, dessen Initialisierung aufwendig ist und daher nur einmal durchgeführt werden soll.
APPLICATION_SINGLETON Objekt wird lediglich bei der ersten Anfrage an den ServiceManager bzw. DIServiceManager neu erzeugt und ggf. initialisiert und ist für die Laufzeit des Web-Servers gültig. Objekte mit diesem Gültigkeitsbereich können zum Austausch von Daten einer Anwendung unabhängig von einer Anfrage oder einem Besuch genutzt werden.

Ein weiterer Anwendungsfall ist die mehrfache Verwendung eines Service an unterschiedlichen Stellen innerhalb einer Applikation, dessen Initialisierung aufwendig ist und daher nur einmal durchgeführt werden soll.

Bitte beachten Sie, dass bei Nutzung des Gültigkeitsbereichs SESSION_SINGLETON und APPLICATION_SINGLETON die Inhalte eines Objekts zwischen zwei Anfragen serialisiert werden. Da Resourcen (File-Pointer, Datenbank-Verbindungen) nicht searialisiert werden können müssen diese ggf. in der nächsten Anfrage neu initialisiert werden müssen.

Um in einer Klasse DataMapper, die als Service über den ServiceManager oder DIServiceManager bezogen wird, eine Datenbank-Verbindung zu verwalten können Sie folgenden Code nutzen:

PHP-Code
class DataMapper extends APFObject { /** * @var MySQLiHandler */ private $connection; ... public function __wakeup() { $this->connection = ...; } }

Sobald Sie den Service in einer weiteren Anfrage erneut beziehen, wird die Methode __wakeup() aufgerufen und die Verbindung wieder hergestellt.

Nutzen Sie den DIServiceManager und ist für Ihren Service eine setupmethod konfiguriert, können Sie bei der Serialisierung den Initialisierugstatus des Objekts zurück zu setzen. Dies sorgt ebenfalls dafür, dass das Objekt beim nächsten Bezug über die setupmethod neu initialisiert wird. Hierzu lässt sich folgender Code nutzen:

PHP-Code
class DataMapper extends APFObject { /** * @var MySQLiHandler */ private $connection; ... public function __sleep() { $this->markAsPending(); } public function initialize() { $this->connection = ...; } }

Diese Möglichkeit ist jedoch an den DIServiceManager gebunden und setzt voraus, dass Sie Ihren Service mit einer setupmethod initialisieren.

Kommentare

Möchten Sie den Artikel eine Anmerkung hinzufügen, oder haben Sie ergänzende Hinweise? Dann können Sie diese hier einfügen. Die bereits verfassten Anmerkungen und Kommentare finden Sie in der untenstehenden Liste.
« 1   »
Einträge/Seite: | 5 | 10 | 15 | 20 |
1
Christian 24.09.2009, 22:28:48
Hallo Sebastian,

dies ist seit Version 1.10 nicht mehr der Fall, hier werden die Komponenten ebenfalls vom ServiceManager für dich eingebunden. Die Doku wird dahingehend noch im Rahmen von Release 1.11 überarbeitet.
2
Sebastian 24.09.2009, 10:53:44
Entgegen der Funktionsweise des DIServiceManagers müssen die betreffenden Services bei _getServiceObject bzw. __getAndInitServiceObject ja noch per import() eingebunden werden. Welchen Sinn macht dann noch die Angabe des Namespaces bei der Referenzierung?