Présentateurs
Nous allons apprendre à écrire des présentateurs et des modèles dans Nette. Après la lecture, vous saurez :
- comment fonctionne le présentateur
- ce que sont les paramètres persistants
- comment rendre un modèle
Nous savons déjà qu'un présentateur est une classe qui représente une page spécifique d'une application Web, comme une page d'accueil, un produit dans une boutique en ligne, un formulaire d'inscription, un flux sitemap, etc. L'application peut avoir de un à plusieurs milliers de présentateurs. Dans d'autres frameworks, ils sont également connus sous le nom de contrôleurs.
En général, le terme „présentateur“ fait référence à un descendant de la classe Nette\Application\UI\Presenter, qui convient aux interfaces Web et dont nous parlerons dans la suite de ce chapitre. D'une manière générale, un présentateur est tout objet qui implémente l'interface Nette\Application\IPresenter.
Cycle de vie du présentateur
Le travail du diffuseur consiste à traiter la demande et à renvoyer une réponse (qui peut être une page HTML, une image, une redirection, etc.)
Au départ, il y a donc une demande. Ce n'est pas directement une requête HTTP, mais un objet Nette\Application\Request en lequel la requête HTTP a été transformée à l'aide d'un routeur. Nous n'entrons généralement pas en contact avec cet objet, car le diffuseur délègue astucieusement le traitement de la requête à des méthodes spéciales, que nous allons voir maintenant.
Cycle de vie du diffuseur
La figure montre une liste de méthodes qui sont appelées séquentiellement de haut en bas, si elles existent. Aucune d'entre elles n'a besoin d'exister, nous pouvons avoir un présentateur complètement vide sans une seule méthode et construire un simple web statique dessus.
__construct()
Le constructeur n'appartient pas exactement au cycle de vie du présentateur, car il est appelé au moment de la création de l'objet. Mais nous le mentionnons en raison de son importance. Le constructeur (avec la méthode inject) est utilisé pour passer les dépendances.
Le présentateur ne doit pas s'occuper de la logique métier de l'application, écrire et lire dans la base de données,
effectuer des calculs, etc. C'est la tâche des classes d'une couche, que nous appelons un modèle. Par exemple, la classe
ArticleRepository
peut être responsable du chargement et de la sauvegarde des articles. Pour que le présentateur
puisse l'utiliser, elle est passée en utilisant l'injection de
dépendances:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
Immédiatement après la réception de la demande, la méthode startup ()
est invoquée. Vous pouvez l'utiliser
pour initialiser les propriétés, vérifier les privilèges des utilisateurs, etc. Il est nécessaire de toujours appeler
l'ancêtre parent::startup()
.
action<Action>(args...)
Similaire à la méthode render<View>()
. Alors que render<View>()
a pour but de préparer
les données pour un modèle spécifique, qui est ensuite rendu, en action<Action>()
une requête est traitée
sans qu'il y ait de rendu ultérieur du modèle. Par exemple, des données sont traitées, un utilisateur est connecté ou
déconnecté, et ainsi de suite, puis il est redirigé ailleurs.
Il est important que action<Action>()
soit appelé avant render<View>()
afin qu'à
l'intérieur de celui-ci, nous puissions éventuellement modifier le cours suivant du cycle de vie, c'est-à-dire changer le
modèle qui sera rendu et également la méthode render<View>()
qui sera appelée, en utilisant
setView('otherView')
.
Les paramètres de la requête sont transmis à la méthode. Il est possible et recommandé de spécifier des types pour les
paramètres, par exemple actionShow(int $id, ?string $slug = null)
– si le paramètre id
est manquant
ou s'il ne s'agit pas d'un nombre entier, le présentateur renvoie l'erreur 404 et met fin à
l'opération.
handle<Signal>(args...)
Cette méthode traite ce que l'on appelle les signaux, dont nous parlerons dans le chapitre sur les composants. Elle est destinée principalement aux composants et au traitement des requêtes AJAX.
Les paramètres sont passés à la méthode, comme dans le cas de action<Action>()
y compris la vérification
de type.
beforeRender()
La méthode beforeRender
, comme son nom l'indique, est appelée avant chaque méthode
render<View>()
. Elle est utilisée pour la configuration commune des modèles, le passage de variables pour la
mise en page, etc.
render<View>(args...)
L'endroit où nous préparons le modèle pour un rendu ultérieur, nous lui passons des données, etc.
Les paramètres sont passés à la méthode, comme dans le cas de action<Action>()
y compris la vérification
de type.
public function renderShow(int $id): void
{
// nous obtenons des données du modèle et les passons au modèle
$this->template->article = $this->articles->getById($id);
}
afterRender()
La méthode afterRender
, comme son nom l'indique, est appelée après chaque méthode.
render<View>()
méthode. Elle est utilisée assez rarement.
shutdown()
Il est appelé à la fin du cycle de vie du présentateur.
Bon conseil avant de passer à autre chose. Comme vous pouvez le constater, le présentateur peut gérer plus
d'actions/visites, c'est-à-dire avoir plus de méthodes… render<View>()
. Mais nous recommandons de concevoir
des présentateurs avec une ou aussi peu d'actions que possible.
Envoi d'une réponse
La réponse du présentateur consiste généralement à rendre le modèle avec la page HTML, mais il peut aussi s'agir d'envoyer un fichier, JSON ou même de rediriger vers une autre page.
À tout moment au cours du cycle de vie, vous pouvez utiliser l'une des méthodes suivantes pour envoyer une réponse et quitter le présentateur en même temps :
redirect()
,redirectPermanent()
,redirectUrl()
etforward()
redirectionserror()
quitte le présentateur en raison d'une erreursendJson($data)
quitte le présentateur et envoie les données au format JSON.sendTemplate()
quitte le présentateur et rend immédiatement le modèle.sendResponse($response)
quitte le présentateur et envoie sa propre réponseterminate()
quitte le présentateur sans réponse
Si vous n'appelez aucune de ces méthodes, le présentateur procède automatiquement au rendu du modèle. Pourquoi ? Eh bien, parce que dans 99% des cas, nous voulons dessiner un modèle, donc le présentateur prend ce comportement par défaut et veut nous faciliter le travail.
Création de liens
Le présentateur possède une méthode link()
, qui est utilisée pour créer des liens URL vers d'autres
présentateurs. Le premier paramètre est le présentateur cible et l'action, suivi des arguments, qui peuvent être passés sous
forme de tableau :
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'en']);
Dans le modèle, nous créons des liens vers d'autres présentateurs et actions comme suit :
<a n:href="Product:show $id">product detail</a>
Il suffit d'écrire la paire familière Presenter:action
au lieu de l'URL réelle et d'inclure tous les
paramètres. L'astuce est n:href
, qui indique que cet attribut sera traité par Latte et générera une véritable
URL. Dans Nette, vous ne devez pas du tout penser aux URL, mais seulement aux présentateurs et aux actions.
Pour plus d'informations, voir Création de liens.
Redirection
Les méthodes redirect()
et forward()
sont utilisées pour passer à un autre présentateur. Elles
ont une syntaxe très similaire à celle de la méthode link().
La méthode forward()
permet de passer immédiatement au nouveau présentateur sans redirection HTTP :
$this->forward('Product:show');
Exemple de redirection temporaire avec le code HTTP 302 (ou 303, si la méthode de requête actuelle est POST) :
$this->redirect('Product:show', $id);
Pour obtenir une redirection permanente avec le code HTTP 301, utilisez :
$this->redirectPermanent('Product:show', $id);
Vous pouvez rediriger vers une autre URL en dehors de l'application en utilisant la méthode redirectUrl()
. Le
code HTTP peut être spécifié comme deuxième paramètre, la valeur par défaut étant 302 (ou 303, si la méthode de requête
actuelle est POST) :
$this->redirectUrl('https://nette.org');
La redirection met immédiatement fin au cycle de vie du présentateur en lançant l'exception dite de terminaison silencieuse
Nette\Application\AbortException
.
Avant la redirection, il est possible d'envoyer un message flash, message qui sera affiché dans le modèle après la redirection.
Messages flash
Il s'agit de messages qui informent généralement sur le résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le modèle même après une redirection. Même après avoir été affichés, ils restent vivants pendant 30 secondes supplémentaires – par exemple, au cas où l'utilisateur rafraîchirait involontairement la page – le message ne sera pas perdu.
Il suffit d'appeler la méthode flashMessage() et Presenter
se chargera de transmettre le message au modèle. Le premier argument est le texte du message et le deuxième argument facultatif
est son type (erreur, avertissement, info, etc.). La méthode flashMessage()
renvoie une instance de message flash,
pour nous permettre d'ajouter plus d'informations.
$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);
Dans le modèle, ces messages sont disponibles dans la variable $flashes
en tant qu'objets stdClass
,
qui contiennent les propriétés message
(texte du message), type
(type de message) et peuvent contenir
les informations utilisateur déjà mentionnées. Nous les dessinons comme suit :
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Erreur 404 etc.
Lorsque nous ne pouvons pas répondre à la demande, par exemple parce que l'article que nous voulons afficher n'existe pas
dans la base de données, nous envoyons l'erreur 404 en utilisant la méthode
error(?string $message = null, int $httpCode = 404)
, qui représente l'erreur HTTP 404 :
public function renderShow(int $id): void
{
$article = $this->articles->getById($id);
if (!$article) {
$this->error();
}
// ...
}
Le code d'erreur HTTP peut être passé comme deuxième paramètre, la valeur par défaut est 404. La méthode fonctionne en
lançant l'exception Nette\Application\BadRequestException
, après quoi Application
passe le contrôle
au présentateur de l'erreur. Il s'agit d'un présentateur dont le rôle est d'afficher une page d'information sur l'erreur. Le
présentateur d'erreur est défini dans la configuration de l'application.
Envoi de JSON
Exemple de méthode d'action qui envoie des données au format JSON et quitte le présentateur :
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Paramètres de la demande
Le présentateur, ainsi que chaque composant, obtient ses paramètres à partir de la demande HTTP. Leurs valeurs peuvent être
récupérées à l'aide de la méthode getParameter($name)
ou getParameters()
. Les valeurs sont des
chaînes ou des tableaux de chaînes, essentiellement des données brutes obtenues directement à partir de l'URL.
Pour plus de commodité, nous recommandons de rendre les paramètres accessibles par le biais de propriétés. Il suffit de les
annoter avec l'attribut #[Parameter]
attribut :
use Nette\Application\Attributes\Parameter; // cette ligne est importante
class HomePresenter extends Nette\Application\UI\Presenter
{
#[Parameter]
public string $theme; // doit être publique
}
Pour les propriétés, nous suggérons de spécifier le type de données (par exemple, string
). Nette va alors
automatiquement calculer la valeur en fonction de ce type de données. Les valeurs des paramètres peuvent également être validées.
Lors de la création d'un lien, vous pouvez directement définir la valeur des paramètres :
<a n:href="Home:default theme: dark">click</a>
Paramètres persistants
Les paramètres persistants sont utilisés pour maintenir l'état entre les différentes requêtes. Leur valeur reste
inchangée même après avoir cliqué sur un lien. Contrairement aux données de session, ils sont transmis dans l'URL. Cette
opération est entièrement automatique, il n'est donc pas nécessaire de les indiquer explicitement dans link()
ou
n:href
.
Exemple d'utilisation ? Vous avez une application multilingue. La langue réelle est un paramètre qui doit toujours faire
partie de l'URL. Mais il serait incroyablement fastidieux de l'inclure dans chaque lien. Vous en faites donc un paramètre
persistant, nommé lang
, qui se chargera de lui-même. C'est super !
La création d'un paramètre persistant est extrêmement simple dans Nette. Il suffit de créer une propriété publique et de
la baliser avec l'attribut : (auparavant /** @persistent */
était utilisé)
use Nette\Application\Attributes\Persistent; // cette ligne est importante
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang; // doit être publique
}
Si $this->lang
a une valeur telle que 'en'
, les liens créés à l'aide de link()
ou
n:href
contiendront également le paramètre lang=en
. Et lorsque le lien sera cliqué, il s'agira à
nouveau de $this->lang = 'en'
.
Pour les propriétés, nous vous recommandons d'indiquer le type de données (par exemple string
) et vous pouvez
également inclure une valeur par défaut. Les valeurs des paramètres peuvent être validées.
Les paramètres persistants sont transmis par défaut entre toutes les actions d'un présentateur donné. Pour les transmettre entre plusieurs présentateurs, vous devez les définir :
- dans un ancêtre commun dont les présentateurs héritent
- dans le trait que les présentateurs utilisent :
trait LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
Vous pouvez modifier la valeur d'un paramètre persistant lors de la création d'un lien :
<a n:href="Product:show $id, lang: cs">detail in Czech</a>
Ou il peut être réinitialisé, c'est-à-dire supprimé de l'URL. Il prendra alors sa valeur par défaut :
<a n:href="Product:show $id, lang: null">click</a>
Composants interactifs
Les présentateurs ont un système de composants intégré. Les composants sont des unités distinctes réutilisables que nous plaçons dans les présentateurs. Il peut s'agir de formulaires, de grilles de données, de menus, en fait de tout ce qui peut être utilisé de manière répétée.
Comment les composants sont-ils placés et ensuite utilisés dans le présentateur ? Ceci est expliqué dans le chapitre Composants. Vous découvrirez même ce qu'ils ont à voir avec Hollywood.
Où puis-je trouver des composants ? Sur la page Componette, vous trouverez des composants open-source et d'autres modules complémentaires pour Nette qui sont réalisés et partagés par la communauté de Nette Framework.
Pour aller plus loin
Ce que nous avons montré jusqu'à présent dans ce chapitre suffira probablement. Les lignes suivantes sont destinées à ceux qui s'intéressent aux présentateurs en profondeur et veulent tout savoir.
Validation des paramètres
Les valeurs des paramètres de requête et des paramètres persistants reçus des URL sont écrites dans les propriétés par la méthode
loadState()
. Elle vérifie également si le type de données spécifié dans la propriété correspond, sinon elle
répondra par une erreur 404 et la page ne sera pas affichée.
Ne faites jamais aveuglément confiance aux paramètres, car ils peuvent facilement être remplacés par l'utilisateur dans
l'URL. Par exemple, voici comment nous vérifions si $this->lang
fait partie des langues prises en charge. Une
bonne façon de le faire est de surcharger la méthode loadState()
mentionnée ci-dessus :
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // ici est défini le $this->lang
// suit la vérification de la valeur de l'utilisateur:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Sauvegarde et restauration de la demande
La demande traitée par le présentateur est un objet Nette\Application\Request et est renvoyée par la
méthode du présentateur getRequest()
.
Vous pouvez enregistrer la requête en cours dans une session ou la restaurer à partir de la session et laisser le
présentateur l'exécuter à nouveau. Ceci est utile, par exemple, lorsqu'un utilisateur remplit un formulaire et que son login
expire. Afin de ne pas perdre de données, avant de rediriger l'utilisateur vers la page de connexion, nous sauvegardons la
demande en cours dans la session à l'aide de la méthode $reqId = $this->storeRequest()
, qui renvoie un
identifiant sous la forme d'une chaîne courte et le transmet en tant que paramètre au présentateur de connexion.
Après l'ouverture de session, nous appelons la méthode $this->restoreRequest($reqId)
, qui récupère la
demande de la session et la lui transmet. La méthode vérifie que la requête a été créée par le même utilisateur que celui
qui est maintenant connecté. Si un autre utilisateur se connecte ou si la clé n'est pas valide, elle ne fait rien et le
programme continue.
Voir le livre de recettes Comment revenir à une page antérieure.
Canonisation
Les présentateurs ont une fonction vraiment formidable qui améliore le référencement (optimisation de la possibilité de
recherche sur Internet). Ils empêchent automatiquement l'existence de contenu dupliqué à différentes URL. Si plusieurs URL
mènent à une certaine destination, par exemple /index
et /index?page=1
, le framework désigne l'une
d'entre elles comme primaire (canonique) et redirige les autres vers elle en utilisant le code HTTP 301. Grâce à cela, les
moteurs de recherche n'indexent pas deux fois les pages et n'affaiblissent pas leur page rank.
Ce processus est appelé canonisation. L'URL canonique est l'URL générée par le routeur, généralement la première route appropriée dans la collection.
La canonisation est activée par défaut et peut être désactivée via $this->autoCanonicalize = false
.
La redirection ne se produit pas avec une demande AJAX ou POST, car elle entraînerait une perte de données ou une absence de valeur ajoutée en termes de référencement.
Vous pouvez également invoquer la canonisation manuellement à l'aide de la méthode canonicalize()
, qui, comme
la méthode link()
, reçoit le présentateur, les actions et les paramètres comme arguments. Elle crée un lien et
le compare à l'URL actuelle. Si elle est différente, elle redirige vers le lien généré.
public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// redirige si $slug est différent de $realSlug
$this->canonicalize('Product:show', [$id, $realSlug]);
}
Événements
En plus des méthodes startup()
, beforeRender()
et shutdown()
, qui sont appelées dans
le cadre du cycle de vie du diffuseur, d'autres fonctions peuvent être définies pour être appelées automatiquement. Le
diffuseur définit ce que l'on appelle des événements, et vous
ajoutez leurs gestionnaires aux tableaux $onStartup
, $onRender
et $onShutdown
.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
Les gestionnaires du tableau $onStartup
sont appelés juste avant la méthode startup()
, puis
$onRender
entre beforeRender()
et render<View>()
et enfin $onShutdown
juste avant shutdown()
.
Réponses
La réponse renvoyée par le présentateur est un objet implémentant l'interface Nette\Application\Response. Il existe un certain nombre de réponses toutes faites :
- Nette\Application\Responses\CallbackResponse – envoie un callback
- Nette\Application\Responses\FileResponse – envoie le fichier
- Nette\Application\Responses\ForwardResponse – envoie ()
- Nette\Application\Responses\JsonResponse – envoie JSON
- Nette\Application\Responses\RedirectResponse – redirection
- Nette\Application\Responses\TextResponse – envoie du texte
- Nette\Application\Responses\VoidResponse – réponse vide
Les réponses sont envoyées par la méthode sendResponse()
:
use Nette\Application\Responses;
// Texte en clair
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));
// Envoi d'un fichier
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Envoi d'un 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));
Restriction d'accès à l'aide de
#[Requires]
L'attribut #[Requires]
fournit des options avancées pour restreindre l'accès aux présentateurs et à leurs
méthodes. Il peut être utilisé pour spécifier des méthodes HTTP, exiger des requêtes AJAX, limiter l'accès à la même
origine et restreindre l'accès à la transmission uniquement. L'attribut peut être appliqué aux classes de présentateurs ainsi
qu'aux méthodes individuelles telles que action<Action>()
, render<View>()
,
handle<Signal>()
, et createComponent<Name>()
.
Vous pouvez spécifier ces restrictions :
- sur les méthodes HTTP :
#[Requires(methods: ['GET', 'POST'])]
- nécessitant une requête AJAX :
#[Requires(ajax: true)]
- accès uniquement à partir de la même origine :
#[Requires(sameOrigin: true)]
- accès uniquement par le biais d'une redirection :
#[Requires(forward: true)]
- restrictions sur des actions spécifiques :
#[Requires(actions: 'default')]
Pour plus de détails, voir Comment utiliser l'attribut Requires.
Vérification de la méthode HTTP
Dans Nette, les présentateurs vérifient automatiquement la méthode HTTP de chaque requête entrante, principalement pour des
raisons de sécurité. Par défaut, les méthodes GET
, POST
, HEAD
, PUT
,
DELETE
, PATCH
sont autorisées.
Si vous souhaitez activer d'autres méthodes telles que OPTIONS
, vous pouvez utiliser l'attribut
#[Requires]
(à partir de Nette Application v3.2) :
#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Dans la version 3.1, la vérification est effectuée dans checkHttpMethod()
, qui vérifie si la méthode
spécifiée dans la requête est incluse dans le tableau $presenter->allowedMethods
. Ajouter une méthode comme
celle-ci :
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
Il est essentiel de souligner que si vous autorisez la méthode OPTIONS
, vous devez également la gérer
correctement dans votre présentateur. Cette méthode est souvent utilisée comme une requête dite „preflight“, que les
navigateurs envoient automatiquement avant la requête réelle lorsqu'il est nécessaire de déterminer si la requête est
autorisée du point de vue de la politique CORS (Cross-Origin Resource Sharing). Si vous autorisez cette méthode mais ne mettez
pas en œuvre une réponse appropriée, cela peut entraîner des incohérences et des problèmes de sécurité potentiels.