# Views and templates

Views hold your page markup. The Flo Template Engine adds a short syntax for printing values, conditions, loops and forms, and plain PHP works too.

For a complete controller, view, partial, layout and stylesheet working together, follow [the blog example](/docs/2.0/example-blog). Then add [a contact form](/docs/2.0/example-contact-form) and [shared theme markup](/docs/2.0/example-theme).

## Views and layouts

The view for `BlogController::index()` is `views/blog/index.html`. FloCMS renders it, then places the result inside the active layout:

- `templates/<template>/layouts/default.html` for site pages
- `templates/<template>/layouts/admin.html` for `/admin` pages

In the layout, `<?= $data['content'] ?>` prints the rendered view. `APP_TEMPLATE` in `.env` picks the template; files missing from it come from `templates/default/`.

The standard page dispatcher passes only the rendered `$content` to the layout, not every controller variable. Controller data is available in the page view; compute shared layout data independently and pass it explicitly to partials.

## Printing values

```html
<h1>{{ $title }}</h1>
<p>{{ $post['author'] }}</p>
```

`{{ }}` HTML-escapes printable values for text content and quoted HTML attributes. Select a property or array element instead of printing an entire record. HTML escaping does not encode JavaScript or validate URL schemes. To print trusted HTML as it is, use `{!! $html !!}`. In plain PHP, `e($value)` escapes a value.

> [!WARNING]
> Never use `{!! !!}` for anything a visitor typed. It prints raw HTML, which allows cross-site scripting.

## Conditions and loops

```html
@if($posts)
    @foreach($posts as $post)
        <article>{{ $post['title'] }}</article>
    @endforeach
@else
    <p>No posts yet.</p>
@endif
```

| Syntax | Meaning |
|---|---|
| `@if(...)`, `@elseif(...)`, `@else`, `@endif` | Conditions |
| `@foreach(...)`, `@endforeach` | Loop over an array |
| `@for(...)`, `@endfor`, `@while(...)`, `@endwhile` | Other loops |
| `@forelse($items)` ... `@empty` ... `@endforelse` | Loop with a fallback when the array is empty (items are `$key` and `$value`) |
| `@php` ... `@endphp` | A block of plain PHP |
| `@lang('key')` | A translation from `lang/<language>.php` |
| `@config('key')`, `@env('KEY')` | A config or `.env` value |
| `@csrf` | The CSRF field for a form |

Expressions can contain function calls, nested arrays and quoted strings:

```html
@if(count($posts) > 0)
    @foreach(array_slice($posts, 0, 3) as $post)
        <p>@lang('blog.by', ['name' => strtoupper($post['author'])])</p>
    @endforeach
@endif
```

> [!NOTE]
> This needs flocms-core 2.2.2 or newer. In 2.2.1 the expression ended at the first `)`, so `@if(count($posts) > 0)` compiled to broken PHP, and `@forelse` didn't work.

The engine replaces these words anywhere in a view or layout, also inside HTML comments and JavaScript. To show a literal `@` before one of them, write it as `&#64;`.

## Forms

Every form that posts data needs a CSRF token. `@csrf` prints the hidden field:

```html
<form method="post" action="">
    @csrf
    <input type="email" name="email" required>
    <button type="submit">Subscribe</button>
</form>
```

For JavaScript requests, the admin layout includes a `csrf-token` meta tag and `js/csrf.js`, which sends the token as the `X-CSRF-TOKEN` header with fetch and jQuery requests. See [sessions and CSRF](/docs/2.0/session#csrf-protection).

## Partials

Split repeated markup into partials in `templates/<template>/partials/` and render them with data:

```php
<?= render_partial('header.html', ['title' => $title]) ?>
```

Partials use the same template syntax as views.

They do not automatically inherit local variables from the calling template. Pass the values they need in the second argument of `render_partial()`.

## Assets

Link to CSS, JavaScript and images in the active theme with `template_asset()`. It falls back to the `default` theme when the file isn't in yours:

```html
<link rel="stylesheet" href="<?= template_asset('css/style.css') ?>">
```

## Translations

`@lang('key')` prints the text for the current language from `lang/<language>.php`, and `__('key')` returns it in PHP. See [localization](/docs/2.0/localization) for placeholders, right-to-left languages and adding languages.

## Compiled templates

Templates are compiled to PHP in `views/cache/` the first time they're used, and again whenever they change. The web server must be able to write to that folder. `php flo optimize` compiles everything ahead of time, which is handy after a deployment, and `php flo view:clear` empties the cache.

`Config::set('view.cache_path', ...)` moves the cache to another folder. When the folder can't be written, templates still work: they are compiled on every request instead, which is slower.

Set `Config::set('view.cache', false)` to disable compiled-file caching explicitly. Use the boolean `false`; the string `'false'` does not disable it. Rendering then compiles and evaluates the template in memory. Cache files incorporate the template's path, modification time, size and compiler version.

## Rendering a file directly

Render a trusted template file without the page controller's view/layout selection:

```php
use FloCMS\Core\TemplateEngine;

$html = TemplateEngine::renderFile(ROOT . '/templates/default/partials/header.html', ['title' => 'My site']);
```

The result is a string. Data is available as template variables and `$data`, and the usual caching rules apply. `TemplateEngine::compiledPath($file)` returns the compiled path, or `null` when caching is disabled/unavailable; `cacheDirectory()` returns the writable cache directory or `null`.

`render_partial()` handles active-template lookup for you. `template_partial()` returns its path without rendering it. See [helpers](/docs/2.0/helpers#theme-assets-and-partials).

