Zum Inhalt springen

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 ein method-Attribut. Außerhalb dieses Paars lässt der Renderer das Token unverändert.
  • Bevorzugen Sie <fo-rm>. dotapp.js wandelt 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 action weg und übergeben Sie $request->getPath() als letztes Argument von form().
  • {{ CSRF }} gibt ein einfaches Token aus. Verwenden Sie formName fü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:

  1. Verschachtelte {{ layout: }} / {{ baselayout: }} auflösen (Tiefe ≤ 20).
  2. privateblock extrahieren und eigene Renderer ausführen.
  3. HTML aus setLayout() in {{ content }} einfügen.
  4. var / if / foreach / while / enc / Übersetzung kompilieren.
  5. {{ CSRF }} und {{ formName() }} ersetzen.
  6. Bridge-Tags verarbeiten.
  7. In RenderingIsolator auswerten.

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') vor setViewVar().
  • 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 Sie renderView() 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.