forked from nette/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpresenters.texy
434 lines (280 loc) · 23.2 KB
/
presenters.texy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
Présentateurs
*************
<div class=perex>
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
</div>
[Nous savons déjà |how-it-works#nette-application] 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 [api: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 [api:Nette\Application\IPresenter].
Cycle de vie du présentateur .[#toc-life-cycle-of-presenter]
============================================================
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 [api: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.
[* lifecycle.svg *] *** *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 |best-practices:inject-method-attribute]) 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 |dependency-injection:passing-dependencies]:
```php
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...)` .{toc: action<Action>()}
--------------------------------------------------
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 |#Redirection].
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 |#Error 404 etc.] et met fin à l'opération.
`handle<Signal>(args...)` .{toc: handle<Signal>()}
--------------------------------------------------
Cette méthode traite ce que l'on appelle les signaux, dont nous parlerons dans le chapitre sur les [composants |components#Signal]. 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...)` .{toc: render<View>()}
----------------------------------------------
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.
```php
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 .[#toc-sending-a-response]
==============================================
La réponse du présentateur consiste généralement à [rendre le modèle avec la page HTML |templates], 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()` et `forward()` [redirections |#Redirection]
- `error()` quitte le présentateur [en raison d'une erreur |#Error 404 etc.]
- `sendJson($data)` quitte le présentateur et [envoie les données |#Sending JSON] au format JSON.
- `sendTemplate()` quitte le présentateur et [rend |templates] immédiatement le [modèle |templates].
- `sendResponse($response)` quitte le présentateur et envoie [sa propre réponse |#Responses]
- `terminate()` 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 .[#toc-creating-links]
========================================
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 :
```php
$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 :
```latte
<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 |Creating Links].
Redirection .[#toc-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() |#Creating Links].
La méthode `forward()` permet de passer immédiatement au nouveau présentateur sans redirection HTTP :
```php
$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) :
```php
$this->redirect('Product:show', $id);
```
Pour obtenir une redirection permanente avec le code HTTP 301, utilisez :
```php
$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) :
```php
$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 |#Flash Messages], message qui sera affiché dans le modèle après la redirection.
Messages flash .[#toc-flash-messages]
=====================================
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() |api:Nette\Application\UI\Control::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.
```php
$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 :
```latte
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
```
Erreur 404 etc. .[#toc-error-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 :
```php
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 |configuration].
Envoi de JSON .[#toc-sending-json]
==================================
Exemple de méthode d'action qui envoie des données au format JSON et quitte le présentateur :
```php
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
```
Paramètres persistants .[#toc-persistent-parameters]
====================================================
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é)
```php
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 |#Validation of Persistent Parameters].
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 :
```php
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 :
```latte
<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 :
```latte
<a n:href="Product:show $id, lang: null">click</a>
```
Composants interactifs .[#toc-interactive-components]
=====================================================
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 |forms:in-presenter], 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 |Components]. Vous découvrirez même ce qu'ils ont à voir avec Hollywood.
Où puis-je trouver des composants ? Sur la page [Componette |https://componette.org], 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 .[#toc-going-deeper]
=========================================
.[tip]
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.
Exigences et paramètres .[#toc-requirement-and-parameters]
----------------------------------------------------------
La requête traitée par le présentateur est l'objet [api:Nette\Application\Request] et est renvoyée par la méthode du présentateur `getRequest()`. Elle comprend un tableau de paramètres et chacun d'entre eux appartient soit à l'un des composants, soit directement au présentateur (qui est également un composant, bien qu'il soit spécial). Nette redistribue donc les paramètres et les transmet entre les différents composants (et le présentateur) en appelant la méthode `loadState(array $params)`. Les paramètres peuvent être obtenus par la méthode `getParameters(): array`, individuellement en utilisant `getParameter($name)`. Les valeurs des paramètres sont des chaînes ou des tableaux de chaînes, il s'agit essentiellement de données brutes obtenues directement à partir d'une URL.
Validation des paramètres persistants .[#toc-validation-of-persistent-parameters]
---------------------------------------------------------------------------------
Les valeurs des [paramètres persistants |#persistent parameters] 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 persistants, 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 :
```php
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 .[#toc-save-and-restore-the-request]
-----------------------------------------------------------------------------
Vous pouvez enregistrer la demande 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 sa connexion expire. Afin de ne pas perdre de données, avant de rediriger l'utilisateur vers la page de connexion, nous sauvegardons la demande actuelle dans la session à l'aide de `$reqId = $this->storeRequest()`, qui renvoie un identifiant sous la forme d'une chaîne courte et le transmet comme 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 |best-practices:restore-request].
Canonisation .[#toc-canonization]
---------------------------------
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 |routing], 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é.
```php
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 .[#toc-events]
-------------------------
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 |nette:glossary#events], et vous ajoutez leurs gestionnaires aux tableaux `$onStartup`, `$onRender` et `$onShutdown`.
```php
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 .[#toc-responses]
--------------------------
La réponse renvoyée par le présentateur est un objet implémentant l'interface [api:Nette\Application\Response]. Il existe un certain nombre de réponses toutes faites :
- [api:Nette\Application\Responses\CallbackResponse] - envoie un callback
- [api:Nette\Application\Responses\FileResponse] - envoie le fichier
- [api:Nette\Application\Responses\ForwardResponse] - envoie ()
- [api:Nette\Application\Responses\JsonResponse] - envoie JSON
- [api:Nette\Application\Responses\RedirectResponse] - redirection
- [api:Nette\Application\Responses\TextResponse] - envoie du texte
- [api:Nette\Application\Responses\VoidResponse] - réponse vide
Les réponses sont envoyées par la méthode `sendResponse()`:
```php
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));
```
Autres lectures .[#toc-further-reading]
=======================================
- [Injecter des méthodes et des attributs |best-practices:inject-method-attribute]
- [Composer des présentateurs à partir de traits |best-practices:presenter-traits]
- [Transmettre des paramètres aux présentateurs |best-practices:passing-settings-to-presenters]
- [Comment revenir à une page précédente |best-practices:restore-request]