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:
Diese drei Templates leiten von einander ab:
{% base_extends "index.twig" %}
{% block blubb %}
{{ parent() }}
3<br/>
{% endblock %}
{% base_extends "index.twig" %}
{% block blubb %}
2<br/>
{% endblock %}
<strong>
{% block blubb %}
1
{% endblock %}
</strong>
Am Ende wollen wir folgendes Ergebnis haben:
<strong>
2<br/>
3<br/>
</strong>
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();
$parts = preg_split("/\//i", preg_replace("/@/", '', $source));
$newBase = findNewBase($template, $this->list, $parts[0]);
$parent = '@' . $newBase . '/' . $template;
$stream->next();
$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),
]);
return new Node();
}
}
Das eigentliche Beispiel sieht dann so aus:
$list = [
'plugin2' => 'templates/path3',
'plugin1' => 'templates/path2',
'base' => 'templates/path1'
];
$loader = new \Twig\Loader\FilesystemLoader();
foreach($list as $plugin => $path) {
$loader->addPath($path, $plugin); //plugin as namespace
}
$twig = new \Twig\Environment($loader);
$twig->addTokenParser(new ExtTokenParser($list));
echo $twig->render('@' . findNewBase('index.twig', $list) . '/index.twig', []);
Und am Ende kommt raus:
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.