Nette Documentation Preview

syntax
Formulários Usados Sozinhos
***************************
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Formulários Usados Sozinhos

Os Nette Forms facilitam muito a criação e o processamento de formulários web. Pode usá-los nas suas aplicações de forma totalmente independente do restante do framework, como mostraremos neste capítulo.

No entanto, se usa Nette Application e presenters, o guia para uso em presenters é para si.

Primeiro formulário

Vamos tentar escrever um formulário de registo simples. O código será o seguinte (código completo):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Nome:');
$form->addPassword('password', 'Senha:');
$form->addSubmit('send', 'Registar');

Podemos renderizá-lo facilmente:

$form->render();

e no navegador ele será exibido assim:

O formulário é um objeto da classe Nette\Forms\Form (a classe Nette\Application\UI\Form é usada em presenters). Adicionamos a ele os chamados controlos nome, senha e um botão de envio.

E agora vamos dar vida ao formulário. Perguntando $form->isSuccess(), descobrimos se o formulário foi enviado e se foi preenchido de forma válida. Se sim, exibimos os dados. Após a definição do formulário, adicionamos:

if ($form->isSuccess()) {
	echo 'Formulário foi preenchido corretamente e enviado';
	$data = $form->getValues();
	// $data->name contém o nome
	// $data->password contém a senha
	var_dump($data);
}

O método getValues() retorna os dados enviados na forma de um objeto ArrayHash. Mostraremos como alterar isso mais tarde. O objeto $data contém as chaves name e password com os dados que o utilizador preencheu.

Normalmente, enviamos os dados diretamente para processamento posterior, que pode ser, por exemplo, inserção no banco de dados. No entanto, durante o processamento, pode ocorrer um erro, por exemplo, o nome de utilizador já está em uso. Nesse caso, passamos o erro de volta para o formulário usando addError() e o deixamos renderizar novamente, junto com a mensagem de erro.

$form->addError('Desculpe, este nome de utilizador já está em uso.');

Após processar o formulário, redirecionamos para a próxima página. Isso evita o reenvio indesejado do formulário pelo botão atualizar, voltar ou movendo-se no histórico do navegador.

O formulário é enviado por padrão pelo método POST para a mesma página. Ambos podem ser alterados:

$form->setAction('/submit.php');
$form->setMethod('GET');

E isso é basicamente tudo :-) Temos um formulário funcional e perfeitamente seguro.

Tente adicionar também outros controlos de formulário.

Acesso aos controlos

Chamamos o formulário e os seus controlos individuais de componentes. Eles formam uma árvore de componentes, onde a raiz é o formulário. Podemos aceder aos controlos individuais do formulário desta forma:

$input = $form->getComponent('name');
// sintaxe alternativa: $input = $form['name'];

$button = $form->getComponent('send');
// sintaxe alternativa: $button = $form['send'];

Os controlos são removidos usando unset:

unset($form['name']);

Regras de validação

A palavra válido foi mencionada, mas o formulário ainda não possui regras de validação. Vamos corrigir isso.

O nome será obrigatório, então marcamo-lo com o método setRequired(), cujo argumento é o texto da mensagem de erro que será exibida se o utilizador não preencher o nome. Se o argumento não for fornecido, a mensagem de erro padrão será usada.

$form->addText('name', 'Nome:')
	->setRequired('Por favor, insira o nome');

Tente enviar o formulário sem preencher o nome e verá que uma mensagem de erro será exibida e o navegador ou servidor o rejeitará até que preencha o campo.

Ao mesmo tempo, não enganará o sistema digitando, por exemplo, apenas espaços no campo. De jeito nenhum. Nette remove automaticamente os espaços à esquerda e à direita. Experimente. É algo que deveria sempre fazer com cada input de linha única, mas muitas vezes é esquecido. Nette faz isso automaticamente. (Pode tentar enganar o formulário e enviar uma string de várias linhas como nome. Mesmo aqui, Nette não se deixa enganar e transforma as quebras de linha em espaços.)

O formulário é sempre validado no lado do servidor, mas também é gerada uma validação JavaScript, que ocorre instantaneamente e o utilizador é informado sobre o erro imediatamente, sem a necessidade de enviar o formulário ao servidor. Isso é feito pelo script netteForms.js. Insira-o na página:

<script src="https://unpkg.com/nette-forms@3"></script>

