Formulários utilizados autônomos

Os Formulários Nette facilitam drasticamente a criação e o processamento de formulários web. Você pode usá-los em suas aplicações completamente por conta própria sem o resto da estrutura, o que demonstraremos neste capítulo.

Entretanto, se você utiliza o aplicativo Nette e apresentadores, há um guia para você: formulários em apresentadores.

Primeiro Formulário

Tentaremos escrever um simples formulário de registro. Seu código será parecido com este (código completo):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');

E vamos renderizá-lo:

$form->render();

e o resultado deve ser parecido com este:

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

Agora vamos reavivar a forma. Perguntando $form->isSuccess(), descobriremos se o formulário foi enviado e se foi preenchido de forma válida. Se for o caso, despejaremos os dados. Após a definição do formulário, acrescentaremos:

if ($form->isSuccess()) {
	echo 'O formulário foi preenchido e enviado corretamente';
	$data = $form->getValues();
	// $data->name contém 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 mudar isso mais tarde. A variável $data contém as chaves name e password com os dados inseridos pelo usuário.

Normalmente, enviamos os dados diretamente para processamento posterior, que pode ser, por exemplo, inserido no banco de dados. Entretanto, pode ocorrer um erro durante o processamento, por exemplo, o nome de usuário já está tomado. Neste caso, passamos o erro de volta ao formulário usando addError() e deixamos que ele seja redesenhado, com uma mensagem de erro:

$form->addError('Sorry, username is already in use.');

Após o processamento do formulário, redirecionaremos para a página seguinte. Isto evita que o formulário seja reapresentado involuntariamente clicando no botão refresh, back, ou movendo o histórico do navegador.

Por padrão, o formulário é enviado usando o método POST para a mesma página. Ambos podem ser modificados:

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

E isso é tudo :-) Temos uma forma funcional e perfeitamente segura.

Tente adicionar mais controles de formulário.

Acesso aos controles

A forma e seus controles individuais são chamados de componentes. Eles criam uma árvore de componentes, onde a raiz é a forma. Você pode acessar os controles individuais da seguinte forma:

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

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

Os controles são removidos com o uso de controles não ajustados:

unset($form['name']);

Regras de validação

A palavra valido foi usada aqui, mas o formulário ainda não tem regras de validação. Vamos corrigi-lo.

O nome será obrigatório, portanto o marcaremos com o método setRequired(), cujo argumento é o texto da mensagem de erro que será exibida se o usuário não a preencher. Se não for apresentado nenhum argumento, será utilizada a mensagem de erro padrão.

$form->addText('name', 'Name:')
	->setRequired('Please enter a name.');

Tente enviar o formulário sem o nome preenchido e você verá que uma mensagem de erro é exibida e o navegador ou servidor irá rejeitá-lo até que você o preencha.

Ao mesmo tempo, você não poderá enganar o sistema digitando apenas espaços na entrada, por exemplo. De jeito nenhum. A Nette apara automaticamente os espaços em branco à esquerda e à direita. Experimente. É algo que você deve fazer sempre com cada entrada de linha, mas muitas vezes é esquecido. A Nette o faz automaticamente. (Você pode tentar enganar os formulários e enviar uma cadeia de várias linhas como o nome. Mesmo aqui, Nette não será enganado e as quebras de linha mudarão para espaços).

O formulário é sempre validado no lado do servidor, mas a validação JavaScript também é gerada, o que é rápido e o usuário sabe do erro imediatamente, sem ter que enviar o formulário para o servidor. Isto é tratado pelo script netteForms.js. Acrescente-o à página:

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

Se você olhar no código fonte da página com formulário, você pode notar que Nette insere os campos necessários em elementos com uma classe CSS required. Tente adicionar o seguinte estilo ao modelo, e a etiqueta “Name” (Nome) será vermelha. Elegantemente, marcamos os campos requeridos para os usuários:

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

Regras de validação adicionais serão adicionadas pelo método addRule(). O primeiro parâmetro é a regra, o segundo é novamente o texto da mensagem de erro e o argumento da regra de validação opcional pode seguir. O que isso significa?

O formulário terá outra entrada opcional idade com a condição, que deve ser um número (addInteger()) e em certos limites ($form::Range). E aqui usaremos o terceiro argumento de addRule(), a própria faixa:

$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);

Se o usuário não preencher o campo, as regras de validação não serão verificadas, pois o campo é opcional.

Obviamente, há espaço para uma pequena refatoração. Na mensagem de erro e no terceiro parâmetro, os números são listados em duplicata, o que não é o ideal. Se estivéssemos criando um formulário multilíngüe e a mensagem contendo números tivesse que ser traduzida para vários idiomas, seria mais difícil mudar os valores. Por este motivo, podem ser utilizados caracteres substitutos %d:

	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);

Vamos voltar ao campo password, torná-lo requerido, e verificar o comprimento mínimo da senha ($form::MinLength), novamente usando os caracteres substitutos na mensagem:

$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);

Adicionaremos um campo passwordVerify ao formulário, onde o usuário digita a senha novamente, para verificação. Usando regras de validação, verificamos se as duas senhas são as mesmas ($form::Equal). E como argumento, fazemos uma referência à primeira senha usando colchetes:

