Vorlagensystem
DotApp rendert HTML mit der Renderer-Fassade und einer kleinen Menge an {{ … }}-Direktiven. Vorlagen sind PHP-Dateien, die einem Modul gehören. Controller übergeben Daten mit setViewVar(). Es gibt keine separate Laufzeitumgebung für eine Vorlagensprache: Direktiven werden zu PHP kompiliert, anschließend wertet eine Sandbox das Ergebnis aus.
Die Live-Seiten dieser Website folgen denselben Regeln. Hello World: /helloworld. Sichere Formulare: /documentation/examples/run/forms2. Die schrittweise Anleitung finden Sie unter Schritt-für-Schritt-Anleitung.
1.1 Was ist das Vorlagensystem?
Jedes Modul hält die Präsentation in app/modules/{Module}/views/. Eine View ist eine vollständige Seite oder eine Hülle. Ein Layout ist ein wiederverwendbares Fragment (Kopfzeile, Listenzeile, Überschrift). Der Controller wählt die Dateien, weist Variablen zu und gibt die HTML-Zeichenkette aus renderView() oder renderLayout() zurück.
Geben Sie einen Wert mit {{ var: $title }} aus. Das ist die einzige unterstützte Ausgabesyntax. {{ $title }} ist keine Direktive und gibt die Variable nicht aus.
1.2 Dateien und Ordner
| Art | Pfad | Auswahl über |
|---|---|---|
| View | app/modules/{Module}/views/{name}.view.php |
setView('name') |
| Layout | app/modules/{Module}/views/layouts/{path}.layout.php |
setLayout('path') oder {{ layout:path }} |
| Anderes Modul | Dieselbe Struktur unter diesem Modul | setView('Shop:home'), {{ layout: Shop:partials/header }} |
| Basis-Layout | app/parts/views/layouts/ |
nur {{ baselayout:name }} |
| Assets | app/modules/{Module}/assets/... |
/assets/modules/{Module}/... |
{{ layout:partials/header }} lädt views/layouts/partials/header.layout.php. Das Layout-Verzeichnis ist bereits die Wurzel — schreiben Sie nicht layout:layouts/header. Verschachtelte Includes enden bei Tiefe 20.
1.3 Die Renderer-Fassade
Erzeugen Sie einen Renderer mit Renderer::new(), richten Sie ihn auf ein Modul und setzen Sie anschließend die View. Benannte Instanzen (Renderer::new('docs')) werden als Singletons wiederverwendet. Für eine Seite beginnen Sie eine neue Kette mit Renderer::new().
use Dotsystems\App\Parts\Logger;
use Dotsystems\App\Parts\Renderer;
use Dotsystems\App\Parts\Response;
$html = Renderer::new()
->module('HelloWorld')
->setView('hello')
->setViewVar('title', 'Hello World')
->setViewVar('message', 'DotApp 2.0 is running.')
->renderView();
if ($html === '') {
Logger::use()->error('HelloWorld view produced empty output');
return new Response(500, 'Template error');
}
return $html;
Es gibt kein setViewVars() im Plural und kein öffentliches getView() / getLayout(). Lesen Sie einen einzelnen Wert mit getViewVar('title') (fehlender Schlüssel gibt "" zurück) oder die gesamte Sammlung mit getViewVars().
1.4 Fehlende Dateien schlagen still fehl
Eine fehlende View oder ein fehlendes Layout löst keine Ausnahme aus. Der Renderer protokolliert eine Warnung und gibt eine leere Zeichenkette zurück. Eine leere Seite bedeutet in der Regel einen falschen Dateinamen, das falsche Modul oder dass setView() nie aufgerufen wurde. Übergeben Sie immer einen Fallback-Namen und prüfen Sie den Rückgabewert:
$html = Renderer::new()
->module('Shop')
->setView('home', 'fallback/empty')
->setLayout('catalog/list', 'catalog/empty')
->setViewVar('title', $title)
->renderView();
Das zweite Argument von setView() und setLayout() ist eine Fallback-Datei, kein Wrapper-Layout. loadViewStatic() prüft nicht, ob die Datei existiert — bevorzugen Sie setView() / loadView().
2.1 Erstes Rendern
Das Live-Modul Hello World ist das minimale Muster: eine View, zwei Variablen, kein verschachteltes Layout.
app/modules/HelloWorld/views/hello.view.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{ var: $title }}</title>
<link rel="stylesheet" href="/assets/modules/HelloWorld/css/hello.css" />
</head>
<body>
<main>
<h1>{{ var: $title }}</h1>
<p>{{ var: $message }}</p>
</main>
</body>
</html>
Rufen Sie setView('hello') vor jedem setViewVar() auf. Ein späterer Wechsel der View verwirft die bisherige Variablensammlung.
2.2 View-Variablen versus Layout-Variablen
renderView() wertet die kompilierte Vorlage mit der View-Variablensammlung aus. Werte, die nur mit setLayoutVar() gesetzt wurden, erscheinen in dieser Ausgabe nicht. Wenn Sie eine Seite mit renderView() rendern, übergeben Sie jeden Wert, den die View und ihre eingebundenen Layouts benötigen, über setViewVar().
Verwenden Sie setLayoutVar() mit renderLayout(), wenn Sie eine Layout-Datei eigenständig rendern (diese Dokumentationswebsite tut das für Artikelabschnitte).
2.3 Shell-View plus Content-Layout
Geben Sie der Seite eine Shell-View, die {{ content }} enthält, und legen Sie das innere HTML in ein Layout, das mit setLayout() ausgewählt wird. Includes aus der View heraus verwenden weiterhin {{ layout:… }}.
return Renderer::new()
->module('Shop')
->setView('home')
->setLayout('content/welcome')
->setViewVar('title', 'Shop')
->setViewVar('items', $items)
->renderView();
View views/home.view.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{ var: $title }}</title>
<link rel="stylesheet" href="/assets/modules/Shop/css/page.css" />
</head>
<body>
<main>{{ content }}</main>
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/modules/Shop/js/page.js"></script>
</body>
</html>
Layout views/layouts/content/welcome.layout.php:
<h1>{{_ "Welcome" }}</h1>
{{ foreach $items as $item }}
<p>{{ var: $item['title'] }}</p>
{{ /foreach }}
Eine View kann auch ein vollständiges HTML-Dokument ohne setLayout() und ohne {{ content }} sein. Hello World, die Examples-Demos und die Users-Demo tun das alle.
2.4 Modulübergreifende Dateien
Stellen Sie dem Pfad den Namen des anderen Moduls und einen Doppelpunkt voran:
Renderer::new()->module('Checkout')->setView('Shop:home')->renderView();
Die Datei liegt weiterhin unter views/ dieses Moduls (bzw. unter views/layouts/ für Layouts).
2.5 Weitere Render-Methoden
| Methode | Verwendung |
|---|---|
renderView() |
Normale Seite. Wertet mit View-Variablen aus. |
renderLayout() |
Eine Layout-Datei. Wertet mit Layout-Variablen aus. |
renderCode($code, $vars) |
Kompiliert und wertet eine HTML-Zeichenkette aus, die Sie bereits haben. |
loadView($name) |
Liest die View-Datei als Text. Fehlende Datei: "". |
3.1 Werte ausgeben
{{ var: $title }}
{{ var: $user['name'] }}
{{ var:$title }}
Der Compiler macht daraus echo. In der Direktive gibt es kein automatisches Escaping. Anfragedaten aus $request->data() sind bereits gegen XSS geschützt. Wenn Sie ungeschütztes HTML aus PHP übergeben, escapen Sie es im Controller mit htmlspecialchars() vor setViewVar(), oder lassen Sie es geschützt und rufen Sie DotApp::DotApp()->unprotect($html) nur auf, wenn Sie absichtlich Markup rendern.
{{ var: }} akzeptiert keine Ausdrücke, kein ??, kein -> und keine Funktionsaufrufe. Bereiten Sie den Wert im Controller vor.
3.2 Übersetzung
{{_ "Login" }}
{{_ var: $message }}
Verwenden Sie doppelte Anführungszeichen um die Quellzeichenkette. Ein fehlender Schlüssel gibt den Originaltext aus. Laden Sie JSON-Dateien aus dem Modul und setzen Sie die Locale in PHP:
use Dotsystems\App\Parts\Translator;
Translator::loadLocaleFile('Shop:sk_sk.json', 'sk_sk');
Translator::setLocale('sk_sk');
echo Translator::trans('Hello, {{ arg0 }}', $name);
Die Dateien liegen in app/modules/{Module}/translations/{locale}.json. Platzhalter sind {{ arg0 }}, {{ arg1 }}. Es gibt keine Pluralisierung und keine Locale-Fallback-Kette.
3.3 Bedingungen
{{ if isset($user) }}
<p>Signed in</p>
{{ elseif $guest === true }}
<p>Guest</p>
{{ else }}
<p>Unknown</p>
{{ /if }}
Setzen Sie ein Leerzeichen nach {{ vor if, elseif, else und /if. Das schließende Tag ist {{ /if }}, nicht {{ endif }}.
3.4 Schleifen
{{ foreach $items as $item }}
<li>{{ var: $item['title'] }}</li>
{{ /foreach }}
{{ while $i < 5 }}
<p>{{ var: $i }}</p>
{{ /while }}
Die schließenden Tags sind {{ /foreach }} und {{ /while }}. Erhöhen Sie Zähler im Controller oder mit einem kleinen PHP-Block in der Vorlage. Halten Sie Geschäftslogik im Controller.
3.5 Includes und der Content-Slot
{{ content }}
Layout-Tags sind Includes. Sie haben kein schließendes Tag. {{ content }} wird nur gefüllt, wenn Sie setLayout() und anschließend renderView() aufgerufen haben.
3.6 Formulare und Verschlüsselung
<fo-rm method="POST" id="saveForm">
<input type="text" name="title" />
{{ formName(saveItem) }}
<button type="submit">Save</button>
</fo-rm>
<script src="/assets/dotapp/dotapp.js"></script>
{{ formName(saveItem) }}muss zwischen<fo-rm>(oder<form>) und dem zugehörigen schließenden Tag stehen. Das Tag benötigt einmethod-Attribut. Außerhalb dieses Paars lässt der Renderer das Token unverändert.- Bevorzugen Sie
<fo-rm>.dotapp.jswandelt es in ein echtes Formular um und sendet mit CRC. PHP führt weiterhin$request->crcCheck()und anschließend$request->form(…)aus. - Wenn das Formular an die aktuelle Seite sendet, lassen Sie
actionweg und übergeben Sie$request->getPath()als letztes Argument vonform(). {{ CSRF }}gibt ein einfaches Token aus. Verwenden SieformNamefür Anwendungsformulare.
Verschlüsseln Sie Werte in der Vorlage mit einem eigenen Extra-Schlüssel pro Feld:
<option value="{{ enc(Shop.user.id): $u['id'] }}">{{ var: $u['name'] }}</option>
{{ enc: $secret }}
{{ enc(mykey): "literal" }}
{{ enc: "literal" }} verschlüsselt, während die Vorlage kompiliert wird. {{ enc(key): $var }} verschlüsselt, wenn die Seite ausgeführt wird. Entschlüsseln Sie mit demselben Extra-Schlüssel: Crypto::decrypt($cipher, 'Shop.user.id'). Ein Fehlschlag ist === false. Vollständige Formularanleitung: Sichere Formulare.
3.7 Blöcke
Registrieren Sie einen benannten Block in initialize($dotApp) und umschließen Sie anschließend Markup in der View:
Renderer::new()->addBlock('alert', function ($inner, array $args) {
$kind = $args[0] ?? 'info';
return '<div class="alert-' . htmlspecialchars($kind, ENT_QUOTES, 'UTF-8') . '">' . $inner . '</div>';
});
{{ blockerror: }} Undefined callable function ! {{ /blockerror: }}
privateblock speichert ein Fragment als PHP-Objekt, das Sie in derselben Datei klonen können:
<?php $block["row"] = new \Dotsystems\App\Parts\PrivateBlock(base64_decode("Jmx0O2xpJmd0O3t7IHZhcjogJG5hbWUgfX0mbHQ7L2xpJmd0Ow==")); ?>
<?php foreach ($items as $it): ?>
<?php echo $block['row']->set('name', $it['name'])->html(); ?>
<?php endforeach; ?>
Natives PHP in einer Vorlage ist erlaubt, aber die Sandbox entfernt gefährliche Funktionen (eval, exec, system, file_*, curl_*, mail, header, extract, call_user_func*, …). Wenn ein Aufruf nichts tut, liegt es daran. Legen Sie E/A und Abfragen in den Controller.
3.8 Nicht unterstützte Syntax
| Nicht schreiben | Schreiben |
|---|---|
{{ $title }} |
{{ var: $title }} |
{{ endif }} / {{ endforeach }} |
{{ /if }} / {{ /foreach }} |
{{ include 'x' }} in einer PHP-View |
{{ layout:x }} |
| extends / section / yield | renderView() + {{ content }} oder {{ layout: }} |
{{ $x ?? 'd' }} |
Bereiten Sie den Wert im Controller vor |
{{ include path }} existiert nur in der optionalen JavaScript-Vorlagen-Engine (Abschnitt 8), niemals in PHP-Views.
Input-Group-Tags wie {{ InputKeys('register_form') }} und {{ input:text … }} stammen aus Input.php, nicht aus der Kern-Direktiventabelle. Bevorzugen Sie formName für gewöhnliche HTML-Formulare.
Bridge-Attribute ({{ dotbridge:on(click)="…" }}) sind unter DotBridge dokumentiert.
4. Assets
Speichern Sie CSS, JS und Bilder unter app/modules/{Module}/assets/. Das Framework liefert sie aus als:
/assets/modules/{Module}/{path}
<link rel="stylesheet" href="/assets/modules/Shop/css/page.css" />
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/modules/Shop/js/page.js"></script>
Seiten, die <fo-rm> absenden, $dotapp().load() aufrufen oder Bridge verwenden, müssen zuerst /assets/dotapp/dotapp.js laden. Diese URL ist eine Framework-Route. Sie injiziert sitzungsbezogene Schlüssel. Verlinken Sie auf einer öffentlichen Seite keine Rohdatei aus app/parts/js/.
Optionale CSS-Helfer: prepareCss() verkettet und minifiziert in eine Cache-Datei und gibt ein <link>-Tag aus. removeUnusedCss(true) entfernt Selektoren, die nicht als class="…" im HTML vorkommen — es entfernt auch Klassen, die später per JavaScript hinzugefügt werden. Lassen Sie es aus, sofern Sie die Ausgabe nicht geprüft haben. Es gibt keinen integrierten Cache-Busting-Helfer; hängen Sie bei Bedarf selbst ?v= an. Aktivieren Sie den HTML-Seitencache nicht mit useCache(true).
5. Eigene Renderer
Ein eigener Renderer ist ein Callable, der das kompilierte HTML (und, falls vorhanden, die Variablensammlung) entgegennimmt und HTML zurückgibt. Registrieren Sie ihn einmal in initialize($dotApp). Danach läuft er bei jedem Rendern.
use Dotsystems\App\Parts\Renderer;
Renderer::new()->addRenderer('shop.money', function (string $code, array $vars = []): string {
$amount = number_format((float) ($vars['price'] ?? 0), 2);
return str_replace('{{ money }}', $amount, $code);
});
Renderer::add($name, $callable) ist dieselbe Registrierung an der Fassade. getRenderer($name) gibt das Callable oder false zurück. renderWith($name, $code) führt einen Renderer auf einer Zeichenkette aus.
Zu den vom Framework registrierten integrierten Renderern gehören dotapp.block, reactive und input_form_*. Dieses Dokumentationsmodul registriert Docs.code.replace, damit Beispiele innerhalb von <pre><code> escaped werden.
6. Pipeline, Sandbox, Debugging
Ein typischer renderView()-Lauf:
- Verschachtelte
{{ layout: }}/{{ baselayout: }}auflösen (Tiefe ≤ 20). privateblockextrahieren und eigene Renderer ausführen.- HTML aus
setLayout()in{{ content }}einfügen. var/if/foreach/while/enc/ Übersetzung kompilieren.{{ CSRF }}und{{ formName() }}ersetzen.- Bridge-Tags verarbeiten.
- In
RenderingIsolatorauswerten.
Ein Kompilier- oder Auswertungsfehler schreibt ERROR WHILE EVAL: … in die Antwort. Für echte Zeilennummern:
define('__RENDER_TO_FILE__', true);
Kompiliertes PHP wird unter app/runtime/generator/rendering_*.php geschrieben, eingebunden und anschließend gelöscht.
7. Translator-API
| Methode | Ergebnis |
|---|---|
trans($text, ...$args) / t() |
Übersetzte Zeichenkette oder der Originaltext, wenn der Schlüssel fehlt |
setLocale($locale) / getLocale() |
Aktuelle Locale (Standard en_us) |
loadLocaleFile('Module:file.json', $locale) |
Eine fehlende Datei wird ohne Ausnahme übersprungen |
has($key, $locale = null) |
bool — damit erkennen Sie einen fehlenden Schlüssel |
all($locale = null) |
Alle Schlüssel für diese Locale |
Produkttexte, die eine Person sieht (Schaltflächen, leere Zustände, Berechtigungsnamen), müssen wie ausgelieferte UI klingen, nicht wie eine Antwort auf einen Prompt. Schlüssel sind die englische (oder sonstige) Originalzeichenkette, bei der Suche in Kleinbuchstaben.
8. Clientseitige Vorlagen
Das optionale Skript /assets/dotapp/dotapp.template.js ergänzt $dotapp('#box').template('path/to/view', { items: […] }) im Browser. Es versteht {{ var: }}, {{ if }}, {{ foreach }}, {{ block: }} und {{ include partials/header }}. Der Standard-Basispfad ist /app/views/. Laden Sie es nach dotapp.js. Warten Sie auf das Ereignis dotapp-template-ready, wenn das Add-on noch lädt.
PHP-Views erhalten niemals include. Server-HTML bleibt bei Renderer + {{ layout: }}. Nutzen Sie die JS-Engine, wenn Sie eine Liste aus $dotapp().load() ohne vollständiges Seitenrendern aktualisieren. Die Kern-Reaktivität (variable, databind, computed) finden Sie im Reaktivitätsbeispiel. Eigene $dotapp().fn-Widgets stehen im JS-Bibliotheksbeispiel. Die Live-Listendemo ist /documentation/examples/run/lists.
9. Checkliste
- View-Datei:
{name}.view.php. Layout-Datei:views/layouts/{path}.layout.php. Renderer::new()->module('Name')->setView('name')vorsetViewVar().- Behandeln Sie
renderView() === ''als Fehler. - Geben Sie mit
{{ var: $x }}aus. Schließen Sie Zweige mit{{ /if }}/{{ /foreach }}. - Übergeben Sie jeden Wert über
setViewVar(), wenn SierenderView()aufrufen. - Setzen Sie
{{ formName(handler) }}in<fo-rm method="…">und laden Sie/assets/dotapp/dotapp.js. - Halten Sie Abfragen, Authentifizierung und Schreibvorgänge im Controller. Die Vorlagen-Sandbox entfernt unsicheres PHP.