Se olhar o código-fonte da página com o formulário, poderá notar que Nette insere os controlos obrigatórios em elementos com a classe CSS required. Tente adicionar a seguinte folha de estilo ao template e o rótulo „Nome“ ficará vermelho. Desta forma, marcamos elegantemente os controlos obrigatórios para os utilizadores:

<style>
.required label { color: maroon }
</style>

Adicionamos outras regras de validação com o método addRule(). O primeiro parâmetro é a regra, o segundo é novamente o texto da mensagem de erro e pode haver ainda um argumento da regra de validação. O que isso significa?

Vamos estender o formulário com um novo campo opcional „idade“, que deve ser um número inteiro (addInteger()) e, além disso, dentro de um intervalo permitido ($form::Range). E aqui usaremos o terceiro parâmetro do método addRule(), pelo qual passamos o intervalo desejado ao validador como um par [de, até]:

$form->addInteger('age', 'Idade:')
	->addRule($form::Range, 'A idade deve ser entre 18 e 120', [18, 120]);

Se o utilizador não preencher o campo, as regras de validação não serão verificadas, pois o controlo é opcional.

Aqui surge espaço para uma pequena refatoração. Na mensagem de erro e no terceiro parâmetro, os números são listados em duplicidade, o que não é ideal. Se estivéssemos a criar formulários multilíngues e a mensagem contendo números fosse traduzida para vários idiomas, uma eventual alteração dos valores seria dificultada. Por esse motivo, é possível usar os placeholders %d e Nette preencherá os valores:

	->addRule($form::Range, 'A idade deve ser entre %d e %d anos', [18, 120]);

Voltemos ao controlo password, que também tornaremos obrigatório e ainda verificaremos o comprimento mínimo da senha ($form::MinLength), novamente usando o placeholder:

$form->addPassword('password', 'Senha:')
	->setRequired('Escolha uma senha')
	->addRule($form::MinLength, 'A senha deve ter pelo menos %d caracteres', 8);

Adicionamos ao formulário também o campo passwordVerify, onde o utilizador digita a senha novamente, para verificação. Usando regras de validação, verificamos se ambas as senhas são iguais ($form::Equal). E como parâmetro, damos uma referência à primeira senha usando colchetes:

$form->addPassword('passwordVerify', 'Senha para verificação:')
	->setRequired('Por favor, digite a senha novamente para verificação')
	->addRule($form::Equal, 'As senhas não coincidem', $form['password'])
	->setOmitted();

Usando setOmitted(), marcamos o controlo cujo valor realmente não nos importa e que existe apenas para fins de validação. O valor não é passado para $data.

Com isso, temos um formulário totalmente funcional com validação em PHP e JavaScript. As capacidades de validação de Nette são muito mais amplas, é possível criar condições, exibir e ocultar partes da página com base nelas, etc. Aprenderá tudo no capítulo sobre validação de formulários.

Valores padrão

Normalmente, definimos valores padrão para os controlos do formulário:

$form->addEmail('email', 'E-mail')
	->setDefaultValue($lastUsedEmail);

Muitas vezes, é útil definir valores padrão para todos os controlos simultaneamente. Por exemplo, quando o formulário é usado para editar registos. Lemos o registo do banco de dados e definimos os valores padrão:

//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);

Chame setDefaults() após a definição dos controlos.

Renderização do formulário

Por padrão, o formulário é renderizado como uma tabela. Os controlos individuais cumprem a regra básica de acessibilidade – todos os rótulos são escritos como <label> e vinculados ao controlo de formulário correspondente. Ao clicar no rótulo, o cursor aparece automaticamente no campo do formulário.

Podemos definir atributos HTML arbitrários para cada controlo. Por exemplo, adicionar um placeholder:

$form->addInteger('age', 'Idade:')
	->setHtmlAttribute('placeholder', 'Por favor, preencha a idade');

Existem realmente muitas maneiras de renderizar um formulário, então há um capítulo separado sobre renderização dedicado a isso.

Mapeamento para classes

Voltemos ao processamento dos dados do formulário. O método getValues() retornou-nos os dados enviados como um objeto ArrayHash. Como é uma classe genérica, algo como stdClass, sentiremos falta de certo conforto ao trabalhar com ela, como sugestões de propriedades em editores ou análise estática de código. Isso poderia ser resolvido tendo uma classe específica para cada formulário, cujas propriedades representam os controlos individuais. Por exemplo:

