Apresentadores
Aprenderemos como escrever apresentadores e modelos em Nette. Após a leitura, você saberá:
- como funciona o apresentador
- o que são parâmetros persistentes
- como fazer um modelo
Já sabemos que um apresentador é uma classe que representa uma página específica de uma aplicação web, como uma página inicial; produto em e-shop; formulário de login; feed de mapa do site, etc. A aplicação pode ter de um a milhares de apresentadores. Em outras estruturas, eles também são conhecidos como controladores.
Normalmente, o termo apresentador se refere a um descendente da classe Nette\Application\UI\Presenter, que é adequado para interfaces web e que discutiremos no resto deste capítulo. Em um sentido geral, um apresentador é qualquer objeto que implemente a interface Nette\Application\IPresenter.
Ciclo de vida do apresentador
O trabalho do apresentador é processar a solicitação e devolver uma resposta (que pode ser uma página HTML, imagem, redirecionamento, etc.).
Portanto, no início é um pedido. Não é diretamente uma solicitação HTTP, mas um objeto Nette\Application\Request no qual a solicitação HTTP foi transformada usando um roteador. Normalmente não entramos em contato com este objeto, porque o apresentador delega inteligentemente o processamento da solicitação a métodos especiais, o que veremos agora.
***Ciclo de vida do apresentador*
A figura mostra uma lista de métodos que são chamados sequencialmente de cima para baixo, se eles existirem. Nenhum deles precisa existir, podemos ter um apresentador completamente vazio sem um único método e construir uma simples teia estática sobre ele.
__construct()
O construtor não pertence exatamente ao ciclo de vida do apresentador, pois é chamado no momento da criação do objeto. Mas nós o mencionamos devido à sua importância. O construtor (juntamente com o método de injeção) é usado para passar dependências.
O apresentador não deve cuidar da lógica comercial da aplicação, escrever e ler a partir do banco de dados, realizar
cálculos, etc. Esta é a tarefa para as classes de uma camada, que chamamos de modelo. Por exemplo, a classe
ArticleRepository
pode ser responsável por carregar e salvar artigos. Para que o apresentador possa utilizá-la,
ela é passada usando a injeção de
dependência:
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articles,
) {
}
}
startup()
Imediatamente após o recebimento do pedido, é invocado o método startup ()
. Você pode utilizá-lo para
inicializar as propriedades, verificar os privilégios do usuário, etc. É necessário chamar sempre o ancestral
parent::startup()
.
action<Action>(args...)
Semelhante ao método render<View>()
. Enquanto render<View>()
é destinado a preparar
dados para um modelo específico, que é posteriormente apresentado, em action<Action>()
uma solicitação é
processada sem acompanhamento da apresentação do modelo. Por exemplo, os dados são processados, um usuário entra ou sai, e
assim por diante, e depois é redirecionado para outro lugar.
É importante que action<Action>()
é chamado antes render<View>()
Assim, dentro dele
podemos possivelmente mudar o próximo ciclo de vida, ou seja, mudar o modelo que será apresentado e também o método
render<View>()
que será chamado, usando setView('otherView')
.
Os parâmetros do pedido são passados para o método. É possível e recomendado especificar tipos para os parâmetros, por
exemplo actionShow(int $id, ?string $slug = null)
– se o parâmetro id
estiver faltando ou se não
for um número inteiro, o apresentador retorna o erro 404 e encerra a operação.
handle<Signal>(args...)
Este método processa os chamados sinais, que discutiremos no capítulo sobre Componentes. Ele se destina principalmente a componentes e processamento de pedidos AJAX.
Os parâmetros são passados para o método, como no caso de action<Action>()
incluindo a verificação
do tipo.
beforeRender()
O método beforeRender
, como o nome sugere, é chamado antes de cada método render<View>()
.
É utilizado para configuração de modelos comuns, passando variáveis para layout e assim por diante.
render<View>(args...)
O local onde preparamos o modelo para posterior renderização, passamos dados para ele, etc.
Os parâmetros são passados para o método, como no caso de action<Action>()
incluindo a verificação
do tipo.
public function renderShow(int $id): void
{
// obtemos os dados do modelo e os passamos para o modelo
$this->template->article = $this->articles->getById($id);
}
afterRender()
O método afterRender
, como o nome sugere novamente, é chamado depois de cada render<View>()
método. Ele é usado muito raramente.
shutdown()
É chamado no final do ciclo de vida do apresentador.
**Bom conselho antes de seguirmos em frente***. Como você pode ver, o apresentador pode lidar com mais ações/visões, ou
seja, ter mais métodos render<View>()
. Mas recomendamos projetar apresentadores com uma ou o menor número
possível de ações.
Enviando uma resposta
A resposta do apresentador geralmente é renderizar o modelo com a página HTML, mas também pode estar enviando um arquivo, JSON ou mesmo redirecionando para outra página.
A qualquer momento durante o ciclo de vida, você pode usar qualquer um dos seguintes métodos para enviar uma resposta e sair do apresentador ao mesmo tempo:
redirect()
,redirectPermanent()
,redirectUrl()
eforward()
redirecionaerror()
deixa o apresentador devido a errosendJson($data)
deixa o apresentador e envia os dados no formato JSONsendTemplate()
desiste do apresentador e imediatamente apresenta o modelosendResponse($response)
deixa o apresentador e envia a sua própria respostaterminate()
deixa o apresentador sem resposta
Se você não chamar nenhum destes métodos, o apresentador procederá automaticamente para renderizar o modelo. Por quê? Bem, porque em 99% dos casos queremos desenhar um modelo, então o apresentador toma este comportamento como padrão e quer tornar nosso trabalho mais fácil.
Criação de links
O apresentador tem um método link()
, que é usado para criar links de URL para outros apresentadores.
O primeiro parâmetro é o apresentador alvo & ação, seguido dos argumentos, que podem ser passados como array:
$url = $this->link('Product:show', $id);
$url = $this->link('Product:show', [$id, 'lang' => 'en']);
No modelo, criamos links para outros apresentadores e ações da seguinte forma:
<a n:href="Product:show $id">product detail</a>
Basta escrever o familiar par Presenter:action
ao invés da URL real e incluir quaisquer parâmetros. O truque
é n:href
, que diz que este atributo será processado por Latte e gera uma URL real. Em Nette, você não precisa
pensar em URLs de forma alguma, apenas em apresentadores e ações.
Para mais informações, consulte Criação de links.
Redirecionamento
Os métodos redirect()
e forward()
são usados para saltar para outro apresentador, que tem uma
sintaxe muito semelhante à do link do método.
O forward()
muda imediatamente para o novo apresentador sem redirecionamento HTTP:
$this->forward('Product:show');
Exemplo de um chamado redirecionamento temporário com o código HTTP 302 (ou 303, se o método de solicitação atual for POST):
$this->redirect('Product:show', $id);
Para obter um redirecionamento permanente com o uso do código HTTP 301:
$this->redirectPermanent('Product:show', $id);
Você pode redirecionar para outra URL fora da aplicação usando o método redirectUrl()
. O código HTTP pode
ser especificado como o segundo parâmetro, sendo o padrão 302 (ou 303, se o método de solicitação atual for POST):
$this->redirectUrl('https://nette.org');
A redireção encerra imediatamente o ciclo de vida do apresentador, lançando a chamada exceção de terminação silenciosa
Nette\Application\AbortException
.
Antes do redirecionamento, é possível enviar uma mensagem flash, mensagens que serão exibidas no modelo após o redirecionamento.
Mensagens Flash
Estas são mensagens que normalmente informam sobre o resultado de uma operação. Uma característica importante das mensagens flash é que elas estão disponíveis no modelo, mesmo após o redirecionamento. Mesmo após serem exibidas, elas permanecerão vivas por mais 30 segundos – por exemplo, caso o usuário atualize involuntariamente a página – a mensagem não será perdida.
Basta ligar para o método flashMessage() e
o apresentador se encarregará de passar a mensagem para o modelo. O primeiro argumento é o texto da mensagem e o segundo
argumento opcional é seu tipo (erro, aviso, informação, etc.). O método flashMessage()
retorna uma instância de
mensagem flash, para nos permitir acrescentar mais informações.
$this->flashMessage('Item was removed.');
$this->redirect(/* ... */);
No modelo, estas mensagens estão disponíveis na variável $flashes
como objetos stdClass
, que
contém as propriedades message
(texto da mensagem), type
(tipo de mensagem) e podem conter as
informações de usuário já mencionadas. Nós as desenhamos da seguinte forma:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Erro 404 etc.
Quando não pudermos atender ao pedido porque, por exemplo, o artigo que queremos exibir não existe no banco de dados, vamos
jogar fora o erro 404 usando o método error(?string $message = null, int $httpCode = 404)
, que representa o erro
HTTP 404:
public function renderShow(int $id): void
{
$article = $this->articles->getById($id);
if (!$article) {
$this->error();
}
// ...
}
O código de erro HTTP pode ser passado como o segundo parâmetro, o padrão é 404. O método funciona lançando a
exceção Nette\Application\BadRequestException
, após a qual Application
passa o controle para
o apresentador do erro. Que é um apresentador cuja função é exibir uma página informando sobre o erro. O apresentador de
erros é definido na configuração da aplicação.
Enviando o JSON
Exemplo de método de ação que envia dados em formato JSON e sai do apresentador:
public function actionData(): void
{
$data = ['hello' => 'nette'];
$this->sendJson($data);
}
Parâmetros de solicitação
O apresentador, assim como todos os componentes, obtém seus parâmetros da solicitação HTTP. Seus valores podem ser
recuperados usando o método getParameter($name)
ou getParameters()
. Os valores são cadeias de
caracteres ou matrizes de cadeias de caracteres, essencialmente dados brutos obtidos diretamente do URL.
Para maior comodidade, recomendamos tornar os parâmetros acessíveis por meio de propriedades. Basta anotá-los com
o atributo #[Parameter]
atributo:
use Nette\Application\Attributes\Parameter; // essa linha é importante
class HomePresenter extends Nette\Application\UI\Presenter
{
#[Parameter]
public string $theme; // deve ser pública
}
Para propriedades, sugerimos especificar o tipo de dados (por exemplo, string
). O Nette converterá
automaticamente o valor com base nele. Os valores dos parâmetros também podem ser validados.
Ao criar um link, você pode definir diretamente o valor dos parâmetros:
<a n:href="Home:default theme: dark">click</a>
Parâmetros Persistentes
Parâmetros persistentes são usados para manter o estado entre diferentes solicitações. Seu valor permanece o mesmo mesmo,
mesmo depois que um link é clicado. Ao contrário dos dados da sessão, eles são passados na URL. Isto é completamente
automático, portanto não há necessidade de declará-los explicitamente em link()
ou n:href
.
Exemplo de uso? Você tem uma aplicação multilíngüe. O idioma real é um parâmetro que precisa fazer parte da URL
o tempo todo. Mas seria incrivelmente entediante incluí-lo em cada link. Portanto, você o torna um parâmetro persistente
chamado lang
e ele se carregará sozinho. Legal!
Criar um parâmetro persistente é extremamente fácil em Nette. Basta criar uma propriedade pública e etiquetá-la com
o atributo: (anteriormente foi utilizado /** @persistent */
)
use Nette\Application\Attributes\Persistent; // esta linha é importante
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang; // deve ser público
}
Se $this->lang
tem um valor como 'en'
, então os links criados usando link()
ou
n:href
também conterão o parâmetro lang=en
. E quando o link for clicado, ele será novamente
$this->lang = 'en'
.
Para propriedades, recomendamos que você inclua o tipo de dados (por exemplo, string
) e também pode incluir um
valor padrão. Os valores dos parâmetros podem ser validados.
Parâmetros persistentes são passados por padrão entre todas as ações de um determinado apresentador. Para passá-los entre vários apresentadores, você precisa defini-los também:
- em um ancestral comum do qual os apresentadores herdam
- no traço que os apresentadores usam:
trait LanguageAware
{
#[Persistent]
public string $lang;
}
class ProductPresenter extends Nette\Application\UI\Presenter
{
use LanguageAware;
}
Você pode alterar o valor de um parâmetro persistente ao criar um link:
<a n:href="Product:show $id, lang: cs">detail in Czech</a>
Ou pode ser reset, ou seja, removido da URL. Então, ele tomará seu valor padrão:
<a n:href="Product:show $id, lang: null">click</a>
Componentes interativos
Os apresentadores têm um sistema de componentes incorporado. Os componentes são unidades reutilizáveis separadas que colocamos nos apresentadores. Eles podem ser formas, datagrids, menus, na verdade qualquer coisa que faça sentido usar repetidamente.
Como os componentes são colocados e posteriormente utilizados no apresentador? Isto é explicado no capítulo Componentes. Você descobrirá até mesmo o que eles têm a ver com Hollywood.
Onde posso obter alguns componentes? Na página Componente você pode encontrar alguns componentes de código aberto e outros addons para Nette que são feitos e compartilhados pela comunidade de Nette Framework.
Indo mais fundo
O que mostramos até agora neste capítulo provavelmente será suficiente. As seguintes linhas destinam-se àqueles que estão interessados em apresentadores em profundidade e querem saber tudo.
Validação de parâmetros
Os valores dos parâmetros de solicitação e dos parâmetros persistentes recebidos dos URLs são gravados nas propriedades pelo método
loadState()
. Ele também verifica se o tipo de dados especificado na propriedade corresponde; caso contrário, ele
responderá com um erro 404 e a página não será exibida.
Nunca confie cegamente nos parâmetros, pois eles podem ser facilmente substituídos pelo usuário no URL. Por exemplo, é
assim que verificamos se $this->lang
está entre os idiomas suportados. Uma boa maneira de fazer isso é
substituir o método loadState()
mencionado acima:
class ProductPresenter extends Nette\Application\UI\Presenter
{
#[Persistent]
public string $lang;
public function loadState(array $params): void
{
parent::loadState($params); // aqui está definido o $this->lang
// segue a verificação do valor do usuário:
if (!in_array($this->lang, ['en', 'cs'])) {
$this->error();
}
}
}
Salvar e Restaurar o Pedido
A solicitação que o apresentador trata é um objeto Nette\Application\Request e é retornada pelo
método do apresentador getRequest()
.
Você pode salvar a solicitação atual em uma sessão ou restaurá-la da sessão e permitir que o apresentador a execute
novamente. Isso é útil, por exemplo, quando um usuário preenche um formulário e seu login expira. Para não perder dados,
antes de redirecionar para a página de login, salvamos a solicitação atual na sessão usando
$reqId = $this->storeRequest()
, que retorna um identificador na forma de uma cadeia de caracteres curta e o passa
como parâmetro para o apresentador de login.
Após o login, chamamos o método $this->restoreRequest($reqId)
, que capta o pedido da sessão e
o encaminha para ele. O método verifica que a solicitação foi criada pelo mesmo usuário que está agora logado. Se outro
usuário faz o login ou a chave é inválida, ele não faz nada e o programa continua.
Veja o livro de receitas Como voltar a uma página anterior.
Canonização
Os apresentadores têm uma característica realmente grande que melhora a SEO (otimização da capacidade de busca na
Internet). Eles evitam automaticamente a existência de conteúdo duplicado em diferentes URLs. Se múltiplas URLs levam a um
determinado destino, por exemplo, /index
e /index?page=1
, a estrutura designa uma delas como a principal
(canônica) e redireciona as outras para ela usando o código HTTP 301. Graças a isso, os mecanismos de busca não indexam
páginas duas vezes e não enfraquecem sua classificação de páginas.
Este processo é chamado de canonização. A URL canônica é a URL gerada pelo roteador, geralmente a primeira rota apropriada na coleção.
A canonização está ligada por padrão e pode ser desligada via $this->autoCanonicalize = false
.
O redirecionamento não ocorre com um pedido AJAX ou POST porque resultaria em perda de dados ou sem valor agregado SEO.
Você também pode invocar a canonização manualmente usando o método canonicalize()
, que, como o método
link()
, recebe o apresentador, ações e parâmetros como argumentos. Ele cria um link e o compara com a URL atual.
Se for diferente, ele se redireciona para o link gerado.
public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// redireciona se $slug for diferente de $realSlug
$this->canonicalize('Product:show', [$id, $realSlug]);
}
Eventos
Além dos métodos startup()
, beforeRender()
e shutdown()
, que são chamados como parte
do ciclo de vida do apresentador, outras funções podem ser definidas para serem chamadas automaticamente. O apresentador define
os chamados eventos, e você adiciona seus manipuladores às arrays
$onStartup
, $onRender
e $onShutdown
.
class ArticlePresenter extends Nette\Application\UI\Presenter
{
public function __construct()
{
$this->onStartup[] = function () {
// ...
};
}
}
Os manipuladores na matriz $onStartup
são chamados pouco antes do método startup()
, depois
$onRender
entre beforeRender()
e render<View>()
e finalmente $onShutdown
pouco antes de shutdown()
.
Respostas
A resposta devolvida pelo apresentador é um objeto que implementa a interface Nette\Application\Response. Há uma série de respostas prontas:
- Nette\Application\Responses\CallbackResponse – envia uma ligação de retorno
- Nette\Application\Responses\FileResponse – envia o arquivo
- Nette\Application\Responses\ForwardResponse – para frente ()
- Nette\Application\Responses\JsonResponse – envia JSON
- Nette\Application\Responses\RedirectResponse – redirecionar
- Nette\Application\Responses\TextResponse – envia texto
- Nette\Application\Responses\VoidResponse – resposta em branco
As respostas são enviadas pelo método sendResponse()
:
use Nette\Application\Responses;
// Texto simples
$this->sendResponse(new Responses\TextResponse('Hello Nette!'));
// Envia um arquivo
$this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf'));
// Envia uma ligação de retorno
$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));
Restrição de acesso usando #[Requires]
O atributo #[Requires]
fornece opções avançadas para restringir o acesso aos apresentadores e seus métodos.
Ele pode ser usado para especificar métodos HTTP, exigir solicitações AJAX, limitar o acesso à mesma origem e restringir
o acesso somente ao encaminhamento. O atributo pode ser aplicado a classes de apresentadores, bem como a métodos individuais,
como action<Action>()
, render<View>()
, handle<Signal>()
, e
createComponent<Name>()
.
Você pode especificar essas restrições:
- em métodos HTTP:
#[Requires(methods: ['GET', 'POST'])]
- que exigem uma solicitação AJAX:
#[Requires(ajax: true)]
- acesso somente a partir da mesma origem:
#[Requires(sameOrigin: true)]
- acesso somente por meio de encaminhamento:
#[Requires(forward: true)]
- restrições a ações específicas:
#[Requires(actions: 'default')]
Para obter detalhes, consulte Como usar o atributo Requires.
Verificação do método HTTP
No Nette, os apresentadores verificam automaticamente o método HTTP de cada solicitação recebida, principalmente por
motivos de segurança. Por padrão, os métodos GET
, POST
, HEAD
, PUT
,
DELETE
, PATCH
são permitidos.
Se quiser habilitar métodos adicionais, como OPTIONS
, você pode usar o atributo #[Requires]
(do
Nette Application v3.2):
#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
class MyPresenter extends Nette\Application\UI\Presenter
{
}
Na versão 3.1, a verificação é realizada em checkHttpMethod()
, que verifica se o método especificado na
solicitação está incluído na matriz $presenter->allowedMethods
. Adicione um método como este:
class MyPresenter extends Nette\Application\UI\Presenter
{
protected function checkHttpMethod(): void
{
$this->allowedMethods[] = 'OPTIONS';
parent::checkHttpMethod();
}
}
É fundamental enfatizar que, se você habilitar o método OPTIONS
, também deverá tratá-lo adequadamente em
seu apresentador. Esse método é frequentemente usado como a chamada solicitação de preflight, que os navegadores enviam
automaticamente antes da solicitação real quando é necessário determinar se a solicitação é permitida do ponto de vista da
política de CORS (Cross-Origin Resource Sharing). Se você permitir esse método, mas não implementar uma resposta adequada,
isso poderá gerar inconsistências e possíveis problemas de segurança.