Das Konfigurations-Konzept des APF sieht eine Abstraktion des Zugriffs auf Konfigurationen vor. Dies ermöglicht es nicht nur beliebige Quellen (z.B. Dateien und Datenbanken), sondern auch beliebige Datei-Formate (z.B. INI, XML) anzusprechen.
Technisch wird diese Abstraktion durch die Trennung von Speicher-Format und Repräsentation sowie die Einführung von Format-abhängigen Zugriffs-Komponenten erreicht. Dazu existiert eine zentrale Instanz, die zur Verwaltung der Formate vorgesehen ist (ConfigurationManager) und konkrete Implementierungen für die gewünschten Formate (z.B. IniConfigurationProvider). Die einzelnen Provider lassen sich beim ConfigurationManager dann für ein definiertes Format registrieren.
Ein weiteres Merkmal ist die gemeinsame Definition der Repräsentation eines Konfigurations-Formates. Hierzu existiert das Interface Configuration, das von jedem Provider hinsichtlich dem lesenden und schreibenden Zugriffs implementiert werden muss.
Das technische Konzept der Konfigurations-Komponente des APF sieht vor, dass eine Konfiguration von folgenden Parametern abhängen kann:
Die folgende Tabelle beschreibt die einzelnen Teile nochmal im Detail:
Installations-Ordner | Basis-Ordner | Namespace | Context | Environment | Name | Endung/Typ |
/APF | /config | widgets\calendar | siteone | DEV | labels | .xml |
Hieraus ergibt sich folgender Datei-Name bzw. Pfad:
/APF/config/widgets/calendar/siteone/DEV_labels.xml
Bitte beachten Sie, dass die Werte Context und Environment üblicherweise in der Bootstrap-Datei (index.php) konfiguriert werden. Der Context kann dem Page-Controller und Front-Controller mit der Methode setContext() mitgeteilt werden, die Umgebung wird via Registry konfiguriert:
use APF\core\frontcontroller\FrontController;
$fC = Singleton::getInstance(FrontController::class);
$fC->setContext('my\context');
echo $fC->start('...', '...');
// Environment-Definition für beide Anwendungfälle
Registry::register('APF\core', 'Environment', 'TESTBOX');
Alle weiteren Parameter einer Konfigurations-Resource werden beim Laden derselben explizit oder implizit mitgegeben.
Zur Definition der Software-Komponenten bringt das APF folgende Interfaces und Implementierungen mit:
Um das Lesen und Schreiben von Konfigurationen zu vereinfachen, stehen in allen Klassen, die von APFObject erben die Methoden getConfiguration() und saveConfiguration() zur Verfügung. Das Löschen von Konfigurationen kann über die Methode deleteConfiguration() bewerkstellig werden.
<Location "/path/to/APF/installation/config">
Order Allow,Deny
Deny from all
</Location>
Das Release des Adventure PHP Framework liefert bereits vier Provider-Implementierungen mit. Diese sind in den folgenden Kapiteln erläutert.
Der INI-Provider implementiert das Standard-Konfigurations-Format des APF. Bis zur Version 1.13 war dieses als einziges Format verfügbar und folgt dem Datei-Schema der ini-Datei.
Das Laden einer INI-Datei mit dem Inhalt
[showCaptcha]
ActionClass = "APF\modules\captcha\biz\actions\ShowCaptchaImageAction"
(Auszug aus einer Front-Controller-Konfiguration) kann innerhalb eines Document-Controllers wie folgt erledigt werden:
class FooController extends BaseDocumentController {
public function transformContent() {
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.ini');
...
}
}
Die Variable $config enthält nun eine Instanz der Klasse IniConfiguration - dem Format des IniConfigurationProvider -, der in der Standard-Konfiguration des APF mit dem Handling von Dateien mit der Endung .ini betraut ist.
Die Objekt-Struktur, die vom Provider zurückgegeben wird hängt dabei von der Definition der Konfigurations-Datei ab. Dabei gelten folgende "Regeln":
$config->getSection('showCaptcha')
$config->getSection('showCaptcha')->getValue('InputParams')
$config->getSection('showCaptcha')->getSection('FC')->getValue('InputParams')
Der schreibende Zugriff gestaltet sich ebenso intuitiv:
class BarController extends BaseDocumentController {
public function transformContent() {
$config = new IniConfiguration();
$section = new IniConfiguration();
$section->setValue('year_label', 'Jahr');
$section->setValue('month_label', 'Monat');
$section->setValue('day_label', 'Tag');
$config->setSection('global-labels', $section);
$this->saveConfiguration('APF\widgets\calendar', 'labels.ini', $config);
}
}
Neben der gezeigten Vorgehensweise ist es auch jederzeit möglich, gelesene Konfigurationen nach einer Manipulation wieder zu speichern.
Der XML-Provider unterstützt das APF-Konfigurations-Format. Hierzu wurde folgendes Schema definiert (Auszug aus der Konfiguration für den Data-Mapper des Gästebuch-Moduls):
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<section name="GuestbookMapper">
<property name="servicetype">NORMAL</property>
<property name="class">APF\modules\guestbook2009\data\GuestbookMapper</property>
<section name="conf">
<section name="db">
<property name="method">setConnectionName</property>
<property name="value">guestbook2009</property>
</section>
<section name="orm">
<property name="method">setORMInitType</property>
<property name="value">NORMAL</property>
</section>
</section>
</section>
</configuration>
Das Schema zeichnet sich durch folgende Punkte aus:
Der Zugriff auf die Konfiguration gestaltet sich analog zu Kapitel 3.1:
class FooService extends APFObject {
public function doSomething() {
$config = $this->getConfiguration('APF\modules\guestbook2009', 'serviceobjects.xml');
...
}
}
Die Variable $config enthält nun eine Instanz der Klasse XmlConfiguration - dem Format des XmlConfigurationProvider. Die Objekt-Struktur, folgt dabei folgenden "Regeln":
$config->getSection('GuestbookMapper')
$config->getSection('GuestbookMapper')->getValue('servicetype')
$config
->getSection('GuestbookMapper')
->getSection('conf')
->getSection('db')
->getValue('method')
Der schreibende Zugriff ist identisch zum INI-Format (hier am Beispiel der Veränderung einer geladenen Konfiguration):
class FooService extends APFObject {
public function doSomethingElse() {
$config = $this->getConfiguration('APF\modules\guestbook2009', 'serviceobjects.xml');
$config->getSection('GuestbookMapper')->setValue('servicetype', APFService::SERVICE_TYPE_SINGLETON);
$this->saveConfiguration('APF\modules\guestbook2009', 'serviceobjects.xml', $config);
}
}
Der DbConfigurationProvider ist eine einfache Implementierung des APF-Konfigurations-Schema um Konfigurationen in der Datenbank abzulegen. Er unterstützt im Gegensatz zu den Formaten INI und XML nur eine flache Hierarchie (Konfiguration -> Sektion -> Wert), beherrscht jedoch die Zuordnung von Context-, Sprach- und Umgebungs-abhängigen Werten.
Zur Nutzung des Providers muss eine Datenbank-Tabelle der Form
CREATE TABLE IF NOT EXISTS `config_widgets_calendar` (
`context` varchar(50) NOT NULL,
`language` varchar(5) NOT NULL,
`environment` varchar(20) NOT NULL,
`name` varchar(20) NOT NULL,
`section` varchar(20) NOT NULL,
`key` varchar(30) NOT NULL,
`value` varchar(500) NOT NULL,
`creationtimestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modificationtimestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`context`,`language`,`environment`,`name`,`section`,`key`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
angelegt werden. Gemäß dem in Kapitel Konfigurations-Schema aufgezeigten Adressierung von Konfigurationen kann ein in der Datenbank befindlicher wie folgt geladen werden:
class FooController extends BaseDocumentController {
public function transformContent() {
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.db');
...
}
}
Die Variable $config enthält nun eine Instanz der Klasse DbConfiguration - dem Format des DbConfigurationProvider. Die Objekt-Struktur, folgt dabei folgenden "Regeln":
$config->getSection('main')
$config->getSection('main')->getValue('icon')
Der schreibende Zugriff auf den Datenbank-Store gestaltet sich identisch zu den zuvor beschriebenen Formaten:
class BarController extends BaseDocumentController {
public function transformContent() {
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.db');
$config->getSection('main')->setValue('icon', 'calendar_big.png');
$this->saveConfiguration('APF\widgets\calendar', 'labels.db', $config);
}
}
Der MemcachedConfigurationProvider unterstützt selbst keine eigenes Format, sondern ist eine Art "Mediator"-Provider, der einen bestehenden Provider für das Lesen und Schreiben nutzt, die Konfiguration jedoch für den schnellen Zugriff in einem Memcached-Store Request-übergreifend hält.
Sofern der Provider auf die Datei-Endung bzw. den Typ "mem" konfiguriert ist (siehe Konfiguration der Provider), so lässt sich eine Konfiguration wie folgt laden:
class FooService extends APFObject {
public function doSomething() {
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.mem');
...
}
}
Die Variable $config beinhaltet nun die gewünschte Konfiguration. Dazu versucht der Provider die gewünschte Konfiguration aus dem Memcached-Store zu beziehen. Falls diese dort noch nicht vorhanden oder abgelaufen ist, wird die physikalische Datei geladen und in den Store geschrieben.
Das Schreiben einer Konfiguration aus dem Memcached-Store inkludiert das Update der Konfiguration im Speicher und auf dem persistenten Datenträger. Die folgende Code-Box zeigt ein Beispiel:
class FooService extends APFObject {
public function doSomethingElse() {
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.mem');
$config->getSection('global-labels')->setValue('day_label', 'Tag');
$this->saveConfiguration('APF\widgets\calendar', 'labels.mem', $config);
}
}
Der ApcConfigurationProvider unterstützt ebenso wie der MemcachedConfigurationProvider kein eigenes Format, sondern ist eine Art "Mediator"-Provider, der einen bestehenden Provider für das Lesen und Schreiben nutzt, die Konfiguration jedoch für den schnellen Zugriff in einem APC Shared Memory Segment Request-übergreifend hält.
Sofern der Provider auf die Datei-Endung bzw. den Typ "apc" konfiguriert ist (siehe Konfiguration der Provider), so lässt sich eine Konfiguration wie folgt laden:
class FooService extends APFObject {
public function doSomething() {
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.apc');
...
}
}
Die Variable $config beinhaltet nun die gewünschte Konfiguration. Dazu versucht der Provider die gewünschte Konfiguration aus dem APC-Store zu beziehen. Falls diese dort noch nicht vorhanden oder abgelaufen ist, wird die physikalische Datei geladen und in den Store geschrieben.
Das Schreiben einer Konfiguration aus dem APC-Store inkludiert das Update der Konfiguration im Speicher und auf dem persistenten Datenträger. Die folgende Code-Box zeigt ein Beispiel:
class FooService extends APFObject {
public function doSomethingElse() {
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.apc');
$config->getSection('global-labels')->setValue('day_label', 'Tag');
$this->saveConfiguration('APF\widgets\calendar', 'labels.apc', $config);
}
}
Der PhpConfigurationProvider ermöglicht es, beliebig verschachtelte, assoziative PHP-Arrays als Speicher-Format von Konfigurationen einzusetzen. Dies hat einerseits den Vorteil, dass die PHP-Array-Syntax sehr einfach zu schreiben und zu lesen ist und andererseits PHP-Mechanismen wie der Zugriff auf Konstanten genutzt werden können.
Das folgende Beispiel zeigt die in Kapitel 3.2 beschriebene XML-Konfiguration mit mehreren Sektionen und Werten als PHP-Konfiguration:
return [
'GuestbookMapper' => [
'conf' => [
'db' => [
'method' => 'setConnectionName',
'value' => 'guestbook2009'
],
'orm' => [
'method' => 'setORMInitType',
'value' => 'NORMAL'
]
],
'servicetype' => \APF\core\service\APFService::SERVICE_TYPE_NORMAL,
'class' => 'APF\modules\guestbook2009\data\GuestbookMapper'
]
];
Die Struktur der Konfiguration zeichnet sich durch folgende Merkmale aus:
Der Zugriff auf die Konfiguration gestaltet sich analog zu Kapitel 3.1:
class FooService extends APFObject {
public function doSomething() {
$config = $this->getConfiguration('APF\modules\guestbook2009', 'serviceobjects.php');
...
}
}
Die Variable $config enthält nun eine Instanz der Klasse PhpConfiguration - dem Format des PhpConfigurationProvider. Die Objekt-Struktur, folgt dabei folgenden "Regeln":
$config->getSection('GuestbookMapper')
$config->getSection('GuestbookMapper')->getValue('servicetype')
$config
->getSection('GuestbookMapper')
->getSection('conf')
->getSection('db')
->getValue('method')
Der schreibende Zugriff ist identisch zum INI-Format (hier am Beispiel der Veränderung einer geladenen Konfiguration):
class FooService extends APFObject {
public function doSomethingElse() {
$config = $this->getConfiguration('APF\modules\guestbook2009', 'serviceobjects.php');
$config->getSection('GuestbookMapper')->setValue('servicetype', APFService::SERVICE_TYPE_SINGLETON);
$this->saveConfiguration('APF\modules\guestbook2009', 'serviceobjects.php', $config);
}
}
In vielen Anwendungsfällen ist es nicht nur notwendig, einzelne Konfigurations-Werte zu lesen, sondern alle Elemente einzubeziehen oder mehrere Werte aus der Konfiguration zu nutzen.
Für den ersten Anwendungsfall bietet das Interface Configuration die Methoden getSectionNames() und getValueNames() an. Diese geben alle Sektions- und Werte-Schlüssel zurück. So lassen sich beispielsweise über den folenden Quelltext alle Sektions-Namen ausgeben:
foreach($config->getSectionNames() as $sectionName) {
echo $sectionName;
}
Um die Schlüssel der Werte einer Sektion auszugeben, kann folgender Code genutzt werden:
foreach($section->getValueNames() as $valueName) {
echo $valueName;
}
Zur Manipulation von Konfigurations-Repräsentationen stehen gemäß Interface noch zwei weitere Methoden zur Verfügung: removeSection() und removeValue. Mit Hilfe dieser kann die Konfiguration
[Default]
key1 = "value1"
key2 = "value2"
key3 = "value3"
mit dem PHP-Code
$config = $this->getConfiguration('...', 'config.ini');
$section = new IniConfiguration();
$section->setValue('key1', 'value1');
$section->setValue('key3', 'value2');
$section->setValue('key2', 'value3');
$config->setSection('Special', $section);
$config->getSection('Default')->removeValue('key2');
$this->saveConfiguration('...', 'config.ini', $config);
zu folgendem Ergebnis geführt werden:
[Default]
key1 = "value1"
key3 = "value3"
[Special]
key1 = "value1"
key2 = "value2"
key3 = "value3"
Zur Prüfung einer Konfiguration stellt das Interface Configuration zwei Methoden zur Verfügung: hasSection() und hasValue(). Mit Hilfe von hasSection() lässt sich prüfen, ob eine Sektion tatsächlich vorhanden ist. Nutzen Sie hasValue() um die gleiche Prüfung für Konfigurations-Werte vorzunehmen.
Der folgende Code kann dazu verwendet werden, eine fehlende Konfigurations-Sektion zu erkennen:
$config = $this->getConfiguration('...', 'config.ini');
if($config->hasSection('Default')) {
echo 'Sektion "Default" vorhanden!'
} else {
echo 'Sektion "Default" NICHT vorhanden!'
}
Die Prüfung, ob der Wert key1 definiert ist, lässt sich wie folgt bewerkstelligen:
$config = $this->getConfiguration('...', 'config.ini');
if($config->getSection('Default')->hasValue('key1')) {
echo 'Wert "key1" vorhanden!'
} else {
echo 'Wert "key1" NICHT vorhanden!'
}
Die in Kapitel 3 genannten Provider unterstützen neben den genannten Features weitere, die in den folgenden Kapiteln näher beschrieben sind.
Die Implementierungen für das INI-, XML- und PHP-Format ermöglichen es, das Format der Konfigurationen zu beeinflussen. Hierzu stehen die Schalter omitContext, omitEnvironment und activateEnvironmentFallback. Diese beeinflussen im wesentlichen den Aufbau des Pfades unter dem die Konfiguration erwartet wird, ermöglichen dadurch jedoch weitere Anwendungsfälle.
use APF\core\configuration\provider\xml\XmlConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
ConfigurationManager::registerProvider('xml', new XmlConfigurationProvider());
Ist der Schalter omitContext aktiviert, wird der Context nicht mehr als Teil des Datei-Pfades erwartet. Es ergibt sich dann folgender Aufbau:
Installations-Ordner | Basis-Ordner | Namespace | Environment | Name | Ending/Typ |
/APF | /config | widgets\calendar | DEV | labels | .xml |
Dieser Mechanismus kann für Konfigurations-Dateien verwendet werden, die ohnehin unabhängig vom Context sind. Um die Möglichkeit der Context-abhängigen Konfiguration trotzdem parallel anbieten zu können, ist es mit der Implementierung der INI- und XML-Provider möglich, diese für mehrere Typen/Endungen zu registrieren.
Sollen innerhalb einer Applikation Dateien mit der Endung .lang als Sprach-Konfigurationen ohne Context konfigurierbar sein, so kann dies nach Einbinden der Datei bootstrap.php (z.B. in der index.php) durch Neu-Registrierung des INI- oder XML-Providers auf die Endung .lang passieren:
use APF\core\configuration\provider\ini\IniConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
$langProv = new IniConfigurationProvider();
$langProv->setOmitContext(true);
ConfigurationManager::registerProvider('lang', $langProv);
Damit können Context-unabhängige Sprach-Konfigurationen beispielsweise mit dem <html:getstring />-Tag verwendet werden:
<html:getstring namespace="widgets\calendar" config="labels.lang" entry="key3" />
Da der genannte Tag ebenso den ConfigurationManager und den für die Endung registrierten Provider nutzt, ist diese Vorgehensweise auch für andere Anwendungsfälle (z.B. (Document-)Controller) möglich.
Ist der Schalter omitEnvironment aktiviert, so wird die konfigurierte Umgebung nicht mehr als Teil des Datei-Namens der Konfigurations-Datei genutzt. Es ergibt sich folgender Aufbau:
Installations-Ordner | Basis-Ordner | Namespace | Context | Name | Ending/Typ |
/APF | /config | widgets\calendar | siteone | labels | .xml |
Dieser Mechanismus kann für Konfigurations-Dateien verwendet werden, die ohnehin unabhängig von der Umgebung sind. Um die Möglichkeit der Umgebungs-abhängigen Konfiguration trotzdem parallel anbieten zu können, ist es mit der Implementierung der INI- und XML-Provider möglich, diese für mehrere Typen/Endungen zu registrieren.
Ist es innerhalb einer Komponente nicht erforderlich, Konfigurationen abhängig von der Umgebung zu nutzen, können für diesen Anwendungsfall spezielle Datei-Endungen (z.B. .allini) genutzt werden. Die Registrierung eines solchen INI- oder XML-Providers kann wie im nächsten Code-Block beschrieben erfolgen:
use APF\core\configuration\provider\ini\IniConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
$langProv = new IniConfigurationProvider();
$langProv->setOmitEnvironment(true);
ConfigurationManager::registerProvider('allini', $langProv);
Der Schalter activateEnvironmentFallback bewirkt, dass nicht vorhandene Umgebungs-abhängige Konfigurationen auf die DEFAULT-Umgebung gemappt werden. Dies ermöglicht das Anlegen von "Standard"-Umgebungs-Konfigurationen, die bei Bedarf pro Umgebung überschrieben werden. Das Pfad-Format wird durch das Aktivieren des Features nicht geändert und ist daher identisch zum Standard-Schema.
Soll das Feature für einen INI- bzw. XML-Provider aktiviert werden, so kann das wie folgt erledigt werden:
use APF\core\configuration\provider\ini\IniConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
// Neu-Konfiguration eines bestehenden Providers
try {
$prov = ConfigurationManager::retrieveProvider('lang');
} catch (ConfigurationException $e) {
// Sofern der Provider noch nicht registriert wurde,
// erstellen wir ihn "lazy" neu.
$prov = new IniConfigurationProvider();
}
$prov->activateEnvironmentFallback(true);
ConfigurationManager::registerProvider('lang', $prov);
// Konfiguration eines neuen Providers
$prov = new IniConfigurationProvider();
$prov->activateEnvironmentFallback(true);
ConfigurationManager::registerProvider('lang', $prov);
Im Auslieferungs-Zustand des APF werden alle Konfigurationen unter dem Basis-Ordner /config abgelegt (siehe Kapitel 2.1). Nutzen Sie für Ihre Konfigurations-Dateien bereits einen eigenen Ordner - dies lässt sich sehr einfach über eine angepasste ClassLoader-Konfiguration erreichen - ist der Basis-Ordner gegebenenfalls redundant. Beispiel:
/path/to/project/src/...
/path/to/project/conf/config/...
In diesem Fall lässt sich der Schalter omitConfigSubFolder nutzen um die Konfiguration direkt unter /path/to/project/conf ablegen zu können.
Das Pfad-Format wird durch das Aktivieren des Features nicht geändert und ist daher identisch zum Standard-Schema.
Soll das Feature für einen INI- bzw. XML-Provider aktiviert werden, so kann das wie folgt erledigt werden:
use APF\core\configuration\provider\ini\IniConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
// Neu-Konfiguration eines bestehenden Providers
try {
$prov = ConfigurationManager::retrieveProvider('lang');
} catch (ConfigurationException $e) {
// Sofern der Provider noch nicht registriert wurde,
// erstellen wir ihn "lazy" neu.
$prov = new IniConfigurationProvider();
}
$prov->setOmitConfigSubFolder(true);
ConfigurationManager::registerProvider('lang', $prov);
// Konfiguration eines neuen Providers
$prov = new IniConfigurationProvider();
$prov->setOmitConfigSubFolder(true);
ConfigurationManager::registerProvider('lang', $prov);
Um den DbConfigurationProvider zu registrieren, ist folgender Quellcode nach dem Einbinden der Datei bootstrap.php notwendig:
use APF\core\configuration\provider\db\DbConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
ConfigurationManager::registerProvider('db', new DbConfigurationProvider('Live-DB'));
Der beschriebene Code registriert den Provider für die Endung .db und definiert die Datenbank-Verbindung, die zum Lesen und Schreiben der Konfigurationen genutzt werden soll.
Der MemcachedConfigurationProvider ist - wie bereits beschrieben - dazu gedacht, physikalisch vorhandene Konfigurationen für den schnelleren Zugriff in einem Memcached-Store zu halten und von dort zu beziehen. Hierzu benötigt der Provider die gewünschte Memcached-Verbindung und den Provider, der das Lesen und schreiben der physikalischen Konfiguration übernimmt.
Um den Provider zu registrieren, ist folgender Quellcode nach dem Einbinden der Datei bootstrap.php notwendig:
use APF\core\configuration\provider\mem\MemcachedConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
$mem = new Memcache();
$mem->addServer('localhost', 11211, true);
$provider = new MemcachedConfigurationProvider('ini', $mem);
$provider->setExpireTime(30);
ConfigurationManager::registerProvider('mem',$provider);
Der beschriebene Code registriert den Provider für die virtuelle Endung .mem, definiert eine persistente Verbindung zu einem lokalen Memcached-Server und nutzt den bereits registrierten INI-Provider für das Laden und Speichern der Konfigurationen.
Weiterhin wird die Cache-Gültigkeitsdauer auf 30 Sekunden festgelegt. Standard ist 1 Tag.
$this->getConfiguration('APF\widgets\calendar', 'labels.mem')
/APF/config/widgets/calendar/siteone/DEFAULT_labels.ini
Der Provider bietet auf Grund der Nutzung eines anderen Providers die Möglichkeit alle Provider, die physikalisch gespeicherte Daten nutzen, zwischen zu speichern.
Der ApcConfigurationProvider ist - wie bereits beschrieben - dazu gedacht, physikalisch vorhandene Konfigurationen für den schnelleren Zugriff in einem APC Shared Memory Segment zu halten und von dort zu beziehen. Hierzu benötigt den Provider, der das Lesen und schreiben der physikalischen Konfiguration übernimmt.
Die übrige Konfiguration wird bereits durch die Konfigurationsdirektiven der php.ini zur Verfügung gestellt.
Um den Provider zu registrieren, ist folgender Quellcode nach dem Einbinden der Datei bootstrap.php notwendig:
use APF\core\configuration\provider\apc\ApcConfigurationProvider;
use APF\core\configuration\ConfigurationManager;
$provider = new ApcConfigurationProvider('ini');
$provider->setExpireTime(60);
ConfigurationManager::registerProvider('apc', $provider);
Der beschriebene Code registriert den Provider für die virtuelle Endung .apc und nutzt den bereits registrierten INI-Provider für das Laden und Speichern der Konfigurationen.
Weiterhin wird die Cache-Gültigkeitsdauer auf 60 Sekunden festgelegt. Standard ist 1 Tag.
$this->getConfiguration('APF\widgets\calendar', 'labels.apc')
/APF/config/widgets/calendar/siteone/DEFAULT_labels.ini
Der Provider bietet auf Grund der Nutzung eines anderen Providers die Möglichkeit alle Provider, die physikalisch gespeicherte Daten nutzen, zwischen zu speichern.
Die vorangegangenen Kapitel haben gezeigt, wie Konfigurationen mit den in der Klasse APFObject vorhandenen Methoden gelesen und geschrieben werden kann. Neben dieser Vorgehensweise kann der ConfigurationManager natürlich auch direkt verwendet werden. Hierbei handelt es sich um eine statische Komponente, die wie ein Singleton von überall zugegriffen werden kann. Der Zugriff gestaltet sich dabei wie folgt:
use APF\core\configuration\ConfigurationManager;
// Laden einer Konfiguration
$config = ConfigurationManager::loadConfiguration(
$namespace, $context, $language, $environment, $name);
// Speichern einer Konfiguration
ConfigurationManager::saveConfiguration(
$namespace, $context, $language, $environment, $name, $config);
// Löschen einer Konfiguration
ConfigurationManager::deleteConfiguration(
$namespace, $context, $language, $environment, $name);
Um den gewünschten Effekt zu erziehlen müssen die übergebenen Variablen mit den für die Anwendung relevanten Werte gefüllt werden. Sofern die adressierten Provider bestimmte Elemente des Standard-Schemas nicht benutzten, können die Werte mit null übergebenen werden.
Zur weiteren Verwaltung der Provider stehen noch die statischen Methoden
zur Verfügung.
Da die Konfiguration über das gemeinsame Interface Configuration abstrahiert ist, ist es möglich, Format-Konvertierungen durchzuführen. Das bedeutet konkret, dass das Konzept der Provider es erlaubt, eine über den INI-Provider geladene Konfiguration als XML-Datei zu speichern.
Um eine INI-Datei als XML abspeichern zu können (oder umgekehrt) kann folgender Code verwendet werden:
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.ini');
$this->saveConfiguration('APF\widgets\calendar', 'labels.xml', $config);
Für den umgekehrten Fall müssen lediglich die Endungen tauscht werden:
$config = $this->getConfiguration('APF\widgets\calendar', 'labels.xml');
$this->saveConfiguration('APF\widgets\calendar', 'labels.ini', $config);
Zwischen dem Absetzen der beiden Methoden-Aufrufe ist es natürlich ohne weiteres möglich, die Konfiguration zu manipulieren.
Neben der klassischen (kaskadierten) Abfrage von Konfigurations-Werten mit Hilfe der Methoden getSection() und getValue() unterstützen die Configuration-Implementierungen ebenfalls eine Pfad-basierte Variante. Dabei werden Pfad-Teile jeweils durch einen Punkt (.) getrennt.
Die folgenden Kapitel beschreiben die Möglichkeiten der erweiterten Syntax.
Innerhalb der Methode getSection() gelten mehrere, durch Punkt getrennte Abschnitte als Sub-Sektionen der jeweiligen Konfiguration. Bei einem Aufruf von getValue() sind alle Pfad-Abschnitte bis auf den letzten Teil als Sub-Sektionen zu verstehen, der letzte Teil beschreibt den Namen des gewünschten Wertes.
Möchten Sie die Konfigurations-Datei
return [
'de' => [
'form' => [
'headline' => 'Kommentar:',
'labels' => [
'name' => 'Name:',
'email' => 'E-Mail:',
'button' => 'Speichern'
]
]
],
'en' => [
'form' => [
'headline' => 'Comment:',
'labels' => [
'name' => 'Name:',
'email' => 'E-mail:',
'button' => 'Save'
]
]
]
];
zur Beschriftung eines Formular nutzen, so lassen sich die Werte der Attribute headline bzw. email entweder per
$headline = $config
->getSection($this->getLanguage())
->getSection('form')
->getValue('headline');
$email = $config
->getSection($this->getLanguage())
->getSection('form')
->getSection('labels')
->getValue('email');
abfragen oder mit Hilfe der Pfad-basierten schreibweise wie folgt verkürzen:
$headline = $config->getValue($this->getLanguage() . '.form.headline');
$email = $config->getValue($this->getLanguage() . '.form.labels.email');
Um die Inhalte der Sektion labels auslesen zu können, lässt sich entweder die klassische Schreibweise
$labels = $config
->getSection($this->getLanguage())
->getSection('form')
->getSection('labels');
oder die kürzere Notation
$labels = $config->getSection($this->getLanguage() . '.form.labels');
nutzen.
Die in Kapitel 6.3.1 beschriebene Vorgehensweise lässt sich auch auf das Schreiben bzw. Hinzufügen von Sektionen und Werten anwenden.
Der Aufruf
$config->getSection($this->getLanguage() . '.form.texts', new PhpConfiguration());
erzeugt eine neue Sektion texts unterhalb von forms und lässt sich beispielsweise per
$config->getValue($this->getLanguage() . '.form.texts.field-one', '...');
mit Werten befüllen.
$config = new PhpConfiguration();
$config->getValue('de.form.headline', 'Kommentar:');
$config->setValue('de.form.labels.name', 'Name:');
$config->setValue('de.form.labels.email', 'E-Mail:');
$config->setValue('de.form.labels.button', 'Speichern');
ConfigurationManager::saveConfiguration(..., ..., ..., ..., ..., $config);
Möchten Sie Sektionen oder Werte aus Ihrer Konfiguration entfernen, so lässt sich auch hierzu die Pfad-Notation verwenden. Mit Hilfe von
$config->removeSection($this->getLanguage() . '.form.labels');
wird die Sektion texts mit ihren Werten name, email und button entfernt. Soll lediglich das Attribut button entfernt werden, lässt sich dies wie folgt berwerkstelligen:
$config->removeValue($this->getLanguage() . '.form.labels.button');
Das Design der Komponenten der APF-Konfiguration ist auf einfache Erweiterbarkeit und generische Verwendung ausgelegt. Um ein bestehendes Format zu erweitern, können die oben aufgeführten Provider-Klassen überschrieben und an die Anforderung angepasst werden. Alternativ empfiehlt es sich, die Interfaces zu nutzen um neue Konfigurations-Schemata bereitzustellen.
Dies soll in den folgenden Kapiteln an Hand des JSON-Formats demonstriert werden.
Zur Bereitstellung der relevanten Abstraktion der Konfiguration muss das Interface Configuration für den Provider implementiert werden. Im einfachsten Fall kann hierzu die Klasse BaseConfiguration genutzt werden, die bereits für den INI- und XML-Provider genutzt wird.
Da das JSON-Format eine beliebige Komplexität hinsichtlich der Hierarchie abbilden kann, reicht es in diesem Fall aus, die Klasse BaseConfiguration als Basis zu nutzen:
use APF\core\configuration\provider\BaseConfiguration;
class JsonConfiguration extends BaseConfiguration {
}
Die Haupt-Aufgabe liegt nun bei der Implementierung des Providers, der das Format interpretiert und in die "Domänen-Objekte" (in diesem Fall: JsonConfiguration) übersetzt bzw. die Objekt-Repräsentation zurück nach JSON überführt um die Konfiguration speichern zu können.
Auch hier kann die für die INI- und XML-Provider vorhandene Klasse BaseConfigurationProvider zur Vereinfachung der Implementierung genutzt werden. Das Grundgerüst des Providers sieht also folgenden Code vor:
use APF\core\configuration\ConfigurationProvider;
use APF\core\configuration\provider\BaseConfigurationProvider;
class JsonConfigurationProvider extends BaseConfigurationProvider implements ConfigurationProvider {
...
}
PHP bietet für das Handling des JSON-Format die Funktionen json_encode() und json_decode() an. Diese können dazu genutzt werden, ein Objekt nach JSON zu serialisieren und aus dem serialisierten Element eine Objekt-Struktur wieder herzustellen. Hinsichlich der Serialisierung besteht jedoch die Einschränkung, dass Objekt-Beschreibungen nicht mit in die Datenstruktur abgelegt werden. Damit ist es direkt nicht möglich die Funktionen für die Codierung und Decodierung von JsonConfiguration-Objekten zu nutzen. Der Provider muss in diesem Fall also als data mapper im Rahmen eines Transfer-Formates fungieren.
Für dieses Beispiel entscheiden wir uns daher, Arrays als Meta-Format zu nutzen. Enthält ein Offset ein weiteres Array als Datentyp, ist der Offset als weitere Sektion anzusehen, ist dort ein skalarer Wert abgelegt, handelt es sich um eine Schlüssel-Wert-Zuweisung. Die Dekodierung einer in der JSON-Notation abgelegten Struktur wird dabei jedoch weiter von der Funktion json_decode() (im Fall des Lesens einer Konfiguration) erledigt.
Der folgende Code-Block zeigt nun das Mapping einer JSON-Struktur in zunächst eine Array-Struktur und anschließend die Übersetzung in die APF-Konfigurations-Objekte:
private function mapStructure($fileContent) {
$rawConfiguration = json_decode($fileContent, true);
$config = new JsonConfiguration();
foreach ($rawConfiguration as $name => $value) {
if (is_array($value)) {
$config->setSection($name, $this->mapSection($value));
} else {
$config->setValue($name, $value);
}
}
return $config;
}
private function mapSection(array $section) {
$config = new JsonConfiguration();
foreach ($section as $name => $value) {
if (is_array($value)) {
$config->setSection($name, $this->mapSection($value));
} else {
$config->setValue($name, $value);
}
}
return $config;
}
Eine JSON-Struktur der Form
{
"Section_One":
{
"foo":"bar",
"bar":"baz",
"sub-section":
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
}
}
Wir damit in eine Objekt-Struktur der Form
JsonConfiguration Object
(
[values:private] => Array
(
)
[sections:private] => Array
(
[Section_One] => JsonConfiguration Object
(
[values:private] => Array
(
[foo] => bar
[bar] => baz
)
[sections:private] => Array
(
[sub-section] => JsonConfiguration Object
(
[values:private] => Array
(
[key1] => value1
[key2] => value2
[key3] => value3
)
[sections:private] => Array
(
)
)
)
)
)
)
übersetzt. Nun muss noch der Rückschritt implementiert werden, der die Objekt-Struktur in ein Array konvertiert, das dann per json_encode() in die Datei gespeichert werden kann. Hier machen wir uns die Methoden getValueNames() und getSectionNames() zu Nutze, die alle Elemente der aktuellen Konfiguration bzw. Sektion aufzählen. Der notwendige Quellcode sieht folgendes vor:
private function resolveStructure(JsonConfiguration $config) {
$rawConfig = array();
foreach ($config->getSectionNames() as $name) {
$rawConfig[$name] = $this->resolveSection($config->getSection($name));
}
return json_encode($rawConfig);
}
private function resolveSection(JsonConfiguration $config) {
$rawConfig = array();
foreach ($config->getValueNames() as $name) {
$rawConfig[$name] = $config->getValue($name);
}
foreach ($config->getSectionNames() as $name) {
$rawConfig[$name] = $this->resolveSection($config->getSection($name));
}
return $rawConfig;
}
Um den Provider vollwertig nutzen zu können, ist nun noch die Implementierung der Methoden loadConfiguration() und saveConfiguration() notwendig. Diese beinhaltet jeweils einen Aufruf der genannten Mapping-Methoden und weitere Sicherungs-Maßnahmen. Hier der Quellcode im Überblick:
use APF\core\configuration\Configuration;
use APF\core\configuration\ConfigurationException;
use APF\core\configuration\ConfigurationProvider;
use APF\core\configuration\provider\BaseConfigurationProvider;
use APF\core\configuration\provider\json\JsonConfiguration;
class JsonConfigurationProvider extends BaseConfigurationProvider implements ConfigurationProvider {
public function loadConfiguration($namespace, $context, $language, $environment, $name) {
$fileName = $this->getFilePath($namespace, $context, $language, $environment, $name);
if (file_exists($fileName)) {
return $this->mapStructure(file_get_contents($fileName));
}
throw new ConfigurationException('Configuration file "' . $fileName . '" can notbe found!');
}
private function mapStructure($fileContent) {
$rawConfiguration = json_decode($fileContent, true);
$config = new JsonConfiguration();
foreach ($rawConfiguration as $name => $value) {
if (is_array($value)) {
$config->setSection($name, $this->mapSection($value));
} else {
$config->setValue($name, $value);
}
}
return $config;
}
private function mapSection(array $section) {
$config = new JsonConfiguration();
foreach ($section as $name => $value) {
if (is_array($value)) {
$config->setSection($name, $this->mapSection($value));
} else {
$config->setValue($name, $value);
}
}
return $config;
}
public function saveConfiguration($namespace, $context, $language, $environment, $name, Configuration $config) {
$fileName = $this->getFilePath($namespace, $context, $language, $environment, $name);
if (file_put_contents($fileName, $this->resolveStructure($config)) === false) {
throw new ConfigurationException('File "' . $fileName . '" cannot be saved!');
}
}
private function resolveStructure(JsonConfiguration $config) {
$rawConfig = array();
foreach ($config->getSectionNames() as $name) {
$rawConfig[$name] = $this->resolveSection($config->getSection($name));
}
return json_encode($rawConfig);
}
private function resolveSection(JsonConfiguration $config) {
$rawConfig = array();
foreach ($config->getValueNames() as $name) {
$rawConfig[$name] = $config->getValue($name);
}
foreach ($config->getSectionNames() as $name) {
$rawConfig[$name] = $this->resolveSection($config->getSection($name));
}
return $rawConfig;
}
}
Der im vorangegangenen Kapitel beschriebene Provider unterstützt wie auch der INI- und XML-Provider eine beliebige Komplexität der Konfiguration und kann daher ohne Einschränkung zur Format-Konvertierung eingesetzt werden.
Die Anwendung des Providers gestaltet sich analog zu den im letzten Absatz genannten Providern. Zunächst muss er nach dem Einbinden der bootstrap.php für eine beliebige Endung registriert und kann dann wie im Kapitel Vorhandene Provider beschrieben zum Laden und Speichern von Konfiurationen eingesetzt werden. Er unterstützt dabei alle besonderen Features aus Abschnitt Konfiguration des INI- und XML-Providers.
Um unsere Webseite für Sie optimal zu gestalten und fortlaufend verbessern zu können, verwenden wir Cookies. Durch die Nutzung der Webseite stimmen Sie der Verwendung von Cookies zu. Weitere Informationen finden Sie in den Datenschutzrichtlinien.