Presentatori
Impareremo a scrivere presentatori e modelli in Nette. Dopo la lettura saprete che:
- come funziona il presentatore
- cosa sono i parametri persistenti
- come rendere un modello
Sappiamo già che un presenter è una classe che rappresenta una pagina specifica di un'applicazione web, come ad esempio una homepage, un prodotto in un e-shop, un modulo di iscrizione, un feed sitemap, ecc. L'applicazione può avere da uno a migliaia di presenter. In altri framework, sono noti anche come controllori.
Di solito, il termine presenter si riferisce a un discendente della classe Nette\Application\UI\Presenter, adatta alle interfacce web e di cui parleremo nel resto del capitolo. In senso generale, un presentatore è un qualsiasi oggetto che implementa l'interfaccia Nette\Application\IPresenter.
Ciclo di vita del presentatore
Il compito del presentatore è quello di elaborare la richiesta e restituire una risposta (che può essere una pagina HTML, un'immagine, un reindirizzamento, ecc.)
All'inizio c'è una richiesta. Non si tratta direttamente di una richiesta HTTP, ma di un oggetto Nette\Application\Request in cui la richiesta HTTP è stata trasformata tramite un router. Di solito non entriamo in contatto con questo oggetto, perché il presentatore delega abilmente l'elaborazione della richiesta a metodi speciali, che vedremo ora.
Ciclo di vita del presentatore
La figura mostra un elenco di metodi che vengono chiamati in sequenza dall'alto verso il basso, se esistono. Non è necessario che esistano, possiamo avere un presenter completamente vuoto, senza un solo metodo, e costruirci sopra un semplice web statico.
__construct()
Il costruttore non appartiene esattamente al ciclo di vita del presentatore, perché viene chiamato al momento della creazione dell'oggetto. Ma lo citiamo per la sua importanza. Il costruttore (insieme al metodo inject) è usato per passare le dipendenze.
Il costruttore non deve occuparsi della logica di business dell'applicazione, scrivere e leggere dal database, eseguire
calcoli, ecc. Questo è il compito delle classi di un livello, che chiamiamo modello. Per esempio, la classe
ArticleRepository
può essere responsabile del caricamento e del salvataggio degli articoli. Affinché il
presentatore possa utilizzarla, viene passata utilizzando la dependency
injection:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
Subito dopo la ricezione della richiesta, viene invocato il metodo startup ()
. Si può usare per inizializzare le
proprietà, controllare i privilegi dell'utente, ecc. È necessario chiamare sempre l'antenato
parent::startup()
.
action<Action>(args...)
Simile al metodo render<View>()
. Mentre render<View>()
è destinato a preparare i dati
per un modello specifico, che viene successivamente reso, in action<Action>()
una richiesta viene elaborata
senza una successiva resa del template. Ad esempio, i dati vengono elaborati, l'utente viene connesso o disconnesso e così via
e poi viene reindirizzato altrove.
È importante che action<Action>()
sia chiamato prima di render<View>()
quindi al suo
interno si può eventualmente modificare il corso successivo del ciclo di vita, cioè cambiare il template che sarà reso e anche
il metodo render<View>()
che sarà chiamato, utilizzando setView('otherView')
.
I parametri della richiesta vengono passati al metodo. È possibile e consigliabile specificare i tipi di parametri, ad
esempio actionShow(int $id, ?string $slug = null)
– se il parametro id
manca o non è un intero, il
presentatore restituisce l'errore 404 e termina l'operazione.
handle<Signal>(args...)
Questo metodo elabora i cosiddetti segnali, di cui si parlerà nel capitolo sui componenti. È destinato principalmente ai componenti e all'elaborazione di richieste AJAX.
I parametri vengono passati al metodo, come nel caso di action<Action>()
, compreso il controllo
del tipo.
beforeRender()
Il metodo beforeRender
, come suggerisce il nome, viene chiamato prima di ogni metodo
render<View>()
. È usato per la configurazione comune dei template, per passare le variabili per il layout e
così via.
render<View>(args...)
Il luogo in cui si prepara il modello per il rendering successivo, si passano i dati ad esso, ecc.
I parametri vengono passati al metodo, come nel caso di action<Action>()
, compreso il controllo
del tipo.
public function renderShow(int $id): void
{
// otteniamo i dati dal modello e li passiamo al template
$this->template->article = $this->articles->getById($id);
}
afterRender()
Il metodo afterRender
, come suggerisce il nome, viene richiamato dopo ogni metodo.
render<View>()
metodo. Viene utilizzato piuttosto raramente.
shutdown()
Viene richiamato alla fine del ciclo di vita del presentatore.
Buon consiglio prima di andare avanti. Come si può vedere, il presentatore può gestire più azioni/visualizzazioni,
cioè avere più metodi render<View>()
. Ma si consiglia di progettare presentatori con una sola o il minor
numero possibile di azioni.
Invio di una risposta
La risposta del presentatore è solitamente il rendering del template con la pagina HTML, ma può anche essere l'invio di un file, di JSON o persino il reindirizzamento a un'altra pagina.
In qualsiasi momento del ciclo di vita, è possibile utilizzare uno dei seguenti metodi per inviare una risposta e allo stesso tempo uscire dal presentatore:
redirect()
,redirectPermanent()
,redirectUrl()
eforward()
reindirizzamentierror()
chiude il presentatore per erroresendJson($data)
esce dal presentatore e invia i dati in formato JSONsendTemplate()
abbandona il presentatore e renderizzaimmediatamente il templatesendResponse($response)
abbandona il presentatore e invia la propria rispostaterminate()
abbandona il presentatore senza risposta
Se non si chiama nessuno di questi metodi, il presentatore procederà automaticamente al rendering del modello. Perché? Perché nel 99% dei casi si vuole disegnare un modello, quindi il presentatore assume questo comportamento come predefinito e vuole semplificarci il lavoro.
Creazione di collegamenti
Il presentatore ha un metodo link()
, utilizzato per creare collegamenti URL ad altri presentatori. Il primo
parametro è il presentatore e l'azione di destinazione, seguito dagli argomenti, che possono essere passati come array:
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'en']);
Nel modello creiamo collegamenti ad altri presentatori e azioni come segue:
<a n:href="Product:show $id">product detail</a>
È sufficiente scrivere la nota coppia Presenter:action
al posto dell'URL reale e includere eventuali parametri.
Il trucco è n:href
, che dice che questo attributo sarà elaborato da Latte e genererà un vero URL. In Nette non è
necessario pensare agli URL, ma solo ai presentatori e alle azioni.
Per ulteriori informazioni, vedere Creazione di collegamenti.
Reindirizzamento
Per passare a un altro presentatore si usano i metodi redirect()
e forward()
, che hanno una sintassi
molto simile a quella del metodo link().
Il metodo forward()
passa immediatamente al nuovo presentatore senza reindirizzamento HTTP:
$this->forward('Product:show');
Esempio di un cosiddetto reindirizzamento temporaneo con codice HTTP 302 (o 303, se il metodo di richiesta corrente è POST):
$this->redirect('Product:show', $id);
Per ottenere un reindirizzamento permanente con codice HTTP 301 utilizzare:
$this->redirectPermanent('Product:show', $id);
È possibile reindirizzare a un altro URL al di fuori dell'applicazione utilizzando il metodo redirectUrl()
. Il
codice HTTP può essere specificato come secondo parametro; il valore predefinito è 302 (o 303, se il metodo di richiesta
corrente è POST):
$this->redirectUrl('https://nette.org');
Il reindirizzamento termina immediatamente il ciclo di vita del presentatore lanciando la cosiddetta eccezione di terminazione
silenziosa Nette\Application\AbortException
.
Prima del reindirizzamento, è possibile inviare un messaggio flash, che verrà visualizzato nel modello dopo il reindirizzamento.
Messaggi flash
Sono messaggi che di solito informano sul risultato di un'operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel modello anche dopo il reindirizzamento. Anche dopo essere stati visualizzati, rimarranno in vita per altri 30 secondi – ad esempio, nel caso in cui l'utente dovesse involontariamente aggiornare la pagina – il messaggio non andrà perso.
Basta chiamare il metodo flashMessage() e il
presentatore si occuperà di passare il messaggio al modello. Il primo parametro è il testo del messaggio e il secondo parametro
opzionale è il suo tipo (errore, avviso, info ecc.). Il metodo flashMessage()
restituisce un'istanza di messaggio
flash, per consentire di aggiungere ulteriori informazioni.
$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);
Nel modello, questi messaggi sono disponibili nella variabile $flashes
come oggetti stdClass
, che
contengono le proprietà message
(testo del messaggio), type
(tipo di messaggio) e possono contenere le
già citate informazioni sull'utente. Li disegniamo come segue:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Errore 404 ecc.
Quando non possiamo soddisfare la richiesta, perché ad esempio l'articolo che vogliamo visualizzare non esiste nel database,
lanceremo l'errore 404 usando il metodo error(?string $message = null, int $httpCode = 404)
, che rappresenta
l'errore HTTP 404:
public function renderShow(int $id): void
{
$article = $this->articles->getById($id);
if (!$article) {
$this->error();
}
// ...
}
Il codice di errore HTTP può essere passato come secondo parametro; il valore predefinito è 404. Il metodo funziona
lanciando l'eccezione Nette\Application\BadRequestException
, dopo di che Application
passa il controllo
al presentatore dell'errore. Si tratta di un presentatore che ha il compito di visualizzare una pagina che informa dell'errore. Il
presentatore di errori è impostato nella configurazione dell'applicazione.
Invio di JSON
Esempio di metodo-azione che invia dati in formato JSON ed esce dal presentatore:
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Parametri di richiesta
Il presentatore, così come ogni componente, ottiene i suoi parametri dalla richiesta HTTP. I loro valori possono essere
recuperati con il metodo getParameter($name)
o getParameters()
. I valori sono stringhe o array di
stringhe, essenzialmente dati grezzi ottenuti direttamente dall'URL.
Per maggiore comodità, si consiglia di rendere i parametri accessibili tramite proprietà. È sufficiente annotarli con
l'attributo #[Parameter]
attributo:
use Nette\Application\Attributes\Parameter; // questa linea è importante
class HomePresenter extends Nette\Application\UI\Presenter
{
#[Parameter]
public string $theme; // deve essere pubblica
}
Per le proprietà, si consiglia di specificare il tipo di dati (ad esempio, string
). Nette calcolerà
automaticamente il valore in base ad esso. Anche i valori dei parametri possono essere convalidati.
Quando si crea un collegamento, è possibile impostare direttamente il valore dei parametri:
<a n:href="Home:default theme: dark">click</a>
Parametri persistenti
I parametri persistenti sono usati per mantenere lo stato tra diverse richieste. Il loro valore rimane invariato anche dopo
aver cliccato su un link. A differenza dei dati di sessione, vengono passati nell'URL. Questo è completamente automatico, quindi
non è necessario dichiararli esplicitamente in link()
o n:href
.
Un esempio di utilizzo? Avete un'applicazione multilingue. La lingua attuale è un parametro che deve sempre far parte
dell'URL. Ma sarebbe incredibilmente noioso includerlo in ogni link. Perciò lo si rende un parametro persistente, chiamato
lang
, che si autoalimenta. Forte!
Creare un parametro persistente è estremamente facile in Nette. Basta creare una proprietà pubblica e contrassegnarla con
l'attributo: (in precedenza si usava /** @persistent */
)
use Nette\Application\Attributes\Persistent; // questa linea è importante
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang; // deve essere pubblico
}
Se $this->lang
ha un valore come 'en'
, i link creati con link()
o
n:href
conterranno anche il parametro lang=en
. E quando il link viene cliccato, sarà di nuovo
$this->lang = 'en'
.
Per le proprietà, si consiglia di includere il tipo di dati (ad esempio, string
) e si può anche includere un
valore predefinito. I valori dei parametri possono essere convalidati.
I parametri persistenti vengono passati per impostazione predefinita tra tutte le azioni di un determinato presentatore. Per passarli tra più presentatori, è necessario definirli:
- in un antenato comune dal quale i presentatori ereditano
- nel tratto che i presentatori utilizzano:
trait LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
È possibile modificare il valore di un parametro persistente durante la creazione di un collegamento:
<a n:href="Product:show $id, lang: cs">detail in Czech</a>
Oppure può essere ripristinato, cioè rimosso dall'URL. In questo caso, assumerà il valore predefinito:
<a n:href="Product:show $id, lang: null">click</a>
Componenti interattivi
I presentatori hanno un sistema di componenti incorporato. I componenti sono unità separate e riutilizzabili che vengono inserite nei presentatori. Possono essere moduli, griglie di dati, menu, qualsiasi cosa abbia senso usare ripetutamente.
Come si posizionano i componenti e come si utilizzano successivamente nel presentatore? Questo è spiegato nel capitolo Componenti. Scoprirete anche cosa hanno a che fare con Hollywood.
Dove posso trovare alcuni componenti? Alla pagina Componenti si possono trovare alcuni componenti open-source e altri addons per Nette, realizzati e condivisi dalla comunità di Nette Framework.
Approfondimento
Quanto mostrato finora in questo capitolo sarà probabilmente sufficiente. Le righe che seguono sono destinate a chi è interessato ad approfondire i presentatori e vuole sapere tutto.
Convalida dei parametri
I valori dei parametri della richiesta e dei parametri persistenti ricevuti dagli URL vengono scritti nelle proprietà dal metodo
loadState()
. Il metodo controlla anche se il tipo di dati specificato nella proprietà corrisponde, altrimenti
risponde con un errore 404 e la pagina non viene visualizzata.
Non fidarsi mai ciecamente dei parametri, perché possono essere facilmente sovrascritti dall'utente nell'URL. Ad esempio, è
così che controlliamo se $this->lang
è tra le lingue supportate. Un buon modo per farlo è sovrascrivere il
metodo loadState()
citato in precedenza:
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // qui viene impostato $this->lang
// segue il controllo del valore dell'utente:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Salvare e ripristinare la richiesta
La richiesta che il presentatore gestisce è un oggetto Nette\Application\Request e viene restituita dal
metodo del presentatore getRequest()
.
È possibile salvare la richiesta corrente in una sessione o ripristinarla dalla sessione e lasciare che il presentatore la
esegua di nuovo. Ciò è utile, ad esempio, quando un utente compila un modulo e il suo login scade. Per non perdere i dati,
prima di reindirizzare alla pagina di accesso, salviamo la richiesta corrente nella sessione con il metodo
$reqId = $this->storeRequest()
, che restituisce un identificatore sotto forma di stringa breve e lo passa come
parametro al presentatore di accesso.
Dopo l'accesso, chiamiamo il metodo $this->restoreRequest($reqId)
, che preleva la richiesta dalla sessione e la
inoltra ad essa. Il metodo verifica che la richiesta sia stata creata dallo stesso utente che ha effettuato l'accesso. Se un altro
utente accede o la chiave non è valida, non fa nulla e il programma continua.
Vedere il ricettario Come tornare a una pagina precedente.
Canonizzazione
I presentatori hanno una caratteristica davvero eccezionale che migliora la SEO (ottimizzazione della ricercabilità su
Internet). Impediscono automaticamente l'esistenza di contenuti duplicati su URL diversi. Se più URL conducono a una determinata
destinazione, ad esempio /index
e /index?page=1
, il framework ne designa uno come principale (canonico)
e reindirizza gli altri verso di esso utilizzando il codice HTTP 301. In questo modo, i motori di ricerca non indicizzano le
pagine due volte e non ne indeboliscono il page rank.
Questo processo è chiamato canonizzazione. L'URL canonico è l'URL generato dal router, di solito il primo percorso appropriato della collezione.
La canonizzazione è attiva per impostazione predefinita e può essere disattivata tramite
$this->autoCanonicalize = false
.
Il reindirizzamento non avviene con una richiesta AJAX o POST, perché comporterebbe una perdita di dati o non avrebbe alcun valore aggiunto dal punto di vista SEO.
Si può anche invocare la canonizzazione manualmente con il metodo canonicalize()
, che, come il metodo
link()
, riceve come argomenti il presentatore, le azioni e i parametri. Crea un link e lo confronta con l'URL
corrente. Se è diverso, reindirizza al link generato.
public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// reindirizza se $slug è diverso da $realSlug
$this->canonicalize('Product:show', [$id, $realSlug]);
}
Eventi
Oltre ai metodi startup()
, beforeRender()
e shutdown()
, che vengono richiamati durante
il ciclo di vita del presentatore, è possibile definire altre funzioni da richiamare automaticamente. Il presentatore definisce
i cosiddetti eventi, i cui gestori vengono aggiunti agli array
$onStartup
, $onRender
e $onShutdown
.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
I gestori nell'array $onStartup
vengono chiamati subito prima del metodo startup()
, poi
$onRender
tra beforeRender()
e . render<View>()
e infine $onShutdown
subito prima di shutdown()
.
Le risposte
La risposta restituita dal presentatore è un oggetto che implementa l'interfaccia Nette\Application\Response. Esiste una serie di risposte già pronte:
- Nette\Application\Responses\CallbackResponse – invia un callback
- Nette\Application\Responses\FileResponse – invia il file
- Nette\Application\Responses\ForwardResponse – invia ()
- Nette\Application\Responses\JsonResponse – invia JSON
- Nette\Application\Responses\RedirectResponse – reindirizza
- Nette\Application\Responses\TextResponse – invia testo
- Nette\Application\Responses\VoidResponse – risposta vuota
Le risposte sono inviate con il metodo sendResponse()
:
use Nette\Application\Responses;
// Testo normale
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));
// Invia un file
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Invia un callback
$callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) {
if ($httpResponse->getHeader('Content-Type') === 'text/html') {
echo '<h1>Ciao</h1>';
}
};
$this->sendResponse(new Responses\CallbackResponse($callback));
Limitazione dell'accesso tramite #[Requires]
L'attributo #[Requires]
fornisce opzioni avanzate per limitare l'accesso ai presentatori e ai loro metodi. Può
essere usato per specificare metodi HTTP, richiedere richieste AJAX, limitare l'accesso alla stessa origine e limitare l'accesso
al solo inoltro. L'attributo può essere applicato alle classi di presentatori e ai singoli metodi, come ad esempio
action<Action>()
, render<View>()
, handle<Signal>()
, e
createComponent<Name>()
.
È possibile specificare queste restrizioni:
- sui metodi HTTP:
#[Requires(methods: ['GET', 'POST'])]
- che richiedono una richiesta AJAX:
#[Requires(ajax: true)]
- accesso solo dalla stessa origine:
#[Requires(sameOrigin: true)]
- accesso solo tramite inoltro:
#[Requires(forward: true)]
- restrizioni su azioni specifiche:
#[Requires(actions: 'default')]
Per i dettagli, vedere Come usare l'attributo Requires.
Controllo del metodo HTTP
In Nette, i presentatori verificano automaticamente il metodo HTTP di ogni richiesta in arrivo, principalmente per motivi di
sicurezza. Per impostazione predefinita, sono ammessi i metodi GET
, POST
, HEAD
,
PUT
, DELETE
, PATCH
.
Se si desidera abilitare altri metodi, come ad esempio OPTIONS
, è possibile utilizzare l'attributo
#[Requires]
(dall'applicazione Nette v3.2):
#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Nella versione 3.1, la verifica viene eseguita in checkHttpMethod()
, che controlla se il metodo specificato nella
richiesta è incluso nell'array $presenter->allowedMethods
. Aggiungere un metodo come questo:
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
È fondamentale sottolineare che se si abilita il metodo OPTIONS
, è necessario gestirlo correttamente anche nel
presenter. Questo metodo è spesso usato come una cosiddetta richiesta di preflight, che i browser inviano automaticamente prima
della richiesta vera e propria quando è necessario determinare se la richiesta è consentita dal punto di vista della politica
CORS (Cross-Origin Resource Sharing). Se si consente questo metodo ma non si implementa una risposta appropriata, si possono
creare incongruenze e potenziali problemi di sicurezza.