Vielleicht dient es jemanden als Anregung…
Mein Problem:
Ausgangspunkt aller Lösungen ist ein Problem. In meinem Fall war mein Problem dies, dass ich unterschiedliche Layouts setzen wollte. Außerdem wollte ich noch zusätzliche Informationen je http-Request abfragen wie z.B. Description, Keywords, etc.
Wer Typo3 kennt weiß, dass man grundsätzlich ein Default-Layout hat. Dann gibt es aber je Seite die Möglichkeit ein spezielles Layout zu setzen.
Ich wollte es gerne noch variabler haben. Im ersten Moment war der Gedanke dies in eine Tabelle zu speichern – ähnlich wie pages bei Typo3. Doch dies wäre mit einer Lösung die Rekursion unterstützen soll, zum einen nicht leicht gewesen und zum anderen hätte es viele Datenbankabfragen mit sich gebracht. Viel performanter ist es eine Datei zu lesen.
Betrachten wir nur einmal die Anforderung, unterschiedliche Layouts zu verwenden. Unser http-Request kann z.B. so aussehen:
http://localhost/immobilien/search > Default Layout
http://localhost/immobilien/getDetail/4711 > Zweispaltiges Layout
oder wenn wir den Admin-Bereich aufrufen:
http://localhost/admin/administration/getlog > Admin Layout
Mit $this->here erhalten wir jeweils den markierten Substring. Diesen könnte man nun genauso als Array darstellen:
- Code: Alles auswählen
- [immobilien]
[getDetail]
[4711]
Eine XML-Datei ist auch nichts anderes, als ein Array:
- Code: Alles auswählen
- <?xml version="1.0" encoding="utf-8" standalone="yes"?>
<PROFIRMA>
<CONTROLLER>
<immobilien>
<getDetail>
<LAYOUT>admin</LAYOUT>
</getDetail>
<THEME>immoplain</THEME>
<LAYOUT>default</LAYOUT>
<search>
<LAYOUT>default</LAYOUT>
</search>
</immobilien>
<setups>
<index>
<LAYOUT>admin</LAYOUT>
</index>
<testxml>
<LAYOUT>default</LAYOUT>
</testxml>
<LAYOUT>admin</LAYOUT>
</setups>
</CONTROLLER>
</PROFIRMA>
Wahrscheinlich versteht jetzt jeder, worauf ich hinaus möchte: Ich möchte die Möglichkeit haben, für jede Seite ein spezielles Layout zu definieren. Aber ich möchte auch nicht dazu gezwungen sein. Wenn ich für den speziellen Aufruf kein Layout finde, dann suche eine Stufe oder zwei Stufen tiefer. Wenn überhaupt nichts gefunden wird, soll das Standard-Layout verwendet werden.
Genauso könnte ich mit weiteren Dingen verfahren, die ich auf einer HTML-Seite benötige wie eben die Keywords, Description und andere Meta-Tags.
Also, was brauchen wir dazu, um das zu realisieren?
Zuerst benötigen wir eine XML-Datenquelle. Die Datei haben wir ja schon eben definiert.
Unter /app/datasources erstellen wir die Datei xml_source.php.
- Code: Alles auswählen
- <?php
class XmlSource extends DataSource {
var $description = 'XML DataSource';
function construct($config = null) {
parent::__construct($config);
$this->connected = $this->connect();
$this->setConfig($config);
return $config;
}
function __destruct() {
$this->connected = $this->close();
parent::__destruct();
}
function findController() {
App::import('Core','Xml');
$svDatasource = VENDORS . 'meinefirma' . DS . $this->config['file'];
$xml = new Xml($svDatasource);
$controllers = $xml->children[0]->child('CONTROLLER');
return $controllers;
}
function connect() {
}
}
?>
Die xml-Datei liegt dann im Verzeichnis /vendors/meinefirma.
Jetzt sollten wir noch wissen, wie unsere XML-Datei heißen soll. Das ergibt sich aus der Config-Datei.
In der Datei /app/config/database.php machen wir folgenden zusätzlichen Eintrag:
- Code: Alles auswählen
Damit wäre nun auch klar, unter welchem Dateinamen unsere XML-Datei gespeichert werden muss.
Wichtig ist hierbei: der Wert des Schlüssels datasource entspricht dem Klassennamen der Datenquelle.
In der Definition der Datenquelle haben wir gleich die Funktion zum auslesen aufgenommen. Dabei verwenden wir den einfachen XML-Parser, der von CakePHP verwendet wird. Die Funktion findController() liest das Element CONTROLLER komplett aus und übergibt es an die aufrufende Funktion.
Jetzt erstellen wir uns noch ein Modell. Das nenne ich setup.php:
- Code: Alles auswählen
- <?php
class Setup extends AppModel {
var $name = 'Setup';
var $useTable = false;
function xmlFindController() {
$this->setDataSource('xml');
$xml = $this->getDataSource();
return $xml->findController();
}
}
?>
Die Funktion setzt die Datenquelle xml (aus der database.php) und ruft die Funktion findController() der Datenquelle auf.
Damit haben wir jetzt schon einen großen Teil geschafft. Was uns jetzt noch fehlt, ist eine Komponente - denn wir benötigen die Funktion ja auf jeder Seite.
Erstellen wir also in /app/controllers/components die Datei myconfig.php.
Da wir unsere Funktion ganz allgemein auch für andere Dinge verwenden möchten, übergeben wir dieser zwei Parameter: den Pfad und den Schlüssel.
- Code: Alles auswählen
- class MyconfigComponent extends Object {
/******************************************************************************
Function : startup
Arguments : $controller
Description:
*******************************************************************************/
function startup(&$controller) {
}
/******************************************************************************
Function : getValue
Arguments : $path, $key
Description:
*******************************************************************************/
function __getValue($path, $key) {
$svPath = '';
$result = '';
$controllers = $this->Setup->xmlFindController();
$saController = $this->__xmltoArray($controllers);
// der erste Slash muss weg
if(substr($path,0,1) == '/') {
$path = substr($path,1,strlen($path));
}
$searchpath = $path . '/' . $key;
$saResult = $this->__xmltoArray($controllers);
$searchpath = str_replace('/', '.', $searchpath);
$saResult = Set::extract($this->__xmltoArray($controllers),$searchpath);
if(!empty($saResult)) {
$result = $saResult['#text'];
} else
{ // Funktion so lange aufrufen bis wir auf der untersten Ebene sind
// wenn nicht gefunden muss der erste slash weg
if(substr($path,0,1) == '/') {
$path = substr($path,1,strlen($path));
}
// dann den String in ein Array zerlegen denn wir suchen bis max. auf die dritte Ebene (erster Parameter)
$saPath = explode('/',$path);
$nPath = count($saPath);
if(count($saPath) > 1) {
$pfadneu = array_pop($saPath);
foreach ($saPath as $value) {
$svPath = $svPath . '/' . $value;
}
$result = $this->getValue($svPath,$key);
} else {
return null;
}
}
return $result;
}
In __getValue rufen wir die Funktion xmlFindController() aus unserem Modell. Damit erhalten wir das XML-Objekt. Aus diesem müssen wir erst einmal ein Array machen. Die Funktion xmltoArray habe ich bei Felix Geisendörfer gesehen.
Mit der Cake-Funktion Set::extract können wir prüfen, ob wir den Schlüssel finden. Dazu müssen die Schrägstriche / durch Punkte ersetzt werden.
Die Funktion rufen wir so lange rekursiv auf, bis wir was finden oder wir geben null zurück. Dann muss ein Default-Wert ziehen.
Hier noch die Funktion von Felix, die wir auch in unsere Komponente packen:
- Code: Alles auswählen
- /******************************************************************************
Function : __xmltoArray
Arguments : $node
Description:
*******************************************************************************/
function __xmltoArray($node) {
$array = array();
foreach ($node->children as $child) {
if (empty($child->children)) {
$value = $child->value;
}
else {
$value = $this->__xmltoArray($child);
}
$key = $child->name;
if (!isset($array[$key])) {
$array[$key] = $value;
}
else {
if (!is_array($array[$key]) || !isset($array[$key][0])) {
$array[$key] = array($array[$key]);
}
$array[$key][] = $value;
}
}
return $array;
}
Ebenfalls in unsere Komponente können wir jetzt unsere Funktion aufnehmen, mit der wir das Layout ermitteln. Diese ist recht einfach:
- Code: Alles auswählen
- /******************************************************************************
Function : getLayout
Arguments : $path
Description:
*******************************************************************************/
function getLayout($path) {
$svPath = '';
App::import('Model', 'setup');
$this->Setup = new Setup();
// falls der Pfad mit Slash endet, muss dieser entfernt werden
if (substr($path, -1) == '/') {
$path = substr($path,0,strlen($path)-1);
}
//echo $path;
$svLayout = $this->__getValue($path, 'LAYOUT');
//echo "<br/>Layout nach Funktion: " . $svLayout;
return $svLayout;
}
Ich habe hier noch ein paar Kommentare belassen, mit der man an einigen Stellen prüfen kann, was übergeben wird.
In der Funktion beforeRender des AppController können wir jetzt unsere Funktion aufrufen und erhalten das Layout – oder auch nicht. Wenn nicht, setzen wir das default Layout.
Genauso kann die Funktion verwendet werden, um weitere Seiteneigenschaften zu setzen, die in der xml-Datei konfiguriert sind.
- Code: Alles auswählen
So, die Nacht ist rum, der Tag ist angebrochen. Schnell unter die Dusche und los geht's.
Ich hoffe ich konnte mit dem Beispiel ein paar Anregungen geben.
Ciao Thomas