/
presenters.texy
668 lines (446 loc) · 30.1 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
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
MVC aplikace & presentery
*************************
/--div .[perex]
Seznámíme se s tím, jak se vlastně v Nette Framework tvoří aplikace. Po přečtení budete znát:
- MVC, adresářovou strukturu a soubor `Booting.php`
- co jsou to presentery a akce
- jak se používají šablony
- co jsou persistentní parametry
\--
Model-View-Controller (MVC)
===========================
Model-View-Controller je softwarová architektura, která vznikla z potřeby oddělit u aplikací s grafickým rozhraním kód obsluhy (controller) od kódu aplikační logiky (model) a od kódu zobrazujícího data (view). Tím jednak aplikaci zpřehledňuje, usnadňuje budoucí vývoj a umožňuje testování jednotlivých části zvlášť.
Model
-----
Model je datový a zejména funkční základ celé aplikace. Je v něm obsažena aplikační logika. Jakákoliv akce uživatele (přihlášení, vložení zboží do košíku, změna hodnoty v databázi) představuje akci modelu. Model si spravuje svůj vnitřní stav a ven nabízí pevně dané rozhraní. Voláním funkcí tohoto rozhraní můžeme zjišťovat či měnit jeho stav. Model o existenci view nebo kontroleru neví.
View
----
View, tedy pohled, je vrstva aplikace, která má na starost zobrazení výsledku požadavku. Obvykle používá šablonovací systém a ví, jak se má zobrazit ta která komponenta nebo výsledek získaný z modelu.
Controller
----------
Řadič, který zpracovává požadavky uživatele a na jejich základě pak volá patřičnou aplikační logiku (tj. model) a poté požádá view o vykreslení dat. Obdobou kontrolerů v Nette Framework jsou [presentery |glossary#presenter].
Adresářová struktura
====================
Když se po stažení distribuce Nette Framework podíváte do adresáře `sandbox`, uvidíte doporučovanou adresářovou strukturu:
/--pre
<b>sandbox/</b>
├── <b>app/</b> ← adresář s aplikací
│ ├── <b>config/</b> ← konfigurační soubory
│ │ ├── <b>common.neon</b> ← konfigurační soubor
│ │ └── <b>local.neon</b>
│ │
│ ├── <b>forms/</b> ← třídy formulářů
│ ├── <b>model/</b> ← modelová vrstva a její třídy
│ ├── <b>presenters/</b> ← třídy presenterů
│ │ ├── <b>HomepagePresenter.php</b> ← třída presenteru Homepage
│ │ └── <b>templates/</b> ← adresář se šablonami
│ │ ├── <b>@layout.latte</b> ← šablona společného layoutu
│ │ └── <b>Homepage/</b> ← šablony presenteru Homepage
│ │ └── <b>default.latte</b> ← šablona akce default
│ ├── <b>router/</b> ← třídy routerů
│ │
│ └── <b>Booting.php</b> ← zaváděcí soubor aplikace
│
├── <b>log/</b> ← obsahuje logy, error logy atd.
├── <b>temp/</b> ← pro dočasné soubory, cache, ...
│
├── <b>vendor/</b> ← adresář na knihovny (např. třetích stran)
│ ├── <b>nette/</b> ← všechny knihovny Nette Frameworku
│ │ └── <b>nette/Nette</b> ← oblíbený framework nainstalovaný Composerem
│ ├── ...
│ │
│ └── <b>autoload.php</b> ← soubor který se stará o načítání tříd nainstalovaných balíčků
│
└── <b>www/</b> ← veřejný adresář, document root projektu
├── <b>.htaccess</b> ← pravidla pro mod_rewrite
├── <b>index.php</b> ← který spouští aplikaci
└── <b>images/</b> ← další adresáře, třeba pro obrázky
\--
Krom toho se v některých složkách nacházejí soubory `.htaccess` resp. `web.config`, které zakazují přístup z prohlížeče (pro Apache resp. IIS). Ujistěte se, že to funguje a že se skutečně z prohlížeče do složky `app/` nebo `libs/` nedostanete.
.[note]
Adresářům `log/` a `temp/` nezapomeňte nastavit práva pro zápis (`chmod 0777`).
Všechny požadavky posílá prohlížeč přes jediný PHP soubor, který se nachází ve veřejném adresáři `www/`, a tím je soubor `index.php`. Ten předává řízení do aplikace (tj. do adresáře `app/`) zaváděcímu souboru `Booting.php`, kde se [konfiguruje prostředí a DI kontejner |bootstrap].
Výše uvedená adresářová struktura je tak skutečně jen doporučená, protože ji můžeme snadno jakkoliv změnit nebo složky přejmenovat a bude stačit pouze přenastavit cesty v souboru `Booting.php`.
Zpracování akce presenteru
==========================
Nyní prosím zbystřete. Každý požadavek na naši aplikaci se dostane přes soubory `index.php` a `Booting.php` do objektu `$application`. Ten ale HTTP požadavkům nerozumí, proto požádá [router |routing], aby mu ho přeložil do řeči, které už rozumí. Tedy aby mu řekl, pro který **presenter** je požadavek určen a kterou **akci** s ním chce vykonat. Router kupříkladu odpoví, že uživatel chce akci `show` presenteru `Product` (je dobrý zvykem to zapisovat jako `Product:show`) a dále předává parametr `id = 123`. Česky by se řeklo: uživatele chce zobrazit produkt s id=123.
Tomu už `$application` rozumí a přistoupí k plnění přání. Vyrobí objekt třídy `ProductPresenter`, která reprezentuje presenter `Product`. (Abychom byli zcela přesní, o výrobu objektu presenteru požádá službu `presenterFactory`). A pak bude presenter požádán o provedení akce (`show` s parametrem `id`).
Presenter je objekt, který vezme požadavek přeložený routerem a vymyslí odpověď. Odpovědí může být HTML stránka, obrázek, XML dokument, soubor na disku, JSON, přesměrování nebo cokoliv potřebujete. Konkrétně ProductPresenter požádá model o data a ty poté předá do šablony k vykreslení. Tohle se zpravidla odehraje v metodě `renderShow`, kde slovo `Show` odpovídá názvu akce a parametr požadavku `id` bude předán jako parametr této metodě:
/--php
class ProductPresenter extends Nette\Application\UI\Presenter
{
public function renderShow(int $id): void
{
// získáme data z modelu a předáme do šablony
$this->template->product = $this->productRepository->getProduct($id);
}
}
\--
Pole všech parametrů odeslaných aplikaci požadavkem GET najdete v `$this->request->getParameters()`. V běžných případech byste je ale neměli potřebovat, využívejte [routování |routing] a [parametrů akce |#zpracovani-akce-presenteru].
Obdobně pole všech hodnot přijatých přes POST naleznete v `$this->request->getPost()`. Ani to byste neměli potřebovat, pokud nezpracováváte požadavek z jiné aplikace. Nejčastěji zpracováváte totiž formuláře vlastní aplikace a na ty máme [formulářovou komponentu|forms#formulare-v-presenterech].
Následně přistoupí presenter k vykreslení šablony.
Šablony
-------
Cestu k souboru se šablonou odvodí presenter podle jednoduché logiky. Pro presenter `Product` a akci `show` zkusí, zda existuje jeden z těchto souborů:
/--
- templates/Product/show.latte
- templates/Product.show.latte
\--
Taktéž se pokusí dohledat layout (ten je nepovinný):
/--
- templates/Product/@layout.latte
- templates/Product.@layout.latte
- templates/@layout.latte layout společný pro více presenterů
\--
.[tip]
Způsob dohledávání šablon můžeme změnit přepsáním metod [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] nebo [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()].
Presentery a jejich komponenty předávají do šablon několik užitečných proměnných:
- `$basePath` je absolutní URL cesta ke kořenovému adresáři (např. `/CD-collection`)
- `$baseUrl` je absolutní URL ke kořenovému adresáři (např. `http://localhost/CD-collection`)
- `$user` je objekt [reprezentující uživatele |access-control]
- `$presenter` je aktuální presenter
- `$control` je aktuální komponenta nebo presenter
- `$flashes` pole [zpráv |#flash zprávy] zaslaných funkcí `flashMessage()`
O vykreslování šablon podrobněji v [samostatné kapitole |latte:].
Ono to vlastně není nic těžkého! Pokud požaduji akci například `Homepage:default`, tak se
1) vytvoří objekt třídy `HomepagePresenter`
2) zavolá se metoda `renderDefault()` (existuje-li, ale nemusí)
3) vykreslí se šablona např. `templates/Homepage/default.latte` s layoutem např. `templates/@layout.latte`
a v šabloně pak můžeme [vytvořit odkaz |#Vytváření odkazů] na zmíněný `Product:show($id)`, zhruba takto:
/--html
<a n:href="Product:show $productId">detail produktu</a>
\--
Nechci to zakřiknout, ale tvorba aplikací v Nette bude pohodička.
Moduly
------
U složitějších aplikací můžeme složky s presentery a šablonami rozčlenit do podadresářů, kterým říkáme moduly. Pokud by naše aplikace obsahovala například moduly `Front` a `Admin`, její struktura by mohla vypadat takto:
/--pre
<b>sandbox/</b>
<b>app/</b> ← adresář s aplikací
<b>AdminModule/</b> ← modul Admin
<b>presenters/</b> ← a jeho presentery
<b>templates/</b> ← a šablony
<b>FrontModule/</b> ← modul Front
<b>presenters/</b> ← a jeho presentery
<b>templates/</b> ← a šablony
...
\--
Moduly nemusí tvořit jen plochou strukturu, lze vytvářet i submoduly atd.
Pokud by součástí modulu `Front` byl presenter `Product`. Akce `show` se pak zapíše jako `Front:Product:show` a třída `ProductPresenter` se umístí do jmenného prostoru `FrontModule`:
/--php
namespace FrontModule;
class ProductPresenter extends \Nette\Application\UI\Presenter
{
// ...
}
\--
Vytváření odkazů
================
Vytvoření odkazů patří mezi nejsilnější místa Nette Frameworku. Díky [obousměrnému routování |routing] není potřeba do šablon či kódu natvrdo zapisovat URL nebo je komplikovaně skládat. Stačí se odkazovat na akce presenterů, předávat jim parametry a framework už URL vygeneruje sám. Vytvořit odkaz je pak stejně snadné, jako zavolat funkci. To se vám bude tuze líbit!
Během programování a vytváření šablon nás nemusí zajímat tvar URL adres, budeme se totiž odkazovat přímo na akci presenteru, tj. například na již zmíněnou `Product:show`.
Odkazy v šablonách
------------------
Nejčastěji vytváříme odkazy v šablonách. Aby to bylo co nejjednodušší, nabízí nám framework hned tři makra. Nejšikovnější z nich je makro `n:href`
/--html
<a n:href="Product:show $productId">detail produktu</a>
\--
Všimněte si, že místo HTML atributu `href` jsme použili [n:makro |latte:#n-makra] `n:href`. Jeho hodnotou pak není URL, jak by tomu bylo v případě atributu `href`, ale rovnou akce presenteru. Tam má tvar
/--
[Presenter:]action [,] [arg1] [, arg2] [, ...]
\--
Po kliknutí na odkaz se dostane ke slovu metoda `ProductPresenter::renderShow()` a jako parametr `$id` ji bude předána hodnota proměnné `$productId`. Parametrů bychom mohli předat i víc, stejně jako když voláme metodu. Mohlo by to být snad jednodušší?
.[note]
Doporučuje se dodržovat konvenci velkého prvního písmenka pro název presenteru a malého pro akci. Jako oddělovač slouží právě jedna dvojtečka.
Kromě toho je možné předávat i pojmenované parametry. Následující odkaz předává parametr `lang` s hodnotou `cs`:
/--html
<a n:href="Product:show $productId, lang => cs">detail produktu</a>
\--
Ačkoliv metoda `renderShow` nemá `$lang` ve své hlavičce, může hodnotu parametru zjistit voláním `$lang = $this->getParameter('lang')`.
Máme-li všechny parametry v poli, můžeme jej rozvinout operátorem `(expand)`:
/--html
{var $args = [$productId, lang => cs]}
<a n:href="Product:show (expand) $args">detail produktu</a>
\--
Pokud je šablona, ve které vytváříme odkaz, také součástí `Product` presenteru, můžeme název presenteru vynechat a psát přímo `n:href="show $productId"`. Nebo obráceně, vede-li odkaz na akci nazvanou `default`, lze tuto vynechat a napsat `n:href="Product: $id"` (nezapomeňte na dvojtečku).
Odkazy mohou také směřovat do jiných [modulů |#moduly]. Zde se rozlišuje, zda se odkazujeme "relativně" do zanořeného submodulu, nebo "absolutně" do jiného modulu - pak cesta začne dvojtečkou. Pro ukázku předpokládejme, že aktuální presenter je součástí modul `Front`, potom zapíšeme:
/--html
<a n:href="Shop:Product:show">odkaz na Front:Shop:Product:show</a>
<a n:href=":Admin:Product:show">odkaz na Admin:Product:show</a>
\--
Speciálním případem je odkaz na sebe sama, kdy jako cíl uvedeme `this`.
Vygenerovaný odkaz má podobu relativní cesty. Pokud potřebujeme **absolutní odkaz včetně domény**, přidáme na začátek dvě lomítka (např. `n:href="//show $productId"`). Pokud nastavíme v presenteru proměnnou [$absoluteUrls |api:Nette\Application\UI\Presenter::$absoluteUrls] na `true`, budou všechny od té chvíle generované odkazy absolutní.
Odkazovat můžeme na určitou část na stránce přes tzv. fragment za znakem křížky `#`:
/--html
<a n:href="show#comments">odkaz na Product:show a fragment #comments</a>
\--
Makro `n:href` je velmi šikovné, pokud vytváříme HTML značku `<a>`. Chceme-li odkaz vypsat jinde, například v textu šablony, použijeme makro `{link}` se stejnou vnitřní syntaxí:
/--html
Adresa je: {link Product:show $productId}
\--
Blok uvnitř `{ifCurrent $link}...{/ifCurrent}` se vykoná, pokud je cíl odkazu shodný s aktuální stránkou. Makro je vhodné použít například pokud chceme aktivnímu odkazu přidat CSS třídu.
/--html
<!-- příklady použití -->
<a href="{link edit, 10}">edituj</a>
<ul class="menu">
<li><a href="{link Default:default}">...</a></li>
<li><a href="{link}">...</a></li>
...
<li {ifCurrent Default:default}class="active"{/ifCurrent}><a href="{link Default:default}">...</a></li>
<!-- rozšíření scope -->
<li {ifCurrent Default:*}class="active"{/ifCurrent}><a href="{link Default:default}">...</a></li>
</ul>
<!-- znegování -->
{ifCurrent Admin:login}{else}<a href="{link Admin:login}">Přihlašte se!</a>{/ifCurrent}
\--
Přečtěte si další podrobnosti o syntaxi [Latte šablon |latte:macros].
Odkazování v presenteru
-----------------------
Presenter a [komponenta |api:Nette\Application\UI\Component] disponují metodou `link()`, pomocí které lze vytvářet odkazy podobně jako v šabloně. Prvním parametrem je cílová akce presenteru, následují předávané argumenty:
/--php
$url = $this->link(destination [,arg [,arg ...]]);
\--
Ty lze předat také pomocí pole. Příklady:
/--php
$url = $this->link('Product:show', $productId);
$url = $this->link('Product:show', [$productId, 'lang' => 'cs']);
\--
.[caution]
Pokud při generování odkazu předáte parametru metody hodnotu `false`, do odkazu se vůbec nepřidá. Řešením je definovat pro parametr výchozí hodnotu `true` nebo `false`. Nette pak pochopí, že je typu boolean, a předávanou hodnotu změní na 1 nebo 0 a následně při zpracování requestu je presenter převede zpět na boolean.
Neplatné odkazy
---------------
Může se stát, že vytvoříme neplatný odkaz - buď proto, že vede na neexistující presenter, proto, že předává víc parametrů, než které cílová metoda přijímá ve své definici, nebo když pro cílovou akci nelze vygenerovat URL. Jak naložit s neplatnými odkazy určuje statická proměnná `Presenter::$invalidLinkMode`. Ta může nabývat kombinaci těchto hodnot (konstant):
- `Presenter::INVALID_LINK_SILENT` - tichý režim, jako URL se vrátí znak #
- `Presenter::INVALID_LINK_WARNING` - vyhodí se varování E_USER_WARNING, které bude v produkčním režimu zalogováno, ale nezpůsobí přerušení běhu skriptu
- `Presenter::INVALID_LINK_TEXTUAL` - vizuální varování, vypíše chybu přímo do odkazu
- `Presenter::INVALID_LINK_EXCEPTION` - vyhodí se výjimka InvalidLinkException
Výchozí nastavení je `INVALID_LINK_WARNING` v produkčním režimu a `INVALID_LINK_WARNING | INVALID_LINK_TEXTUAL` ve vývojovém. `INVALID_LINK_WARNING` v produkčním prostředí nezpůsobí přerušení skriptu, ale varování bude zalogováno. Ve vývojovém prostředí ho zachytí [Tracy | tracy:] a zobrazí bluescreen. `INVALID_LINK_TEXTUAL` pracuje tak, že jako URL vrátí chybovou zprávu, která začíná znaky `#error:`. Aby takové odkazy byly na první pohled patrné, doplníme si do CSS:
/--css
a[href^="#error:"] {
background: red;
color: white;
}
\--
Pokud nechceme, aby se ve vývojovém prostředí produkovala varování, můžeme nastavit tichý režim přímo v [konfiguraci|configuring#chyby].
/--neon
application:
silentLinks: true
\--
Persistentní parametry
----------------------
Krom klasických parametrů, které jsme používali nyní, existují i tzv. persistentní parametry. Ty se liší v jediné avšak zásadní věci: **přenášejí se automaticky.** To znamená, že je nemusíme v odkazech explicitně uvádět, ale přesto se přenesou.
Pokud má vaše aplikace dvě jazykové mutace, bylo by neskutečně únavné v každém odkazu přenášet i aktuální jazyk. To není s Nette Framework potřeba. Prostě si parametr `$lang` označíme jako persistentní a to tímto způsobem:
/--php
class ProductPresenter extends Nette\Application\UI\Presenter
{
/** @persistent */
public $lang;
...
\--
Pokud aktuální hodnota parametru `$lang` bude `'en'`, tak do odkazu
/--html
<a n:href="Product:show $productId">detail produktu</a>
\--
se automaticky doplní i `lang => en`. Paráda!
Samozřejmě můžeme `lang` uvést a jeho hodnotu změnit:
/--html
<a n:href="Product:show $productId, lang => cs">detail v češtině</a>
\--
Zároveň v proměnné třídy `$lang` objektu `ProductPresenter` budeme mít hodnotu parametru k dispozici, můžeme k ní přistoupit přes `$this->lang`. Můžeme v definici třídy uvést i výchozí hodnotu persistentního parametru. Bude-li mít parametr tuto výchozí hodnotou, pak nebude přenášen v URL.
.[note]
Persistentní proměnná musí být deklarovaná jako public.
Persistence zohledňuje hierarchii tříd presenterů, tedy parametr definovaný v určitém presenteru je poté automaticky přenášen do každého presenteru z něj dědícího.
Životní cyklus presenteru
=========================
Už víme, že tzv. akce presenteru způsobí zavolání metody `render<Akce>`, tedy například `renderShow`. Ale to není zdaleka jediná metoda, která se volá. Při psaní presenterů můžeme mimo jiné vytvořit i následující metody:
[* lifecycle2.gif *] *** *Životní cyklus presenteru* .<>
`startup()`
-----------
Ihned po vytvoření presenteru se zavolá metoda `startup()`. Ta inicializuje proměnné nebo ověří uživatelská oprávnění.
.[note]
Pokud si píšete vlastní metodu `startup()`, nezapomeňte zavolat předka `parent::startup()`.
`action<Action>()`
------------------
Obdoba metody `render<View>()`. Jsou situace, kdy presenter provede určitý úkon (přihlásí uživatele, zapíše data do databáze) a poté přesměruje jinam. Dávat název `render` metodě, která nic nekreslí, by bylo nepěkné, proto existuje alternativa s názvem `action`.
Důležité je vědět, že `action<Action>()` se volá dříve, než metoda `render<View>()`. Může tedy i rozhodnout, aby se zavolala jiná metoda `render` pomocí příkazu `$this->setView('jineView')` (zavolá se `renderJineView()`).
`handle<Signal>()`
------------------
Metoda zpracovává tzv. [signály |components#signál neboli subrequest] neboli subrequesty. Určeno zejména pro komponenty a zpracování AJAXových požadavků. Na stejné úrovni se také provádí zpracování formulářů.
`beforeRender()`
----------------
Metoda `beforeRender`, jak už název napovídá, se volá před nám známou metodou `render<View>()` a může obsahovat například nastavení šablony, předání proměnných společných pro více view a podobně.
`render<View>()`
----------------
Obvykle nasype do šablony potřebná data.
`shutdown()`
------------
Je vyvolána při ukončení životního cyklu presenteru.
Přesnější by bylo říci, že jsme si povídali o životním cyklu třídy [api:Nette\Application\UI\Presenter], ze které se presentery dědí nejčastěji. Obecně je totiž presenter jakákoliv třída implementující trivální rozhraní [api:Nette\Application\IPresenter].
Odeslání odpovědi
=================
Presenter můžeme během jeho životního cyklu kdykoliv ukončit a odeslat jinou odpověď, než je výchozí vykreslení šablony.
- presenter ukončíme metodou `$presenter->terminate()`
- presenter ukončíme a hned vyrenderujeme šablonu: `$presenter->sendTemplate()`
- presenter ukončíme a odešleme payload: `$presenter->sendPayload()` (pro AJAX)
- presenter ukončíme a odešleme vlastní odpověď: `$presenter->sendResponse($response)`
- presenter ukončíme a odešleme JSON: `$presenter->sendJson($data)`
- presenter ukončíme a odešleme chybovou hlášku: `$presenter->error($message, $httpCode)`
Příklad odeslání JSONu:
/--php
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
\--
Příklady použití metody `sendResponse`:
/--php
// Prosty text
$this->sendResponse(new Nette\Application\Responses\TextResponse('Hello Nette!'));
// Odešle soubor
$this->sendResponse(new Nette\Application\Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Odpovědí bude callback
$this->sendResponse(new Nette\Application\Responses\CallbackResponse(function (Http\IRequest $httpRequest, Http\IResponse $httpResponse) {
if ($httpResponse->getHeader('Content-Type') === 'text/html')) {
echo '<h1>Hello</h1>';
}
});
\--
Presenter lze také ukončit přesměrováním či forwardem, viz dále.
.[tip]
Repozitář daších odpovědí presenteru najdete na "Componette":https://componette.com/search/response.
Přesměrování
============
K přechodu na jiný presenter slouží metody `redirect()` a `forward()`, které mají velmi podobnou syntax jako zmíněná funkce [link() |#odkazování v presenteru]. Například po odeslání formuláře a zápisu dat do databáze přesměrujeme na detail produktu zavoláním:
/--php
$this->redirect('Product:show', $id);
\--
Zatímco `forward()` přejde na novou akci bez přesměrování, metoda redirect() přesměruje prohlížeč HTTP kódem 302 nebo 303. Takto přesměrujeme permanentně s kódem 301:
/--php
$this->redirectPermanent('Product:show', $id);
\--
Na jinou URL mimo rozsah aplikace lze přesměrovat metodou redirectUrl()
/--php
$this->redirectUrl('https://nette.org');
\--
Přesměrování okamžitě ukončí činnost presenteru vyhozením tzv. ukončovací výjimky `Nette\Application\AbortException`.
Před přesměrováním si občas chceme odeslat tzv. [flash message |#flash zprávy], tedy zprávu, která se objeví po přesměrování v šabloně.
Chyba 404 a spol.
=================
Pokud nemůžeme splnit požadavek z důvodu, že například záznam neexistuje v databázi, vyhodíme metodou `error()` výjimku `Nette\Application\BadRequestException`, která představuje HTTP chybu 404:
/--php
public function renderShow(int $id): void
{
$product = $this->productRepository->getProduct($id);
if (!$product) {
$this->error();
}
// ...
}
\--
Pokud uživatel nemá oprávnění stránku vidět, vyhodíme výjimku `Nette\Application\ForbiddenRequestException` (chyba 403). Další chybové kódy lze uvést jako kód výjimky `Nette\Application\BadRequestException`.
Flash zprávy
============
Jde o zprávy obvykle informující o výsledku nějaké operace. Důležitým rysem flash zpráv je to, že jsou v šabloně k dispozici i po přesměrování. I po zobrazení zůstanou živé ještě další 3 sekundy – například pro případ, že by z důvodu chybného přenosu uživatel dal stránku obnovit - zpráva mu tedy hned nezmizí.
Stačí zavolat metodu [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] a o předání do šablony se postará presenter. Prvním parametrem je text zprávy a nepovinným druhým parametrem její typ (error, warning, info apod.). Metoda `flashMessage()` vrací instanci flash zprávy, které je možné přidávat další informace.
/--php
public function deleteFormSubmitted(): void
{
// ... požádáme model o smazání záznamu ...
// předáme flash zprávu
$this->flashMessage('Položka byla smazána.');
$this->redirect(...); // a přesměrujeme
}
\--
Šabloně jsou tyto zprávy k dispozici v proměnné `$flashes` jako anonymní objekty, které obsahují vlastnosti `message` (text zprávy), `type` (typ zprávy) a mohou obsahovat již zmíněné uživatelské informace. Vykreslíme je třeba takto:
/--php
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
\--
Použití modelových tříd
=======================
Jak jsme si řekli, model je samostatná vrstva a je složen z hromady tříd, kde každá obstarává část logiky aplikace. Pokud bychom měli například model `App\ArticleRepository`, který se stará o načítání a ukládání článků, mohli bychom si o něj v presenteru říct pomocí [Dependency Injection |dependency-injection], za předpokladu že je [registrovaný jako služba |configuring#sluzby] v [DI kontejneru |dependency-injection#nette-di-container].
/--php
class ArticlePresenter extends Nette\Application\UI\Presenter
{
/** @var ArticleRepository */
public $articles;
public function __construct(ArticleRepository $articles)
{
$this->articles = $articles;
}
public function renderShow(int $id): void
{
$this->template->article = $this->articles->find($id);
}
}
\--
Díky tomu pak můžu tuto službu hned používat v metodách presenteru a nemusím se starat o její vytváření, protože mám jistotu, že mi bude vždy předána, pokud ji správně zaregistruji do DI kontejneru.
Injektovat lze pouze do `public` vlastností tříd.
Presenter a komponenty
======================
Bavíme-li se o presenterech, tak pod pojmem [komponenty |components] obvykle myslíme potomky třídy [Control |api:Nette\Application\UI\Control]. Přesnější by tedy bylo používat termín „controls“ (tj. ovládací prvky), ale „kontrola“ má v češtině zcela jiný význam a spíš se ujaly „komponenty“.
Sám presenter `Nette\Application\UI\Presenter` je přitom potomkem třídy `Control`, takže je tu velká podobnost mezi komponentami a presenterem. Především však `UI\Control` (a tím pádem i `UI\Presenter`) je tzv. komponentový kontejner, což znamená, že do něj lze vkládat další komponenty. Podobně, jako třeba do komponenty [formuláře |forms] vkládáme formulářové prvky (textové políčko, tlačítko, ...). A stejně jako u formulářů lze k prvkům přistupovat přes hranaté závorky:
/--php
// připojíme komponentu do presenteru
$presenter['mymenu'] = new MenuControl;
// získáme komponentu z presenteru a vykreslíme
$presenter['mymenu']->render();
\--
Připojením komponenty do presenteru (jejich svázáním) získáte možnost:
- vytvářet v komponentě odkazy
- používat [signály |components#signál neboli subrequest]
- používat v komponentě persistentní parametry
Pokud nic z toho nepotřebujeme, není potřeba komponentu s presenterem vázat.
Továrny na komponenty
---------------------
Továrna na komponenty představuje elegantní způsob, jak komponenty vytvářet teprve ve chvíli, kdy jsou skutečně potřeba (lazy / on demand). Celé kouzlo spočívá v implementaci metody s názvem `createComponent<Name>()`, kde `<Name>` je název vytvářené komponenty, a která komponentu vytvoří a vrátí. Komponenta je následně připojena k presenteru. Metodě `createComponent<Name>` je předáván volitelný parametr s názvem komponenty, kterou vytváří.
/--php
class DefaultPresenter extends Nette\Application\UI\Presenter
{
public function renderDefault(): void
{
$menu = $this['menu']; // přistoupíme ke komponentě
// a pokud to bylo poprvé, zavolá se createComponentMenu()
// ...
}
protected function createComponentMenu(): MenuControl
{
// vytvoříme a nakonfigurujeme komponentu
$menu = new MenuControl;
$menu->items = $this->item;
// a vrátíme ji
return $menu;
}
}
\--
.[note]
Názvy komponent začínají vždy malým písmenem, přestože se v názvu továrny píší s velkým.
Díky tomu, že jsou všechny komponenty vytvářeny v samostatné metodě, získává kód na přehlednosti.
Továrny nikdy nevoláme přímo, zavolá se sama ve chvíli, kdy komponentu poprvé použijeme. Díky tomu je komponenta vytvořena ve správný okamžik a pouze v případě, když je skutečně potřeba. Pokud komponentu nepoužijeme (třeba při AJAXovém požadavku, kdy se přenáší jen část stránky, nebo při cachování šablony), nevytvoří se vůbec a ušetříme výkon serveru.
V šabloně je možné získat a vykreslit komponentu pomocí [makra {control} |latte:macros#vykreslovani-komponent]. Není proto potřeba manuálně komponenty předávat do šablony.
/--html
<h2>Editační formulář</h2>
{control editForm}
\--
Podrobnější informace o komponentách najdete na [samostatné stránce |components].
Persistentní komponenty
-----------------------
Nejen parametry, ale také komponenty mohou být persistentní. Jejich stav se pak přenáší při přechodu na jiný presenter podobně, jako v případě [persistentních parametrů |#persistentni-parametry]. Persistentní komponenty značíme touto anotací: (zde značíme komponenty `calendar` a `menu`):
/--php
/**
* @persistent(calendar, menu)
*/
class DefaultPresenter extends Nette\Application\UI\Presenter
{
// ...
}
\--
Podkomponenty uvnitř těchto komponent není třeba nijak značit, jsou persistentní samy o sobě.
Kde mohu získat komponenty?
---------------------------
Na stránce [Doplňky, pluginy a komponenty |https://componette.com] můžete najít open-source komponenty, které sem umístili dobrovolníci z komunity okolo Nette Framework. Nette Foundation za ně neručí.
Konfigurace
===========
Pomocí aplikační konfiurace lze ovlivnit některé chování aplikace. Zapisujeme ji do sekce `application` v našem konfiguračním souboru, viz [konfigurace application|configuring#application].
{{composer: nette/application}}