Aus Shopware kennt man das Prinzip, dass man beim Erweitern von Templates einfach "parent:" angeben muss und es wird immer das vorher gehende Template mit dem selben Pfad erweitert. So kann man ein Template mehrmals durch eine unbekannte Anzahl von Plugins erweitern lassen. Twig will aber immer einen Namespace haben. Also muss man heraus finden, mit welchen Plugin man anfängt und welches Plugin dann auf das aktuelle folgt oder man auf das Basis-Template gehen muss. Ich hab mich von der Shopware 6 Implementierung inspirieren lassen und ein kleines Beispiel gebaut, bei dem man die ein Template erweitern kann und die Plugin-Namespaces immer dynamisch ergänzt werden.
Die Verzeichnisstruktur des Beispiels ist sehr einfach:
Die Basislogik habe ich in einer einfachen Function zusammen gefasst. Hier wird entweder das Plugin heraus gesucht mit dem angefangen werden muss oder das Plugin, das auf das aktuelle folgt und auch dieses Template mitbringt.
function findNewBase($template, $list = [], $currentBase = null) {
$result = 'base';
$found = $currentBase == null; //if null, took the first one
foreach($list as $key => $path) {
if($key == $currentBase) {
$found = true;
}
else if ($found && file_exists($path . '/' . $template)) {
$result = $key;
break;
}
}
return $result;
}
Die Integration wird über ein Token-Parser implementiert.
final class ExtTokenParser extends AbstractTokenParser {
/**
* @var Parser
*/
protected $parser;
private $list = [];
public function __construct(array $list)
{
$this->list = $list;
}
public function getTag(): string
{
return 'base_extends';
}
/**
* @return Node
*/
public function parse(Token $token)
{
$stream = $this->parser->getStream();
$source = $stream->getSourceContext()->getName();
$template = $stream->next()->getValue();
$stream->injectTokens([
new Token(Token::BLOCK_START_TYPE, '', 2),
new Token(Token::NAME_TYPE, 'extends', 2),
new Token(Token::STRING_TYPE, $parent, 2),
new Token(Token::BLOCK_END_TYPE, '', 2),
]);
Jetzt fehlt nur noch eine passende include-Funktion und man kann sich selbst ein System bauen, desen Templates sich über Plugins ohne Probleme erweitern lassen. Ich arbeite daran....
Edit: Die vollständige Implementierung mit extends und include ist jetzt auf GitHub zu finden.
Ein Problem bei Smarty 3 ist, dass man zwar sehr schön Blöcke überschreiben oder erweitern kann, es aber keine Möglichkeit gibt, die im Block vorhanden anderen Blöcke dan unangetastet zu lassen. Man muss in so einem Fall die nested Blöcke immer mit kopieren und mit dem Parent-Template immer wieder bei Änderungen abgleichen. Wenn z.B. ein Template einen Block mit einer <form> hat und darin dann die ganzen Eingabe-Möglichkeiten auch als Blöcke ausgelegt sind, muss man wenn man die Form-Action ändern will zwingend auch das ganze Form-Content Templating mit übernehmen.
Eine Möglichkeit das zu umgehen ist den Block rendern zu lassen und dann per String-Operationen das Ergebnis anzupassen. Primitiv aber auch sehr effektiv!
Falls man Formular von Shopware in anderen Contexts als dem forms-Controller einsetzt, kann es nötig sein, dass Template so anzupassen, damit es noch auf den forms-Controller zeigt und nicht auf den Controller in desen Context man gerade arbeitet.
Wenn man sich für Shopware ein Plugin kauft, kann es sein, dass man die Daten genau so vorfindet, wie man sie braucht, aber möchte das dort verwendete Template ersetzen oder die Daten in einem Template verwenden, das im Plugin noch gar nicht vorgesehen war.
Dafür kann man sich ein eigenes kleines Plugin schreiben. Das geht in 5 Minuten. Wir schreiben uns das Plugin TESTNotLoggedIn und blenden damit den Newsletter in der Footer Navigation aus.
Ins Verzeichnis TESTNotLoggedIn kommt die Datei TESTNotLoggedIn.php:
namespace TESTNotLoggedIn;
use Shopware\Components\Plugin;
class TESTNotLoggedIn extends Plugin{
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_PostDispatchSecure_Frontend' => 'addTemplateDir',
'Enlight_Controller_Action_PostDispatchSecure_Widgets' => 'addTemplateDir',
];
}
public function addTemplateDir(\Enlight_Controller_ActionEventArgs $args)
{
try {
$args->getSubject()->View()->addTemplateDir($this->getPath() . '/Resources/views/');
}
catch(\Exception $e){
//TODO
}
}
}
jetzt kommt das Verzeichnis Resources/views/ dazu und die Datei plugin.xml:
<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/5.2/engine/Shopware/Components/Plugin/schema/plugin.xsd">
<label lang="de">TEST Not Logged In</label>
<label lang="en">TEST Not Logged In</label>
Hier wird das Plugin, das wir erweitern wollen, angegeben. Ich verwendet das kostenlose "Globale Kunden Smarty-Variablen". Nun können wir einfach unsere Templates im views-Verzeichnis anlegen und vorhandene erweitern.
Als Beispiel kommt hier Resources/views/frontend/index/footer-navigation.tpl
Damit ist das Plugin schon fertig und kann installiert werden. Der Newsletter Bereich im Footer ist nun nur sichtbar, wenn man eingelogged ist.
Das Plugin "Eigenschaften in Artikel-Listing" bietet z.B. auch an in der Detailseite die Daten nur als Smarty-Variable bereit zu stellen, damit man selbst direkt die Darstellung implementieren kann und nicht noch eine vorhandene unpassende anpassen oder entfernen und ersetzen muss.
Am Ende dachte ich mir nur: "ist an sich ja genau so wie in meinem Framework.."
Ok. Alle Sprachen in einer Datei und nicht wie bei den Properties-Dateien in Java, aber ansonsten. Im Grunde hat man eine ini-Datei die eingelesen wird. Sie hat einen Namespace, was es sehr viel einfacher macht auf Text-Snippets anderer Plugins oder des Core-Systems zuzugreifen.
Gehen wir mal davon aus wir erstellen eine Homepage für eine Restaurant-Kette mit 3 Restaurants. Die Seite soll so gebaut sein, dass Mitarbeiter der Restaurants einfache Datenänderungen vornehmen können, ohne dafür um Hilfe fragen zu müssen. Auch sollen Änderungen im Layout einfach und schnell möglich sein, dass aber nicht durch die Mitarbeiter, da diese wohl kein HTML beherrschen.
Jedes Restaurant hat eine eigene Unterseite, die vom Aufbau aber immer gleich sind.
Das Problem ist, dass wenn es Änderungen im Layout dieser 3 Seiten geben soll, müssen die Änderungen auch in allen 3 Seiten getätigt werden. Ein einfaches Kopieren geht natürlich nicht, da man sonst ja die individuellen Texte und Bilder überschreiben würde. Die speziellen Öffnungszeiten für Feiertage einzutragen ist nicht ganz so kompliziert, wenn man einen WYSIWYG-Editor hat. Aber einfacher wäre es natürlich, wenn diese Angaben atomar eingegeben werden können und so auch nicht die Gefahr besteht, durch Fehler die gesamte Seite durcheinander zu bringen.
Ich habe für mein Framework/CMS diese Lösung entwickelt. In dem Code der Seite sind Platzhalter eingebaut, die heraus geparst werden und mit individuellen Werten gefüllt werden können. Daher kann man einfach eine vorhandene Seite kopieren und muss nur die im Formular ausgewiesenen Platzhalter neu füllen. Das Layout zu Ändern geht auch einfach weil man den Code wirklich mit Copy und Paste in alle Seiten übernehmen kann, ohne Werte zu überschreiben.
Man hätte natürlich auch Entitäten und Controller mit Templates bauen können. Aber für solche Zwecke ist das oft vom Aufwand her nicht gerechtfertigt, weil die verwendete Zeit nicht entsprechend bezahlt werden würde und mit dieser Lösung es wohl auch schneller geht.
Und für die einfachen Benutzer ist es so auch viel übersichtlicher und sie können dann bestimmt mehr alleine Ändern, weil sie nicht der Content der gesamten Seite erschlägt.
Zum 10. Geburtstag meines PHP-Frameworks wird sich da viel ändern und es damit dann wieder etwas mehr zu anderen Frameworks aufschließen können.
Mein eigener Class-Loader fliegt raus und es wird noch der ClassLoader vom Composer verwendet. Meiner kann zwar auch mit PSR4 umgehen, aber nach dem Umstieg muss ich dann auch alles darauf umstellen und habe keine Ausrede mehr, weil die Kompatibilität weg fällt.
Es gibt nun ein public-Verzeichnis so dass alle Config-Dateien und Classes außerhalb des Bereiches liegen, auf dem Benutzer zugreifen können. Das sorgt für mehr Sicherheit, macht das Laden von CSS und Bildern aus dem Theme-Verzeichnis aber komplizierter. Ich überlege hier auf Assetic umzusteigen. Momentan läuft meine Lösung aber sehr gut.
Und das Beste und wichtigste was aber auch am meisten Arbeit machen wird (darum habe ich ganz viele alte Module erst einmal gelöscht und lasse auch die alten PHP-Pages drin) ist, dass Module-Pages nun wirklich mit Controller und Twig-Template gerendert werden. Die Twig-Templates könenn vom Theme und von eigenes Templates in der Instance überschrieben werden und so kann man nun endlich das Aussehen der Module leicht und schnell anpassen.
Smarty war schon länger drin, wurde aber nie benutzt und ist jetzt raus geflogen.
Mein erstes Projekt damit wird sein, die Zeiterfassung für Kunden-Projekte neu zu schreiben und für alle brauchbar zu machen. Das wird wohl am Ende nur 1-2 Tage dauern. Also nichts im Vergleich zum Umbau des Frameworks.
Das grobe Konzept habe ich wohl so weit verstanden. Erstmal müssen die keine wohl geformten XML oder HTML Dateien sein. Also man kann ein HTML-Dokument einfach in der Mitte teilen und als Header und Footer verwenden und in der Mitte wird dann einfach was dazwischen eingefügt bzw wird eher der Header und der Footer vor bzw hinter den speziellen HTML-Code eingefügt, wo durch ein komplettes Dokument entsteht.
Wenn Header und Footer nicht angezeigt werden ist es nicht schlimm. Es wird wohl nur angezeigt was die Core-Templates überschreibt. Also "neu" und dann das Core-Template auswählen und schon kann man seine eigene VErsion davon erstellen.
Blog-entries by search-pattern/Tags:
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?