Skip to content

AI blog · DotApp PHP Framework 2.0

How to render views and layouts in DotApp PHP Framework

Pages are built with Renderer::new()->module()->setView()->setViewVar()->renderView(). The view is the outer file. The layout is inserted at the {{ content }} token. Without that token the layout is discarded. Print values with {{ var: $title }} only — {{ $title }} is not a directive. A missing file logs a warning and returns "".

Common mistakes

Wrong Right
Treat the layout as an outer shell around the view View = document. Layout HTML replaces {{ content }} inside that view.
Call setLayout() on a view that has no content token The layout is discarded. Put {{ content }} in the view, or skip setLayout.
{{ $title }} or expressions inside the braces {{ var: $title }} only. Prepare values in the controller.
setLayoutVar('title', ...) then renderView() renderView() sees view vars only. Use setViewVar.
Assume a missing template throws You get "" and a log warning. Check the string before you return it.
Link a raw file under app/parts/js/ Pages that post or call PHP load /assets/dotapp/dotapp.js.

The Renderer chain


$html = Renderer::new()
    ->module('Shop')
    ->setView('home', 'fallback/empty')
    ->setLayout('content/welcome', 'content/empty')
    ->setViewVar('title', 'Shop')
    ->setViewVar('prefix', Config::module('Shop', 'prefix'))
    ->renderView();

if ($html === '') {
    Logger::use()->error('Shop home view produced empty output');
    return new Response(500, 'Template error');
}
return $html;
    
Method Returns Notes
Renderer::new() Renderer Named instances are singletons. There is no setViewVars() plural.
module('Shop') $this Sets view and layout directories for this module.
setView($name, $fallback = null) $this File: views/{name}.view.php. Cross-module: Shop:home.
setLayout($name, $fallback = null) $this File: views/layouts/{name}.layout.php.
setViewVar / setLayoutVar $this Separate bags. They do not bleed into each other.
renderView() HTML string Eval sees view vars only.
renderLayout() HTML string Eval sees layout vars only. The view is ignored.
getViewVar($k) value or "" Missing key is an empty string, not null.

Files: view app/modules/Shop/views/{name}.view.php, layout app/modules/Shop/views/layouts/{path}.layout.php. Full directive list: official templates documentation.

View is outer, layout fills the slot

renderView() loads the .view.php. If you also called setLayout(), the generated layout HTML replaces the literal {{ content }} token in that view. The layout does not wrap the view. Without the token, the layout is discarded. Without setLayout(), the token stays in the HTML as text.

Three valid ways:

  1. Automatic slot — view is the page (doctype, head, body); layout is the middle at {{ content }}.
  2. Layout onlysetLayoutVar + renderLayout() for a fragment, AJAX row, or email block.
  3. Inject a string — render a layout to a variable, then setViewVar('navbar', $nav) and print it with {{ var: $navbar }}.

{{ layout:partials/header }} is a plain include (no closer). It is not wrapping and it is not the content token. On renderLayout() the content token is not filled — nest with layout includes instead.

Missing files fail silently

Situation Result
View file missing Warning in the log, render returns "" — no exception
Layout file missing Warning in the log, ""
loadViewStatic($view) missing No existence check — PHP include error
getViewVar('missing') ""

Always pass the fallback argument and test $html === '' before you treat the page as rendered. Do not enable useCache(true) on the renderer.

Printing values

Output is {{ var: $title }}, {{ var: $user['name'] }}, or {{ var:$title }}. It compiles to echo with no auto-escaping. No expressions, ??, ->, or function calls inside var:. {{ $title }} is not a directive — it will not print the variable. Escape in PHP before you pass HTML-sensitive strings into setViewVar.


{{ if isset($user) }}
{{ elseif $guest === true }}
{{ else }}
{{ /if }}

{{ foreach $items as $item }}
  <li>{{ var: $item['title'] }}</li>
{{ /foreach }}
    

Space required after {{ before if. Closers are {{ /if }} and {{ /foreach }}. Conditionals and loops belong in the template; business logic stays in the controller.

Assets and translations

Store files under app/modules/Shop/assets/.... They are served at /assets/modules/Shop/....


<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>
    

Translation in a template: {{_ "Send" }} or {{_ var: $message }} (double quotes only). A missing key returns the original text. There is no pluralization and no locale fallback chain. PHP: Translator::loadLocaleFile('Shop:sk_sk.json', 'sk_sk'), then Translator::trans('Hello, {{ arg0 }}', $name). Files live in app/modules/Shop/translations/{locale}.json. Secure forms use <fo-rm> plus {{ formName(saveItem) }} between the tags: How to create secure forms in DotApp PHP Framework.

Complete home view and controller

View file: app/modules/Shop/views/home.view.php. Layout file: app/modules/Shop/views/layouts/content/welcome.layout.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>
  <header>{{ var: $navbar }}</header>
  <h1>{{ var: $title }}</h1>
  <p>{{_ "Welcome" }}</p>
  {{ content }}
  <script src="/assets/dotapp/dotapp.js"></script>
</body>
</html>
    

<p>Prefix: {{ var: $prefix }}</p>
<p>{{_ "Catalog" }}</p>
    

On the renderView() path, {{ var: $prefix }} inside the layout file is filled from view vars after the content token is replaced. Use setLayoutVar only with renderLayout() (the navbar fragment above).


<?php
namespace Dotsystems\App\Modules\Shop\Controllers;

use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\Logger;
use Dotsystems\App\Parts\Renderer;
use Dotsystems\App\Parts\Response;

class Home extends \Dotsystems\App\Parts\Controller
{
    public static function index($request)
    {
        $nav = Renderer::new()
            ->module('Shop')
            ->setLayout('partials/nav')
            ->setLayoutVar('active', 'home')
            ->renderLayout();

        $html = Renderer::new()
            ->module('Shop')
            ->setView('home', 'fallback/empty')
            ->setLayout('content/welcome', 'content/empty')
            ->setViewVar('title', 'Shop')
            ->setViewVar('prefix', Config::module('Shop', 'prefix'))
            ->setViewVar('navbar', $nav)
            ->renderView();

        if ($html === '') {
            Logger::use()->error('Shop home view produced empty output');
            return new Response(500, 'Template error');
        }
        return $html;
    }
}
    

Route: Router::get($p . '/', 'Shop:Home@index!', Router::STATIC_ROUTE). For an AJAX table row, skip the view: setLayout('partials/item-row')->setLayoutVar('item', $row)->renderLayout().

FAQ

Why is my title blank when I wrote braces around $title?

{{ $title }} is not a directive. The printer is {{ var: $title }}. Also confirm you used setViewVar on a renderView() path, not setLayoutVar.

The layout never appears

The view is missing the {{ content }} token, or you called renderLayout() (that path does not fill the token), or the layout file name is wrong and you got "".

Can I put PHP in the template?

Isolated eval strips dangerous functions. If a call “does nothing”, that is why. Keep queries and auth in the controller. For syntax errors, define('__RENDER_TO_FILE__', true); writes compiled PHP under app/runtime/generator/ with real line numbers.

Where does formName go?

Between <fo-rm method="POST" ...> and </fo-rm>. After the closing tag the directive is left unchanged — a silent failure. Full form walkthrough: secure forms.

How do I cache-bust CSS?

There is no built-in helper. Append ?v= yourself. prepareCss() concatenates and minifies but echoes a <link> tag — it is not a string return.

Are printed vars escaped?

No. Escape in the controller (or pass already-safe HTML that you built yourself, such as a rendered navbar string).

See also