Presenters

We will learn how to write presenters and templates in Nette. After reading you will know:

  • how the presenters 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 exists. 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 the importance, because is used to pass the 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
{
	/** @var ArticleRepository */
	private $articles;

	public function __construct(ArticleRepository $articles)
	{
		$this->articles = $articles;
	}
}

startup()

Immediately after receiving the request, method startup () is invoked. You can use it to initialize properties, checks 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 follow-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, ie. 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, eg 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, ie 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 can be an HTML page, sending a file, JSON or redirecting to another page.

At any time during the lifecycle, we can use one of the following methods to send a response and terminate the presenter:

  • 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

Example of sending JSON:

public function actionData(): void
{
	$data = ['hello' => 'nette'];
	$this->sendJson($data);
}

Something important now: if we don't explicitly say what response should presenter send, the answer will be HTML template rendering. 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.

Template Rendering

Nette uses the Latte template system. Latte is used because it is the most secure template system for PHP, and at the same time the most intuitive system. You don't have to learn much new, you just need to know PHP and a few Latte tags.

It is usual that the page is completed from the layout template + the action template. This is what a layout template might look like, notice the blocks {block} and tag {include}:

<!DOCTYPE html>
<html>
<head>
	<title>{block title}My App{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>

And this might be the action template:

{block title}Homepage{/block}

{block content}
<h1>Homepage</h1>
....
{/block}

It defines block content, which is inserted in place of {include content} in the layout, and also re-defines block title, which overwrites {block title} in the layout. Try to imagine the result.

The path to the templates is deduced according to simple logic. In the case of presenter Product and action show, it tries to see if one of these template files exists relative to the directory where presenter class is located:

  • templates/Product/show.latte
  • templates/Product.show.latte

If it does not find the template, the response is error 404.

It will also try to find the layout in these locations:

  • templates/Product/@layout.latte
  • templates/Product.@layout.latte
  • templates/@layout.latte layout common to multiple presenters

We pass variables to the template by writing them to $this->template. For example, we will create a variable $article as follows:

$this->template->article = $this->articles->getById($id);

Presenters and components pass several useful variables to templates automatically:

  • $basePath is an absolute URL path to root dir (for example /CD-collection)
  • $baseUrl is an absolute URL to root dir (for example http://localhost/CD-collection)
  • $user is an object representing the user
  • $presenter is the current presenter
  • $control is the current component or presenter
  • $flashes list of messages sent by method flashMessage()

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', $productId);

$url = $this->link('Product:show', [$productId, 'lang' => 'en']);

In template we create a links to other presenters & actions as follows:

<a n:href="Product:show $productId">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 temporary redirection with HTTP code 302 or 303:

$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 with the redirectUrl() method:

$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 three 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.

Persistent Parameters

Persistent parameters are transferred automatically in links. This means that we do not have to explicitly specify them in every link() or n:href in the template, but they will still be transferred.

If your application has multiple language versions, then the current language is a parameter that must always be part of the URL. And it would be incredibly tiring to mention it in every link. That's not necessary with Nette. We simply mark the lang parameter as persistent in this way:

class ProductPresenter extends Nette\Application\UI\Presenter
{
	/** @persistent */
	public $lang;

If the current value of the parameter lang is 'en', then the URL created with link() or n:href in the template will contain lang=en. Great!

However, we can also add parameter lang and by that change its value:

<a n:href="Product:show $productId, lang => en">detail in English</a>

The persistent variable must be declared as public. We can also specify a default value. If the parameter has the same value as the default, it will not be included in the URL.

Persistence reflects the hierarchy of presenter classes, thus parameter defined in a certain presenter or trait is then automatically transferred to each presenter inheriting from it or using the same trait.

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.

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.

Requirement and Parameters

The request handled by the presenter is the Nette\Application\Request object and is returned by the presenter's method getRequest(). It includes an array of parameters and each of them belongs either to some of the components or directly to the presenter (which is actually also a component, albeit a special one). So Nette redistributes the parameters and passes between the individual components (and the presenter) by calling the method loadState(array $params), which is further described in the chapter Components. The parameters can be obtained by the method getParameters(): array, individually using getParameter($name). Parameter values ​​are strings or arrays of strings, they are basically raw data obtained directly from a URL.

Save and Restore the Request

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 it's 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.

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, eg /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\IResponse 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));

Templates

You can change the location of the template files by overriding the formatTemplateFiles or formatLayoutTemplateFiles methods, which return an array of file paths.

The layout is searched in files named @layout.latte. You can change this with setLayout('otherLayout'), then it will search for files @otherLayout.latte. Or turn off layout use setLayout(false).

Links generated with link() or n:href are always absolute paths, but not absolute URLs with protocol and domain like http://domain. To generate absolute URLs, you need to set $this->absoluteUrls = true.


Related blog posts