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.