class RegistrationFormData
{
	public string $name;
	public ?int $age;
	public string $password;
}

Alternativamente, pode usar o construtor:

class RegistrationFormData
{
	public function __construct(
		public string $name,
		public int $age,
		public string $password,
	) {
	}
}

As propriedades da classe de dados também podem ser enums e serão mapeadas automaticamente.

Como dizer ao Nette para nos retornar os dados como objetos desta classe? Mais fácil do que pensa. Basta fornecer o nome da classe ou o objeto a ser hidratado como parâmetro:

$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;

Também é possível fornecer 'array' como parâmetro e, em seguida, os dados serão retornados como um array.

Se os formulários formarem uma estrutura multinível composta por contêineres, crie uma classe separada para cada um:

$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */

class PersonFormData
{
	public string $firstName;
	public string $lastName;
}

class RegistrationFormData
{
	public PersonFormData $person;
	public ?int $age;
	public string $password;
}

O mapeamento então reconhece pelo tipo da propriedade $person que deve mapear o contêiner para a classe PersonFormData. Se a propriedade contiver um array de contêineres, especifique o tipo array e passe a classe para mapeamento diretamente para o contêiner:

$person->setMappedType(PersonFormData::class);

Pode gerar o design da classe de dados do formulário usando o método Nette\Forms\Blueprint::dataClass($form), que o exibirá na página do navegador. Em seguida, basta clicar para selecionar o código e copiá-lo para o projeto.

Múltiplos botões

Se o formulário tiver mais de um botão, geralmente precisamos distinguir qual deles foi pressionado. Essa informação é retornada pelo método isSubmittedBy() do botão:

$form->addSubmit('save', 'Salvar');
$form->addSubmit('delete', 'Excluir');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}

Não pule a verificação $form->isSuccess(), ela verifica a validade dos dados.

Quando o formulário é enviado pressionando a tecla Enter, é considerado como se tivesse sido enviado pelo primeiro botão.

Proteção contra vulnerabilidades

O Nette Framework dá grande ênfase à segurança e, portanto, cuida meticulosamente da boa segurança dos formulários.

Além de proteger os formulários contra ataques Cross Site Scripting (XSS) e Cross-Site Request Forgery (CSRF), ele realiza muitas pequenas medidas de segurança com as quais já não precisa de se preocupar.

Por exemplo, ele filtra todos os caracteres de controlo das entradas e verifica a validade da codificação UTF-8, para que os dados do formulário estejam sempre limpos. Para caixas de seleção e listas de rádio, ele verifica se os itens selecionados eram realmente das opções oferecidas e se não houve falsificação. Já mencionamos que, para entradas de texto de linha única, ele remove os caracteres de fim de linha que um invasor poderia ter enviado. Para entradas de várias linhas, ele normaliza os caracteres de fim de linha. E assim por diante.

Nette resolve para si riscos de segurança que muitos programadores nem sabem que existem.

O ataque CSRF mencionado consiste no facto de que o invasor atrai a vítima para uma página que, discretamente no navegador da vítima, executa uma requisição ao servidor no qual a vítima está logada, e o servidor acredita que a requisição foi executada pela vítima por sua própria vontade. Portanto, Nette impede o envio de formulários POST de outro domínio. Se, por algum motivo, quiser desativar a proteção e permitir o envio de formulários de outro domínio, use:

$form->allowCrossOrigin(); // ATENÇÃO! Desativa a proteção!

Esta proteção usa um cookie SameSite chamado _nss. Portanto, crie o objeto do formulário antes de enviar a primeira saída, para que o cookie possa ser enviado.

A proteção usando o cookie SameSite pode não ser 100% confiável, por isso é aconselhável ativar também a proteção por token:

$form->addProtection();

Recomendamos proteger desta forma os formulários na parte administrativa do site, que alteram dados sensíveis na aplicação. O framework defende-se contra o ataque CSRF gerando e verificando um token de autorização, que é armazenado na sessão. Portanto, é necessário ter a sessão aberta antes de exibir o formulário. Na parte administrativa do site, a sessão geralmente já está iniciada devido ao login do utilizador. Caso contrário, inicie a sessão com o método Nette\Http\Session::start().

Então, passamos por uma rápida introdução aos formulários em Nette. Tente dar uma olhada no diretório examples na distribuição, onde encontrará mais inspiração.