Presenters
We will explore how presenters and templates are written in Nette. After reading, you will understand:
- how presenters work
- what persistent parameters are
- how templates are rendered
We already know that a presenter is a class representing a specific page of a web application, such as the homepage, a product in an e-shop, a login form, a sitemap feed, etc. An application can have anywhere from one to thousands of presenters. In other frameworks, they are also known as controllers.
Usually, the term presenter refers to a descendant of the Nette\Application\UI\Presenter class, which is suitable for generating web interfaces and will be the focus of the rest of this chapter. In a general sense, a presenter is any object implementing the Nette\Application\IPresenter interface.
Presenter Life Cycle
The presenter's task is to handle a request and return a response (which could be an HTML page, an image, a redirect, etc.).
So, initially, a request is passed to it. This isn't the direct HTTP request, but a Nette\Application\Request object, into which the HTTP request was transformed with the help of the router. We usually don't interact directly with this object, as the presenter cleverly delegates request processing to other methods, which we will now explore.
The diagram shows a list of methods that are called sequentially from top to bottom, if they exist. None of them are mandatory; you can have a completely empty presenter without a single method and build a simple static website upon it.
__construct()
The constructor doesn't strictly belong to the presenter's life cycle, as it's called at the moment the object is created. However, we mention it due to its importance. The constructor (along with the inject method) is used for passing dependencies.
The presenter should not handle the application's business logic, write to or read from the database, perform calculations,
etc. That's the responsibility of classes in the layer we call the model. For example, an ArticleRepository
class
might be responsible for loading and saving articles. For the presenter to work with it, it needs to have it passed via dependency injection:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
Immediately after receiving the request, the startup()
method is invoked. You can use it to initialize properties,
check user permissions, etc. It is required that this method always calls its parent: parent::startup()
.
action<Action>(args...)
Similar to the render<View>()
method. While render<View>()
is intended to prepare data
for a specific template that will subsequently be rendered, action<Action>()
processes a request without
necessarily rendering a template afterwards. For example, it might process data, log a user in or out, and so on, and then redirect elsewhere.
It's important that action<Action>()
is called before render<View>()
. This allows
us to potentially change the course of the request within the action method, for instance, by changing the template that will be
rendered or even the render<View>()
method that will be called, using setView('otherView')
.
Parameters from the request are passed to the method. It's possible and recommended to specify types for these parameters,
e.g., actionShow(int $id, ?string $slug = null)
. If the id
parameter is missing or is not an integer,
the presenter returns a 404 error and terminates.
handle<Signal>(args...)
This method processes so-called signals, which we'll learn about in the chapter dedicated to components. It's primarily intended for components and handling AJAX requests.
Parameters from the request are passed to the method, just like with action<Action>()
, including type
checking.
beforeRender()
The beforeRender
method, as its name suggests, is called before every render<View>()
method.
It's used for common template configuration, passing variables to the layout, and similar tasks.
render<View>(args...)
This is where we prepare the template for subsequent rendering, pass data to it, etc.
Parameters from the request are passed to the method, just like with action<Action>()
, including type
checking.
public function renderShow(int $id): void
{
// obtain data from the model and pass it to the template
$this->template->article = $this->articles->getById($id);
}
afterRender()
The afterRender
method, as the name suggests again, is called after every render<View>()
method. It's used rather rarely.
shutdown()
Called at the end of the presenter's life cycle.
A piece of advice before we continue: As you can see, a presenter can handle multiple actions/views, meaning it can
have multiple render<View>()
methods. However, we recommend designing presenters with one or as few actions as
possible.
Sending a Response
The presenter's response is typically rendering a template into an HTML page, but it can also be sending a file, JSON, or even redirecting to another page.
At any point during the life cycle, we can use one of the following methods to send a response and simultaneously terminate the presenter:
redirect()
,redirectPermanent()
,redirectUrl()
, andforward()
perform a redirecterror()
terminates the presenter due to an errorsendJson($data)
terminates the presenter and sends data in JSON formatsendTemplate()
terminates the presenter and immediately renders the templatesendResponse($response)
terminates the presenter and sends a custom responseterminate()
terminates the presenter without a response
If you don't call any of these methods, the presenter automatically proceeds to render the template. Why? Because in 99% of cases, we want to render a template, so the presenter adopts this behavior as the default to simplify our work.
Creating Links
The presenter has a link()
method used to create URL links to other presenters. The first parameter is the target
presenter & action, followed by arguments, which can be passed as an array:
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'en']);
In the template, links to other presenters & actions are created like this:
<a n:href="Product:show $id">product detail</a>
Simply write the familiar Presenter:action
pair instead of the actual URL and include any necessary parameters.
The trick lies in n:href
, which tells Latte to process this attribute and generate the real URL. In Nette, you don't
need to think about URLs at all, just about presenters and actions.
You can find more information in the chapter Creating URL Links.
Redirection
The redirect()
and forward()
methods are used to switch to another presenter. They have a very
similar syntax to the link() method.
The forward()
method switches to the new presenter immediately without an HTTP redirect:
$this->forward('Product:show');
Example of a temporary redirect with HTTP code 302 (or 303 if the current request method is POST):
$this->redirect('Product:show', $id);
To achieve a permanent redirect with HTTP code 301, use this:
$this->redirectPermanent('Product:show', $id);
You can redirect to another URL outside the application using the redirectUrl()
method. The HTTP code can be
specified as the second parameter; the default is 302 (or 303 if the current request method is POST):
$this->redirectUrl('https://nette.org');
Redirection immediately terminates the presenter's activity by throwing the so-called silent termination exception,
Nette\Application\AbortException
.
Before redirection, it's possible to send flash messages, i.e., messages that will be displayed in the template after redirection.
Flash Messages
These are messages typically informing about the result of some operation. An important feature of flash messages is that they remain available in the template even after redirection. Once displayed, they stay active for an additional 30 seconds – for instance, if the user refreshes the page due to a transmission error, the message won't disappear immediately.
Simply call the flashMessage() method,
and the presenter handles passing it to the template. The first parameter is the message text, and the optional second parameter
is its type (e.g., error, warning, info). The flashMessage()
method returns an instance of the flash message,
allowing additional information to be added.
$this->flashMessage('The item has been deleted.');
$this->redirect(/* ... */); // and redirect
In the template, these messages are available in the $flashes
variable as stdClass
objects containing
the properties message
(the message text), type
(the message type), and potentially the user-added
information mentioned earlier. We render them like this:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Error 404 etc.
If the request cannot be fulfilled, for example, because the article we want to display doesn't exist in the database, we throw
a 404 error using the error(?string $message = null, int $httpCode = 404)
method.
public function renderShow(int $id): void
{
$article = $this->articles->getById($id);
if (!$article) {
$this->error();
}
// ...
}
The HTTP error code can be passed as the second parameter; the default is 404. The method works by throwing a
Nette\Application\BadRequestException
, after which the Application
passes control to the error
presenter. This is a presenter whose task is to display a page informing about the error that occurred. The error presenter is
configured in the application configuration.
Sending JSON
Example of an action method that sends data in JSON format and terminates the presenter:
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Request Parameters
The presenter, and also each component, obtains its parameters from the HTTP request. You can retrieve their values using the
getParameter($name)
or getParameters()
methods. The values are strings or arrays of strings, essentially
raw data obtained directly from the URL.
For greater convenience, we recommend accessing parameters via properties. Simply mark them with the #[Parameter]
attribute:
use Nette\Application\Attributes\Parameter; // this line is important
class HomePresenter extends Nette\Application\UI\Presenter
{
#[Parameter]
public string $theme; // must be public
}
For the property, we recommend specifying the data type (e.g., string
), and Nette will automatically cast the
value accordingly. Parameter values can also be validated.
When creating a link, you can set the parameter's value directly:
<a n:href="Home:default theme: dark">click</a>
Persistent Parameters
Persistent parameters are used to maintain state across different requests. Their value remains the same even after clicking a
link. Unlike session data, they are transmitted in the URL. And this happens completely automatically, so there's no need to
explicitly include them in link()
or n:href
.
An example use case? Imagine you have a multilingual application. The current language is a parameter that must always be part
of the URL. But it would be incredibly tedious to include it in every link. So, you make it a persistent parameter
lang
, and it will be carried along automatically. Neat!
Creating a persistent parameter in Nette is extremely simple. Just create a public property and mark it with the attribute:
(previously, /** @persistent */
was used)
use Nette\Application\Attributes\Persistent; // this line is important
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang; // must be public
}
If $this->lang
has a value like 'en'
, then links created using link()
or
n:href
will also contain the parameter lang=en
. And after clicking the link, $this->lang
will again be 'en'
.
For the property, we recommend specifying the data type (e.g., string
), and you can also provide a default value.
Parameter values can be validated.
Persistent parameters are typically transferred between all actions of a given presenter. To transfer them across multiple presenters as well, they need to be defined either:
- in a common ancestor from which the presenters inherit
- or in a trait that the presenters use:
trait LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
When creating a link, the value of a persistent parameter can be changed:
<a n:href="Product:show $id, lang: cs">detail in Czech</a>
Alternatively, it can be reset, i.e., removed from the URL. It will then assume its default value:
<a n:href="Product:show $id, lang: null">click</a>
Interactive Components
Presenters have a built-in component system. Components are separate reusable units that we embed into presenters. They can be forms, datagrids, menus, essentially anything that makes sense to use repeatedly.
How are components embedded into presenters and subsequently used? You'll learn this in the Components chapter. You'll even find out what they have in common with Hollywood.
And where can I get components? On Componette, you'll find open-source components and many other add-ons for Nette, contributed by volunteers from the framework community.
Going Deeper
What we've covered so far in this chapter will likely be sufficient for most uses. The following sections are intended for those interested in delving deeper into presenters and wanting to know absolutely everything.
Validation of Parameters
The values of Request-Parameters and Persistent-Parameters received from URLs are written to properties by the
loadState()
method. It also checks if the data type specified in the property matches, otherwise it will respond with
a 404 error and the page will not be displayed.
Never blindly trust parameters received from the URL, as they can easily be overwritten by the user. For example, this is how
we would verify if the language $this->lang
is among the supported ones. A suitable way to do this is by
overriding the aforementioned loadState()
method:
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // $this->lang is set here
// followed by custom value check:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Save and Restore the Request
The request handled by the presenter is a Nette\Application\Request object, returned by
the presenter's getRequest()
method.
The current request can be saved to the session or, conversely, restored from it and have the presenter execute it again. This
is useful, for example, when a user is filling out a form and their login session expires. To avoid data loss, before redirecting
to the login page, we save the current request to the session using $reqId = $this->storeRequest()
. This returns
its identifier as a short string, which we then pass as a parameter to the login presenter.
After logging in, we call the $this->restoreRequest($reqId)
method, which retrieves the request from the
session and forwards to it. The method verifies that the request was created by the same user who is now logged in. If a different
user logs in or the key is invalid, it does nothing, and the program continues as usual.
See the guide How to Return to a Previous Page.
Canonization
Presenters have a truly excellent feature that contributes to better SEO (Search Engine Optimization). They automatically
prevent the existence of duplicate content at different URLs. If multiple URLs lead to a specific destination, e.g.,
/index
and /index?page=1
, the framework designates one of them as primary (canonical) and redirects the
others to it using HTTP code 301. Thanks to this, search engines don't index your pages twice and dilute their page rank.
This process is called canonization. The canonical URL is the one generated by the router, typically the first matching route in the collection.
Canonization is enabled by default and can be disabled via $this->autoCanonicalize = false
.
Redirection does not occur during AJAX or POST requests, as this could lead to data loss or would offer no added SEO value.
You can also trigger canonization manually using the canonicalize()
method. Similar to the link()
method, you pass it the presenter, action, and parameters. It generates a link and compares it with the current URL address. If
they differ, it redirects to the generated link.
public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// redirects if $slug is different from $realSlug
$this->canonicalize('Product:show', [$id, $realSlug]);
}
Events
In addition to the startup()
, beforeRender()
, and shutdown()
methods, which are called
as part of the presenter's life cycle, other functions can be defined to be called automatically. The presenter defines so-called
events, and you add their handlers to the $onStartup
,
$onRender
, and $onShutdown
arrays.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
Handlers in the $onStartup
array are called just before the startup()
method, $onRender
handlers between beforeRender()
and render<View>()
, and finally $onShutdown
handlers
just before shutdown()
.
Responses
The response returned by the presenter is an object implementing the Nette\Application\Response interface. Several pre-built responses are available:
- Nette\Application\Responses\CallbackResponse – sends a callback
- Nette\Application\Responses\FileResponse – sends the file
- Nette\Application\Responses\ForwardResponse – forward()
- Nette\Application\Responses\JsonResponse – sends JSON
- Nette\Application\Responses\RedirectResponse – redirect
- Nette\Application\Responses\TextResponse – sends text
- Nette\Application\Responses\VoidResponse – blank response
Responses are sent using the sendResponse()
method:
use Nette\Application\Responses;
// Plain text
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));
// Sends a file
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Sends a callback
$callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) {
if ($httpResponse->getHeader('Content-Type') === 'text/html') {
echo '<h1>Hello</h1>';
}
};
$this->sendResponse(new Responses\CallbackResponse($callback));
Access Restriction Using #[Requires]
The #[Requires]
attribute provides advanced options for restricting access to presenters and their methods. It can
be used to specify HTTP methods, require an AJAX request, restrict to the same origin, and allow access only via forwarding. The
attribute can be applied both to presenter classes and to individual methods like action<Action>()
,
render<View>()
, handle<Signal>()
, and createComponent<Name>()
.
You can specify these restrictions:
- on HTTP methods:
#[Requires(methods: ['GET', 'POST'])]
- requiring an AJAX request:
#[Requires(ajax: true)]
- access only from the same origin:
#[Requires(sameOrigin: true)]
- access only via forwarding:
#[Requires(forward: true)]
- restrictions on specific actions:
#[Requires(actions: 'default')]
Details can be found in the guide How to Use the Requires Attribute.
HTTP Method Check
Presenters in Nette automatically verify the HTTP method of every incoming request, primarily for security reasons. By default,
the methods GET
, POST
, HEAD
, PUT
, DELETE
, PATCH
are
allowed.
If you want to additionally allow, for example, the OPTIONS
method, use the #[Requires]
attribute
(since Nette Application v3.2):
#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
In version 3.1, verification is performed in checkHttpMethod()
, which checks if the method specified in the
request is included in the $presenter->allowedMethods
array. Add a method like this:
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
It's important to emphasize that if you enable the OPTIONS
method, you must subsequently handle it appropriately
within your presenter. This method is often used as a so-called preflight request, which the browser automatically sends before
the actual request when it's necessary to determine if the request is permissible according to the CORS (Cross-Origin Resource
Sharing) policy. If you enable the method but don't implement the correct response, it can lead to inconsistencies and potential
security problems.