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 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
{
/** @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 by
specifying their default value, eg actionShow($id = 0, $slug = '')
– if parameter id
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($id)
{
// 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 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 a 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 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($id)
{
$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()
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
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 $id, lang => en">detail in English</a>
Or, conversely, it can be removed by setting to null:
<a n:href="Product:show $id, lang => null">click here</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 is then automatically transferred to each presenter inheriting from it.
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.
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.
Since nette/application 2.4.6 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($id, $slug = null)
{
$realSlug = $this->facade->getSlugForId($id);
// redirects if $slug is different from $realSlug
$this->canonicalize('Product:show', [$id, $realSlug]);
}
Events
(since nette/application 2.4.11) 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
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()
and $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:
- 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));