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() and forward() redirects
  • error() quits presenter due to error
  • sendJson($data) quits presenter and sends the data in JSON format
  • sendTemplate() quits presenter and immediately renderes the template
  • sendResponse($response) quits presenter and sends own response
  • terminate() 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.

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 LangAware
{
	#[Persistent]
	public string $lang;
}

class ProductPresenter extends Nette\Application\UI\Presenter
{
	use LangAware;
}

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:

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

HTTP Method Check

Presenters in Nette automatically verify the HTTP method of every incoming request. The primary reason for this verification is security. The method check is carried out in checkHttpMethod(), which determines if the method specified in the request is included in the $presenter->allowedMethods array. By default, this array contains the items GET, POST, HEAD, PUT, DELETE, PATCH, meaning these methods are allowed.

If you wish to additionally allow the OPTIONS method, it can be achieved in the following manner:

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.

Further Reading

version: 4.0 3.x 2.x