Validação de formulários
Controlos obrigatórios
Marcamos os controlos obrigatórios com o método setRequired(), cujo argumento é o texto da mensagem de erro, que será exibida se o utilizador não preencher o controlo. 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'); Regras
Adicionamos regras de validação aos controlos usando o método addRule(). O primeiro parâmetro é a regra, o segundo é o texto da mensagem de erro e o terceiro é o argumento da regra de validação.
$form->addPassword('password', 'Senha:') ->addRule($form::MinLength, 'A senha deve ter pelo menos %d caracteres', 8); As regras de validação são verificadas apenas se o utilizador preencher o controlo.
Nette vem com uma série de regras predefinidas, cujos nomes são constantes da classe Nette\Forms\Form. Podemos usar estas regras para todos os controlos:
| constante | descrição | tipo de argumento |
|---|---|---|
Required | controlo obrigatório, alias para setRequired() | – |
Filled | controlo obrigatório, alias para setRequired() | – |
Blank | o controlo não deve ser preenchido | – |
Equal | o valor é igual ao parâmetro | mixed |
NotEqual | o valor não é igual ao parâmetro | mixed |
IsIn | o valor é igual a um dos itens no array | array |
IsNotIn | o valor não é igual a nenhum item no array | array |
Valid | o controlo está preenchido corretamente? (para Condições) | – |
Entradas de texto
Para os controlos addText(), addPassword(), addTextArea(), addEmail(), addInteger(), addFloat(), algumas das seguintes regras também podem ser usadas:
MinLength | comprimento mínimo do texto | int |
MaxLength | comprimento máximo do texto | int |
Length | comprimento no intervalo ou comprimento exato | par [int, int] ou int |
Email | endereço de e-mail válido | – |
URL | URL absoluta | – |
Pattern | corresponde à expressão regular | string |
PatternInsensitive | como Pattern, mas insensível a maiúsculas/minúsculas | string |
Integer | valor inteiro | – |
Numeric | alias para Integer | – |
Float | número | – |
Min | valor mínimo do controlo numérico | int|float |
Max | valor máximo do controlo numérico | int|float |
Range | valor no intervalo | par [int|float, int|float] |
As regras de validação Integer, Numeric e Float convertem diretamente o valor para inteiro ou float, respetivamente. Além disso, a regra URL também aceita um endereço sem esquema (por exemplo, nette.org) e adiciona o esquema (https://nette.org). A expressão em Pattern e PatternIcase deve corresponder a todo o valor, ou seja, como se estivesse envolvida pelos caracteres ^ e $.
Número de itens
Para os controlos addMultiUpload(), addCheckboxList(), addMultiSelect(), as seguintes regras também podem ser usadas para limitar o número de itens selecionados ou ficheiros enviados:
MinLength | número mínimo | int |
MaxLength | número máximo | int |
Length | número no intervalo ou número exato | par [int, int] ou int |
Upload de ficheiros
Para os controlos addUpload(), addMultiUpload(), as seguintes regras também podem ser usadas:
MaxFileSize | tamanho máximo do ficheiro em bytes | int |
MimeType | tipo MIME, curingas permitidos ('video/*') | string|string[] |
Image | imagem JPEG, PNG, GIF, WebP, AVIF | – |
Pattern | nome do ficheiro corresponde à expressão regular | string |
PatternInsensitive | como Pattern, mas insensível a maiúsculas/minúsculas | string |
MimeType e Image exigem a extensão PHP fileinfo. Elas detetam se um ficheiro ou imagem é do tipo desejado com base na sua assinatura e não verificam a integridade de todo o ficheiro. Se uma imagem não está danificada pode ser verificado, por exemplo, tentando carregá-la.
Mensagens de erro
Todas as regras predefinidas, exceto Pattern e PatternInsensitive, têm uma mensagem de erro padrão, então ela pode ser omitida. No entanto, fornecer e formular todas as mensagens sob medida tornará o formulário mais amigável ao utilizador.
Pode alterar as mensagens padrão na configuração, editando os textos no array Nette\Forms\Validator::$messages ou usando um tradutor.
No texto das mensagens de erro, podem ser usadas as seguintes strings de placeholder:
%d | substitui sequencialmente pelos argumentos da regra |
%n$d | substitui pelo n-ésimo argumento da regra |
%label | substitui pelo rótulo do controlo (sem dois pontos) |
%name | substitui pelo nome do controlo (por exemplo, name) |
%value | substitui pelo valor inserido pelo utilizador |
$form->addText('name', 'Nome:') ->setRequired('Preencha por favor %label'); $form->addInteger('id', 'ID:') ->addRule($form::Range, 'pelo menos %d e no máximo %d', [5, 10]); $form->addInteger('id', 'ID:') ->addRule($form::Range, 'no máximo %2$d e pelo menos %1$d', [5, 10]); Condições
Além das regras, também é possível adicionar condições. Elas são escritas de forma semelhante às regras, mas em vez de addRule(), usamos o método addCondition() e, obviamente, não fornecemos nenhuma mensagem de erro (a condição apenas pergunta):
$form->addPassword('password', 'Senha:') // se a senha não tiver mais de 8 caracteres ->addCondition($form::MaxLength, 8) // então deve conter um dígito ->addRule($form::Pattern, 'Deve conter um dígito', '.*[0-9].*'); A condição também pode ser vinculada a outro controlo que não o atual, usando addConditionOn(). Como primeiro parâmetro, fornecemos uma referência ao controlo. Neste exemplo, o e-mail será obrigatório apenas se a caixa de seleção for marcada (o seu valor será true):
$form->addCheckbox('newsletters', 'enviar-me newsletters'); $form->addEmail('email', 'E-mail:') // se a caixa de seleção estiver marcada ->addConditionOn($form['newsletters'], $form::Equal, true) // então exija o e-mail ->setRequired('Insira o endereço de e-mail'); É possível criar estruturas complexas a partir de condições usando elseCondition() e endCondition():
$form->addText(/* ... */) ->addCondition(/* ... */) // se a primeira condição for atendida ->addConditionOn(/* ... */) // e a segunda condição em outro controlo ->addRule(/* ... */) // exija esta regra ->elseCondition() // se a segunda condição não for atendida ->addRule(/* ... */) // exija estas regras ->addRule(/* ... */) ->endCondition() // voltamos à primeira condição ->addRule(/* ... */); Em Nette, é muito fácil reagir ao cumprimento ou não cumprimento de uma condição também no lado do JavaScript usando o método toggle(), veja JavaScript dinâmico.
Referência a outro controlo
Como argumento de uma regra ou condição, também é possível passar outro controlo do formulário. A regra então usará o valor inserido posteriormente pelo utilizador no navegador. Desta forma, é possível, por exemplo, validar dinamicamente que o controlo password contém a mesma string que o controlo password_confirm:
$form->addPassword('password', 'Senha'); $form->addPassword('password_confirm', 'Confirme a senha') ->addRule($form::Equal, 'As senhas inseridas não coincidem', $form['password']); Regras e condições personalizadas
Ocasionalmente, chegamos a uma situação em que as regras de validação incorporadas em Nette não são suficientes e precisamos validar os dados do utilizador à nossa maneira. Em Nette, isso é muito simples!
Aos métodos addRule() ou addCondition(), é possível passar qualquer callback como primeiro parâmetro. Ele recebe o próprio controlo como primeiro parâmetro e retorna um valor booleano indicando se a validação foi bem-sucedida. Ao adicionar uma regra usando addRule(), é possível fornecer argumentos adicionais, que são então passados como segundo parâmetro.
Podemos criar o nosso próprio conjunto de validadores como uma classe com métodos estáticos:
class MyValidators { // testa se o valor é divisível pelo argumento public static function validateDivisibility(BaseControl $input, $arg): bool { return $input->getValue() % $arg === 0; } public static function validateEmailDomain(BaseControl $input, $domain) { // outros validadores } } O uso é então muito simples:
$form->addInteger('num') ->addRule( [MyValidators::class, 'validateDivisibility'], 'O valor deve ser um múltiplo de %d', 8, ); Regras de validação personalizadas também podem ser adicionadas ao JavaScript. A condição é que a regra seja um método estático. O seu nome para o validador JavaScript é formado pela junção do nome da classe sem barras invertidas \, um sublinhado _ e o nome do método. Por exemplo, App\MyValidators::validateDivisibility é escrito como AppMyValidators_validateDivisibility e adicionado ao objeto Nette.validators:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => { return val % args === 0; }; Evento onValidate
Após o envio do formulário, a validação é realizada, onde as regras individuais adicionadas via addRule() são verificadas e, em seguida, o evento onValidate é disparado. O seu handler pode ser usado para validação adicional, tipicamente para verificar a combinação correta de valores em múltiplos controlos do formulário.
Se um erro for detetado, passamos para o formulário usando o método addError(). Ele pode ser chamado num controlo específico ou diretamente no formulário.
protected function createComponentSignInForm(): Form { $form = new Form; // ... $form->onValidate[] = [$this, 'validateSignInForm']; return $form; } public function validateSignInForm(Form $form, \stdClass $data): void { if ($data->foo > 1 && $data->bar > 5) { $form->addError('Esta combinação não é possível.'); } } Erros durante o processamento
Em muitos casos, descobrimos um erro apenas no momento em que estamos a processar um formulário válido, por exemplo, ao inserir um novo item no banco de dados e encontrar uma duplicidade de chaves. Nesse caso, passamos novamente o erro para o formulário usando o método addError(). Ele pode ser chamado num controlo específico ou diretamente no formulário:
try { $data = $form->getValues(); $this->user->login($data->username, $data->password); $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { $form->addError('Senha inválida.'); } } Se possível, recomendamos anexar o erro diretamente ao controlo do formulário, pois ele será exibido ao lado dele ao usar o renderizador padrão.
$form['date']->addError('Desculpe, mas esta data já está ocupada.'); Pode chamar addError() repetidamente para passar várias mensagens de erro ao formulário ou controlo. Pode obtê-las usando getErrors().
Atenção, $form->getErrors() retorna um resumo de todas as mensagens de erro, incluindo aquelas que foram passadas diretamente para controlos individuais, não apenas diretamente para o formulário. Mensagens de erro passadas apenas para o formulário podem ser obtidas via $form->getOwnErrors().
Modificação da entrada
Usando o método addFilter(), podemos modificar o valor inserido pelo utilizador. Neste exemplo, toleraremos e removeremos espaços no código postal:
$form->addText('zip', 'Código Postal:') ->addFilter(function ($value) { return str_replace(' ', '', $value); // removemos espaços do código postal }) ->addRule($form::Pattern, 'Código Postal não está no formato de cinco dígitos', '\d{5}'); O filtro é integrado entre as regras de validação e condições, portanto, a ordem dos métodos importa, ou seja, o filtro e a regra são chamados na mesma ordem que os métodos addFilter() e addRule().
Validação JavaScript
A linguagem para formular condições e regras é muito poderosa. Todas as construções funcionam tanto no lado do servidor quanto no lado do JavaScript. Elas são transferidas em atributos HTML data-nette-rules como JSON. A validação em si é então realizada por um script que captura o evento submit do formulário, percorre os controlos individuais e executa a validação apropriada.
Esse script é netteForms.js e está disponível em várias fontes possíveis:
Pode inserir o script diretamente na página HTML a partir de um CDN:
<script src="https://unpkg.com/nette-forms@3"></script> Ou copiá-lo localmente para a pasta pública do projeto (por exemplo, de vendor/nette/forms/src/assets/netteForms.min.js):
<script src="/path/to/netteForms.min.js"></script> Ou instalar via npm:
npm install nette-forms E, em seguida, carregar e executar:
import netteForms from 'nette-forms'; netteForms.initOnLoad(); Alternativamente, pode carregá-lo diretamente da pasta vendor:
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js'; netteForms.initOnLoad(); JavaScript dinâmico
Quer exibir os campos para inserir o endereço apenas se o utilizador escolher enviar o produto pelo correio? Sem problemas. A chave é o par de métodos addCondition() & toggle():
$form->addCheckbox('send_it') ->addCondition($form::Equal, true) ->toggle('#address-container'); Este código diz que quando a condição é atendida, ou seja, quando a caixa de seleção está marcada, o elemento HTML #address-container será visível. E vice-versa. Assim, colocamos os controlos do formulário com o endereço do destinatário num contêiner com este ID e, ao clicar na caixa de seleção, eles serão ocultados ou exibidos. Isso é garantido pelo script netteForms.js.
Como argumento do método toggle(), é possível passar qualquer seletor. Por razões históricas, uma string alfanumérica sem outros caracteres especiais é entendida como o ID do elemento, ou seja, da mesma forma que se fosse precedida pelo caractere #. O segundo parâmetro opcional permite inverter o comportamento, ou seja, se usássemos toggle('#address-container', false), o elemento seria exibido apenas se a caixa de seleção não estivesse marcada.
A implementação padrão em JavaScript altera a propriedade hidden dos elementos. No entanto, podemos facilmente alterar o comportamento, por exemplo, adicionando uma animação. Basta sobrescrever o método Nette.toggle em JavaScript com a sua própria solução:
Nette.toggle = (selector, visible, srcElement, event) => { document.querySelectorAll(selector).forEach((el) => { // ocultamos ou exibimos 'el' de acordo com o valor 'visible' }); }; Desativação da validação
Às vezes, pode ser útil desativar a validação. Se o pressionamento de um botão de envio não deve realizar a validação (adequado para botões Cancelar ou Visualizar), desativamo-la com o método $submit->setValidationScope([]). Se deve realizar apenas validação parcial, podemos especificar quais campos ou contêineres de formulário devem ser validados.
$form->addText('name') ->setRequired(); $details = $form->addContainer('details'); $details->addInteger('age') ->setRequired('age'); $details->addInteger('age2') ->setRequired('age2'); $form->addSubmit('send1'); // Valida o formulário inteiro $form->addSubmit('send2') ->setValidationScope([]); // Não valida nada $form->addSubmit('send3') ->setValidationScope([$form['name']]); // Valida apenas o controlo name $form->addSubmit('send4') ->setValidationScope([$form['details']['age']]); // Valida apenas o controlo age $form->addSubmit('send5') ->setValidationScope([$form['details']]); // Valida o contêiner details setValidationScope não afeta o evento onValidate no formulário, que será chamado sempre. O evento onValidate num contêiner será disparado apenas se este contêiner estiver marcado para validação parcial.