Views
When the server returns finished HTML — an admin panel, a page for a crawler, an
email — Winter renders it from ordinary PHP templates through ResponseView. There
are layouts, partials and a few helpers; data reaches the template as variables.
What views are and why
A view is a template the response HTML is assembled from.
The problem. Building markup in a controller by concatenating strings is hard to read and easy to break: HTML mixes with logic, escaping gets forgotten, and the page shell has to be repeated in every method — where it starts diverging with the very first edit.
The solution. Keep markup in separate PHP files and return a ResponseView from
the controller with a template name and data. The shell moves into a layout;
repeated fragments into partials.
Winter is API-first
If your application returns JSON you do not need views: that is ResponseEntity —
see Responses. This page is about the case where the
server assembles HTML.
Two ways to render
The difference between them is whether there is a shared page shell.
use Flytachi\Winter\Kernel\Http\Response\ResponseView;
// one template, as is
return ResponseView::view('user/profile', ['user' => $user]);
// a template nested inside a layout
return ResponseView::render('layouts/main', 'user/profile', ['user' => $user]);In render() the layout comes first and the resource — the page itself —
second. The order is easy to mix up and the error will merely say a file was not
found, so remember it as outside-in.
| What | What it is | Where it usually lives |
|---|---|---|
| Resource | The page: the content the request came for | views/user/profile.php |
| Layout | The shell: <html>, header, footer, stylesheet links |
views/layouts/main.php |
| Partial | A fragment embedded into others | views/partial/nav.php |
There is no technical difference between them — all three are ordinary .php files
under one root; only their role differs.
A typical page-serving controller:
#[RequestMapping('cabinet')]
class PageController extends Controller
{
#[Autowired] private UserService $service;
#[GetMapping('profile')]
public function profile(): ResponseView
{
return ResponseView::render('layouts/main', 'user/profile', [
'title' => 'Profile',
'user' => $this->service->current(),
]);
}
}Where templates live
By default in a views directory inside the project’s resources, that is
resources/views. Nothing needs configuring: the name user/profile becomes
resources/views/user/profile.php, the extension is appended for you.
The root can be changed once, at application startup:
ResponseView::setBasePath(__DIR__ . '/theme');That is rarely needed — for instance when a template theme ships as its own package.
Templates are not scanned — and that is correct
resources/ is deliberately excluded from the class scan: a template is a PHP file,
and the scanner, finding a class declaration in it, would load it at boot — that is,
execute the markup. Keep templates in resources/ and classes outside it.
Template data
Every key of the array you pass becomes a variable in the template:
<h1><?= htmlspecialchars($user->name) ?></h1>
<p>Joined: <?= $user->createdAt->format('d.m.Y') ?></p>The same data is visible in the layout and in partials — you do not have to pass it onwards by hand.
Alongside them there is always $data, the whole array. Useful when the keys are not
known in advance, or when they have to be handed on in bulk:
<?php foreach ($data as $key => $value): ?>
<li><?= htmlspecialchars($key) ?></li>
<?php endforeach; ?>The name data is taken
A data key never reaches the template: the $data variable always means the whole
array. Every other name is free — path, content and title included.
Escaping is on you
Winter escapes nothing: a template is plain PHP, and <?= $var ?> prints the value
as it is. Wrap anything that came from a user in htmlspecialchars().
Template helpers
Four global functions are available inside templates.
| Function | What it does |
|---|---|
wrContent() |
Prints the rendered resource — called in the layout |
wrImport('partial/nav') |
Includes another template right here |
wrData('title') |
The value by key; with no argument, the whole data array |
wrIsActiveLink('/cabinet') |
Returns a CSS class when this is the current address |
A complete layout looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= htmlspecialchars(wrData('title') ?? 'Cabinet') ?></title>
</head>
<body>
<?php wrImport('partial/nav'); ?>
<main>
<?php wrContent(); ?>
</main>
</body>
</html>wrContent() is where the page goes. Without that call the layout renders empty:
the resource is built before the layout, and there is nothing else to insert it.
wrData() and a plain variable are the same value; the function is handy where the
key may be absent, because it returns null instead of an undefined-variable error.
Highlighting the current menu item
wrIsActiveLink() compares its argument with the path of the current request and
returns one of two strings. The match is exact, but the query string is ignored: on
/cabinet/orders?page=2 the /cabinet/orders item stays highlighted.
<nav>
<a class="<?= wrIsActiveLink('/cabinet/profile') ?>" href="/cabinet/profile">Profile</a>
<a class="<?= wrIsActiveLink('/cabinet/orders') ?>" href="/cabinet/orders">Orders</a>
</nav>By default it returns active on a match and an empty string otherwise; both
classes are overridden by the second and third arguments. The first argument also
accepts an array of addresses — then the item counts as active on any of them:
wrIsActiveLink(['/cabinet/orders', '/cabinet/orders/archive'], 'is-current', 'is-muted')Status code and headers
ResponseView is a builder like every other response: the third argument is the
HTTP status, headers are chained.
use Flytachi\Winter\Base\HttpCode;
return ResponseView::view('errors/404', ['path' => $path], HttpCode::NOT_FOUND)
->header('Cache-Control', 'no-store');The response always goes out with Content-Type: text/html; charset=utf-8.
Next
- Responses —
ResponseEntity, files and your ownSendable - Controllers — what a handler returns
- Error handling — an HTML error page
- Localization — translations for template text