Presenters
We will learn how to write presenters and templates in Nette. After reading you will know:
- how the presenter works
- what are persistent parameters
- how to render a template
We already know that a presenter is a class that represents a specific page of a web application, such as a homepage; product in e-shop; sign-in form; sitemap feed, etc. The application can have 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 class Nette\Application\UI\Presenter, which is suitable for web interfaces and which we will discuss in the rest of this chapter. In a general sense, a presenter is any object that implements the Nette\Application\IPresenter interface.
Life Cycle of Presenter
The job of the presenter is to process the request and return a response (which can be an HTML page, image, redirect, etc.).
So at the beginning is a request. It is not directly an HTTP request, but an Nette\Application\Request object into which the HTTP request was transformed using a router. We usually do not come into contact with this object, because the presenter cleverly delegates the processing of the request to special methods, which we will now see.
Life cycle of presenter
The figure shows a list of methods that are called sequentially from top to bottom, if they exist. None of them need to exist, we can have a completely empty presenter without a single method and build a simple static web on it.
__construct()
The constructor does not belong exactly to the life cycle of the presenter, because it is called at the moment of creating the object. But we mention it because of its importance. The constructor (together with method inject) is used to pass dependencies.
The presenter should not take care of the business logic of the application, write and read from the database, perform
calculations, etc. This is the task for classes from a layer, which we call a model. For example, class
ArticleRepository
may be responsible for loading and saving articles. In order for the presenter to use it, it is passed using dependency injection:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
Immediately after receiving the request, method startup ()
is invoked. You can use it to initialize properties,
check user privileges, etc. It is required to always call the parent::startup()
ancestor.
action<Action>(args...)
Similar to the method render<View>()
. While render<View>()
is intended to prepare data
for a specific template, which is subsequently rendered, in action<Action>()
a request is processed without
following-up template rendering. For example, data is processed, a user is logged in or out, and so on, and then it redirects elsewhere.
It is important that action<Action>()
is called before render<View>()
, so inside it we
can possibly change the next course of life cycle, i.e. change the template that will be rendered and also the method
render<View>()
that will be called, using setView('otherView')
.
The parameters from the request are passed to the method. It is possible and recommended to specify types for the parameters,
e.g. actionShow(int $id, ?string $slug = null)
– if parameter id
is missing or if it is not an
integer, the presenter returns error 404 and terminates the operation.
handle<Signal>(args...)
This method processes the so-called signals, which we will discuss in the chapter about Components. It is intended mainly for components and processing of AJAX requests.
The parameters are passed to the method, as in the case of action<Action>()
, including type checking.
beforeRender()
Method beforeRender
, as the name suggests, is called before each method render<View>()
. Is used
for common template configuration, passing variables for layout and so on.
render<View>(args...)
The place where we prepare the template for subsequent rendering, we pass data to it, etc.
The parameters are passed to the method, as in the case of action<Action>()
, including type checking.
public function renderShow(int $id): void
{
// we obtain data from the model and pass it to the template
$this->template->article = $this->articles->getById($id);
}
afterRender()
Method afterRender
, as the name suggests again, is called after each render<View>()
method. It
is used rather rarely.
shutdown()
It is called at the end of the presenter's life cycle.
Good advice before we move on. As you can see, the presenter can handle more actions/views, i.e. have more methods
render<View>()
. But we recommend designing presenters with one or as few actions as possible.
Sending a Response
The presenter's response is usually rendering the template with the HTML page, but it can also be sending a file, JSON or even redirecting to another page.
At any time during the lifecycle, you can use any of the following methods to send a response and exit the presenter at the same time:
redirect()
,redirectPermanent()
,redirectUrl()
andforward()
redirectserror()
quits presenter due to errorsendJson($data)
quits presenter and sends the data in JSON formatsendTemplate()
quits presenter and immediately renderes the templatesendResponse($response)
quits presenter and sends own responseterminate()
quits presenter without answer
If you do not call any of these methods, the presenter will automatically proceed to render the template. Why? Well, because in 99% of cases we want to draw a template, so the presenter takes this behavior as the default and wants to make our work easier.
Creating Links
Presenter has a method link()
, which is used to create URL links to other presenters. The first parameter is the
target presenter & action, followed by the arguments, which can be passed as array:
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'en']);
In template we create links to other presenters & actions as follows:
<a n:href="Product:show $id">product detail</a>
Simply write the familiar Presenter:action
pair instead of the real URL and include any parameters. The trick is
n:href
, which says that this attribute will be processed by Latte and generates a real URL. In Nette, you don't have
to think about URLs at all, just about presenters and actions.
For more information, see Creating Links.
Redirection
Methods redirect()
and forward()
are used to jump to another presenter, which have a very similar
syntax as the method link().
The forward()
switches to the new presenter immediately without HTTP redirection:
$this->forward('Product:show');
Example of a so-called temporary redirection with HTTP code 302 (or 303, if the current request method is POST):
$this->redirect('Product:show', $id);
To achieve permanent redirection with HTTP code 301 use:
$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, with the default being 302 (or 303, if the current request method is POST):
$this->redirectUrl('https://nette.org');
Redirection immediately terminates the presenter's life cycle by throwing the so-called silent termination exception
Nette\Application\AbortException
.
Before redirection, it is possible to send a flash message, messages that will be displayed in the template after redirection.
Flash Messages
These are messages that usually inform about the result of an operation. An important feature of flash messages is that they are available in the template even after redirection. Even after being displayed, they will remain alive for another 30 seconds – for example, in case the user would unintentionally refresh the page – the message will not be lost.
Just call the flashMessage() method and
presenter will take care of passing the message to the template. The first argument is the text of the message and the second
optional argument is its type (error, warning, info etc.). The method flashMessage()
returns an instance of flash
message, to allow us to add more information.
$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);
In the template, these messages are available in the variable $flashes
as objects stdClass
, which
contain the properties message
(message text), type
(message type) and can contain the already mentioned
user information. We draw them as follows:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Error 404 etc.
When we can't fulfill the request because for example the article we want to display does not exist in the database, we will
throw out the 404 error using method error(?string $message = null, int $httpCode = 404)
, which represents HTTP
error 404:
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 exception
Nette\Application\BadRequestException
, after which Application
passes control to the error-presenter.
Which is a presenter whose job is to display a page informing about the error. The error-preseter is set in application configuration.
Sending JSON
Example of action-method that sends data in JSON format and exits the presenter:
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Request Parameters
The presenter, as well as every component, obtains its parameters from the HTTP request. Their values can be retrieved using
the getParameter($name)
method or getParameters()
. The values are strings or arrays of strings,
essentially raw data obtained directly from the URL.
For added convenience, we recommend making parameters accessible through properties. Simply annotate 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 properties, we suggest specifying the data type (e.g., string
). Nette will then automatically cast the value
based on it. Parameter values can be also validated.
When creating a link, you can directly set the value for the parameters:
<a n:href="Home:default theme: dark">click</a>
Persistent Parameters
Persistent parameters are used to maintain state between different requests. Their value remains the same even after a link is
clicked. Unlike session data, they are passed in the URL. This is completely automatic, so there is no need to explicitly state
them in link()
or n:href
.
Example of use? You have a multilingual application. The actual language is a parameter that needs to be part of the URL at all
times. But it would be incredibly tedious to include it in every link. So you make it a persistent parameter named
lang
and it will carry itself. Cool!
Creating a persistent parameter is extremely easy in Nette. Just create a public property and tag 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 such as 'en'
, then links created using link()
or
n:href
will also contain the lang=en
parameter. And when the link is clicked, it will again be
$this->lang = 'en'
.
For properties, we recommend that you include the data type (e.g. string
) and you can also include a default
value. Parameter values can be validated.
Persistent parameters are passed between all actions of a given presenter by default. To pass them between multiple presenters, you need to define them either:
- in a common ancestor from which the presenters inherit
- in the trait that the presenters use:
trait LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
You can change the value of a persistent parameter when creating a link:
<a n:href="Product:show $id, lang: cs">detail in Czech</a>
Or it can be reset, i.e. removed from the URL. It will then take 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 place into presenters. They can be forms, datagrids, menus, in fact anything that makes sense to use repeatedly.
How are components placed and subsequently used in the presenter? This is explained in chapter Components. You'll even find out what they have to do with Hollywood.
Where Can I Get Some Components? On page Componette you can find some open-source components and other addons for Nette that are made and shared by the community of Nette Framework.
Going Deeper
What we have shown so far in this chapter will probably suffice. The following lines are intended for those who are interested in presenters in depth and want to know 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, as they can easily be overwritten by the user in the URL. For example, this is how we check if
$this->lang
is among the supported languages. A good way to do this is to override the loadState()
method mentioned above:
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // here is set the $this->lang
// follows the user value check:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Save and Restore the Request
The request that the presenter handles is an object Nette\Application\Request and is returned by the
presenter's method getRequest()
.
You can save the current request to a session or restore it from the session and let the presenter execute it again. This is
useful, for example, when a user fills out a form and its login expires. In order not to lose data, before redirecting to the
sign-in page, we save the current request to the session using $reqId = $this->storeRequest()
, which returns an
identifier in the form of a short string and passes it as a parameter to the sign-in presenter.
After sign in, we call the method $this->restoreRequest($reqId)
, which picks up the request from the session
and forwards it to it. The method verifies that the request was created by the same user as now logged in is. If another user logs
in or the key is invalid, it does nothing and the program continues.
See the cookbook How to return to an earlier page.
Canonization
Presenters have one really great feature that improves SEO (optimization of searchability on the Internet). They automatically
prevent the existence of duplicate content at different URLs. If multiple URLs lead to a certain destination, e.g.
/index
and /index?page=1
, the framework designates one of them as the primary (canonical) and redirects
the others to it using HTTP code 301. Thanks to this, search engines do not index pages twice and do not weaken their
page rank.
This process is called canonization. The canonical URL is the URL generated by router, usually the first appropriate route in the collection.
Canonization is on by default and can be turned off via $this->autoCanonicalize = false
.
Redirection does not occur with an AJAX or POST request because it would result in data loss or no SEO added value.
You can also invoke canonization manually using method canonicalize()
, which, like method link()
,
receives the presenter, actions, and parameters as arguments. It creates a link and compares it to the current URL. If it is
different, 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 methods startup()
, beforeRender()
and shutdown()
, which are called as
part of the presenter's life cycle, other functions can be defined to be called automatically. The presenter defines the
so-called events, and you add their handlers to arrays
$onStartup
, $onRender
and $onShutdown
.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
Handlers in array $onStartup
are called just before the method startup()
, then $onRender
between beforeRender()
and render<View>()
and finally $onShutdown
just before
shutdown()
.
Responses
The response returned by the presenter is an object implementing the Nette\Application\Response interface. There are a number of ready-made answers:
- 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 by method sendResponse()
:
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 AJAX requests, limit access to the same origin, and restrict access to forwarding only.
The attribute can be applied to presenter classes as well as individual methods such as 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')]
For details, see How to use the Requires attribute.
HTTP Method Check
In Nette, presenters automatically verify the HTTP method of each incoming request primarily for security reasons. By default,
the methods GET
, POST
, HEAD
, PUT
, DELETE
, PATCH
are
allowed.
If you want to enable additional methods such as OPTIONS
, you can use the #[Requires]
attribute (from
Nette Application v3.2):
#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
In version 3.1, the verification is performed in checkHttpMethod()
, which checks if the method specified in the
request is included in the array $presenter->allowedMethods
. Add a method like this:
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
It's crucial to emphasize that if you enable the OPTIONS
method, you must also properly handle it within your
presenter. This method is often used as a so-called preflight request, which browsers automatically send before the actual request
when it's necessary to determine if the request is allowed from the standpoint of CORS (Cross-Origin Resource Sharing) policy. If
you allow this method but do not implement an appropriate response, it can lead to inconsistencies and potential security
issues.