$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();

Usando setOmitted(), marcamos um elemento cujo valor realmente não nos interessa e que existe apenas para validação. Seu valor não é passado para $data.

Temos um formulário totalmente funcional com validação em PHP e JavaScript. As capacidades de validação da Nette são muito mais amplas, você pode criar condições, exibir e ocultar partes de uma página de acordo com elas, etc. Você pode descobrir tudo no capítulo sobre validação de formulários.

Valores por default

Muitas vezes definimos valores padrão para controles de formulários:

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

Muitas vezes é útil definir valores padrão para todos os controles ao mesmo tempo. Por exemplo, quando o formulário é usado para editar registros. Nós lemos o registro do banco de dados e o definimos como valores padrão:

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

Ligue para setDefaults() após definir os controles.

Renderização do formulário

Por padrão, o formulário é apresentado como uma tabela. Os controles individuais seguem as diretrizes básicas de acessibilidade da web. Todas as etiquetas são geradas como <label> e estão associados às suas entradas, clicando na etiqueta move o cursor sobre a entrada.

Podemos definir quaisquer atributos HTML para cada elemento. Por exemplo, acrescente um espaço reservado:

$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');

Há realmente muitas maneiras de renderizar uma forma, por isso é um capítulo dedicado à renderização.

Mapeamento para as classes

Vamos voltar para o processamento de dados. O método getValues() devolveu os dados submetidos como um objeto ArrayHash. Como esta é uma classe genérica, algo como stdClass, nos faltará alguma conveniência ao trabalhar com ela, como preenchimento de código para propriedades em editores ou análise de código estático. Isto poderia ser resolvido tendo uma classe específica para cada formulário, cujas propriedades representam os controles individuais. Por exemplo, a classe

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

A partir do PHP 8.0, você pode usar esta elegante notação que usa um construtor:

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

Como dizer à Nette para nos retornar dados como objetos desta classe? Mais fácil do que você pensa. Tudo que você precisa fazer é especificar o nome da classe ou objeto a ser hidratado como um parâmetro:

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

Um 'array' também pode ser especificado como um parâmetro, e então os dados retornam como uma matriz.

Se as formas consistem em uma estrutura de vários níveis composta de recipientes, crie uma classe separada para cada um deles:

$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 sabe pelo tipo de propriedade $person que deve mapear o container para a classe PersonFormData. Se a propriedade contiver um conjunto de containers, forneça o tipo array e passe a classe a ser mapeada diretamente para o container:

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

Você pode gerar uma proposta para a classe de dados de um formulário usando o método Nette\Forms\Blueprint::dataClass($form), que a imprimirá na página do navegador. Em seguida, basta clicar para selecionar e copiar o código em seu projeto.

Botões de submissão múltipla

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

$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');

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

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

Não omitir o $form->isSuccess() para verificar a validade dos dados.

Quando um formulário é apresentado com a chave Enter, ele é tratado como se tivesse sido apresentado com o primeiro botão.

Proteção contra vulnerabilidades

Nette Framework coloca um grande esforço para ser seguro e como os formulários são a entrada mais comum dos usuários, os formulários Nette são tão bons quanto impenetráveis.

Além de proteger os formulários contra ataques de vulnerabilidades conhecidas, como o Cross-Site Scripting (XSS) e o Cross-Site Request Forgery (CSRF ), ele faz muitas pequenas tarefas de segurança que você não precisa mais pensar.

Por exemplo, ele filtra todos os caracteres de controle 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 que os itens selecionados foram realmente dos oferecidos e que não houve falsificação. Já mencionamos que para entrada de texto de uma linha, ele remove caracteres de fim de linha que um atacante poderia enviar para lá. Para entradas de várias linhas, ele normaliza os caracteres de fim de linha. E assim por diante.

Nette corrige vulnerabilidades de segurança para você que a maioria dos programadores não tem idéia de que existem.

O referido ataque do CSRF é que um agressor atrai a vítima para visitar uma página que silenciosamente executa um pedido no navegador da vítima ao servidor onde a vítima está atualmente logada, e o servidor acredita que o pedido foi feito pela vítima à sua vontade. Portanto, a Nette impede que o formulário seja submetido via POST a partir de outro domínio. Se por algum motivo você quiser desligar a proteção e permitir que o formulário seja submetido a partir de outro domínio, use:

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

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

A proteção de cookies do SameSite pode não ser 100% confiável, por isso é uma boa idéia ativar a proteção simbólica:

$form->addProtection();

É fortemente recomendado aplicar esta proteção aos formulários em uma parte administrativa de seu pedido que altera dados sensíveis. A estrutura protege contra um ataque CSRF gerando e validando o token de autenticação que é armazenado em uma sessão (o argumento é a mensagem de erro mostrada se o token tiver expirado). É por isso que é necessário ter uma sessão iniciada antes de exibir o formulário. Na parte administrativa do site, a sessão normalmente já é iniciada, devido ao login do usuário. Caso contrário, inicie a sessão com o método Nette\Http\Session::start().

Portanto, temos uma rápida introdução aos formulários em Nette. Tente procurar no diretório de exemplos na distribuição para obter mais inspiração.

versão: 4.0