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 :

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.

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

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

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 :

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

Vérification de la méthode HTTP

Les présentateurs de Nette vérifient automatiquement la méthode HTTP de chaque requête entrante. La raison principale de cette vérification est la sécurité. La vérification de la méthode est effectuée dans checkHttpMethod(), qui détermine si la méthode spécifiée dans la demande est incluse dans le tableau $presenter->allowedMethods. Par défaut, ce tableau contient les éléments GET, POST, HEAD, PUT, DELETE, PATCH, ce qui signifie que ces méthodes sont autorisées.

Si vous souhaitez autoriser en plus la méthode OPTIONS, vous pouvez le faire de la manière suivante :

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.

Autres lectures

version: 4.0