Back-End

25 abr, 2007

Validação de Formulários – 5º e última parte: Validando no servidor

Publicidade

(Os arquivos utilizados nestas matérias podem ser baixados em meu site, através do endereço: http://www.galvao.eti.br/downloads/validateForm.zip)

Em nosso último encontro finalizamos a validação do lado cliente utilizando a linguagem JavaScript. Neste quinto e último artigo da série é hora de cuidarmos do lado servidor. Primeiro vamos entender porque é importante validar dos dois lados:

É importante validarmos com JavaScript para evitar uma carga desnecessária para o servidor. Se o formulário não está corretamente preenchido, já barramos o usuário antes mesmo do envio.

É importante validarmos com PHP por causa da facilidade com que se pode desabilitar a execução de scripts desenvolvidos em JavaScript. Em browsers como o Firefox, por exemplo, consegue-se isto com apenas 4 cliques.

Já cuidamos da primeira parte, agora nos resta garantir que o servidor também validará a obrigatoriedade e especialidade dos dados enviados:

Validação de Formulários – 5ª e última parte: Validando no servidor

Antes de começarmos a falar de PHP, vejamos um exemplo de formulário que utilizará nossas rotinas de validação:


  1 <html>
  2 <head>
  3 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  4 <title>Teste das funções de form</title>
  5 <script language="javascript" src="validateForm.js">
  6 </script>
  7 </head>
  8 
  9 <body onLoad="retrieveDate();">
 10 <font color="#FF0000" size="2" face="Verdana, Arial, Helvetica, sans-serif">*</font><font face="Verdana, Arial, Helvetica, sans-serif" size="2"> Campos obrigatórios.</font><br><br>
 11 <form name="form1" method="post" action="validateForm.php">
 12   <table width="100%" border="1" cellpadding="3" cellspacing="3" bordercolor="#FFFFFF" bgcolor="#CCCCCC" style="font-family:Verdana, Arial, Helvetica, sans-serif; font-size:12px">
 13     <tr>
 14       <td width="25%"><font color="#FF0000">*</font>Nome:</td>
 15       <td width="75%">
 16         <input name="RL02XXXXNome" type="text">      
 17       </td>
 18     </tr>
 19     <tr>
 20       <td width="25%"><font color="#FF0000">*</font>Telefone:</td>
 21       <td width="75%">
 22         <input name="RA09TELETelefone" type="text" size="10" maxlength="9" onKeyUp="fmtTel(this, event);" autocomplete="off"> (Digite apenas números)
 23       </td>
 24     </tr>
 25     <tr>
 26       <td>Sexo:</td>
 27       <td>
 28         <input name="sexo" type="radio" value="m">
 29       M 
 30       <input name="sexo" type="radio" value="f">
 31       F
 32       </td>
 33     </tr>
 34     <tr>
 35       <td>Cursos:</td>
 36       <td>
 37         <input type="checkbox" name="cursos[]" value="php">
 38         PHP
 39         <input type="checkbox" name="cursos[]" value="js">
 40         JS
 41       </td>
 42     </tr>
 43     <tr>
 44       <td width="25%">Foto:</td>
 45       <td width="75%">
 46         <input name="foto" type="file" id="foto">      
 47       </td>
 48     </tr>
 49     <tr>
 50       <td>Estado:</td>
 51       <td>
 52         <select name="Estado">
 53           <option value="" selected>Selecione:</option>
 54 		  <optgroup label="Brasil">
 55 			<option value="0">RS</option>
 56 			<option value="1">SP</option>
 57 			<option value="3">RJ</option>
 58 		  </optgroup>
 59 		  <optgroup label="EUA">
 60 			<option value="0">NY</option>
 61 			<option value="1">NJ</option>
 62 			<option value="3">TX</option>
 63 		  </optgroup>
 64         </select>
 65       </td>
 66     </tr>
 67     <tr>
 68       <td><font color="#FF0000">*</font>Data de Nasc.: </td>
 69       <td>
 70         <input type="text" name="RA10DTNSData_Nascimento" onKeyUp="fmtDate(this, event);" size="11" maxlength="10" autocomplete="off">      
 71       </td>
 72     </tr>
 73     <tr>
 74       <td><font color="#FF0000">*</font>Data de cadastro: </td>
 75       <td>
 76         <input name="RA10DTFTData_Cadastro" type="text" onKeyUp="fmtDate(this, event);" size="11" maxlength="10">      </td>
 77     </tr>
 78     <tr>
 79       <td><font color="#FF0000">*</font>Senha:</td>
 80       <td>
 81         <input type="password" name="RN04PWD1Senha">
 82       </td>
 83     </tr>
 84     <tr>
 85       <td><font color="#FF0000">*</font>Confirme a senha: </td>
 86       <td>
 87         <input type="password" name="RN04PWD2Confirme_Senha">
 88       </td>
 89     </tr>
 90     <tr>
 91       <td><font color="#FF0000">*</font>e-mail</td>
 92       <td>
 93         <input name="RA04EMILEmail" type="text">      
 94       </td>
 95     </tr>
 96     <tr>
 97       <td colspan="2">
 98         <input name="Enviar" type="button" id="Enviar" onClick="validateForm();" value="Enviar">
 99       </td>
100     </tr>
101   </table>
102 </form>
103 </body>
104 </html>

Apenas alguns pontos merecem destaque neste código (o restante deve ser familiar para qualquer pessoa já habituada com a linguagem HTML):

Linha 9: Aqui fazemos uma chamada para a função retrieveDate, que através de uma requisição AJAX, consultará a data do servidor (veja)

Linhas 22 e 70: Em ambas as linhas utilizamos um parâmetro “autocomplete” definido como “off” (desligado). Isto impede que o browser tente completar automaticamente o valor que o usuário está digitando, o que certamente atrapalharia nossa rotina de formatação.

Agora vamos tratar do código PHP.

Os leitores perceberão como o código PHP é similar ao código JavaScript. Isto se deve ao fato de que ambas as linguagens possuem sintaxes muito semelhantes. Apenas devemos ter cuidado com uma característica da linguagem JavaScript que não está presente na linguagem PHP: a informação do tipo de campo (o parâmetro type da tag input, por exemplo) onde a informação foi preenchida.

Com isso teremos uma pequena modificação de nosso código, além de um cuidado extra: se você possui um campo que não deverá ser validado, o seu parâmetro name não pode se encaixar em nenhum dos formatos que definimos no 2º artigo. Resumindo, uma forma fácil de evitar isto é simplesmente não utilizando nenhuma letra maiúscula.

Vamos então ao código de nosso script de validação server-side, que comentarei à seguir:


  1 <?php
  2 $serverDate = date('U');
  3 
  4 if ($_POST)
  5 {
  6     $fPreVal = '';
  7     
  8     // RegExs para campos alfa, numéricos e e-mail
  9     $rxLet = '\D';
 10     $rxNum = '\d';
 11     $rxMail = '.+\@{1}.+';
 12     
 13     $errMsg = '';
 14     $errCnt = 0;
 15     
 16     foreach($_POST as $chv => $val)
 17     {
 18         $fLabel = substr($chv, 8);
 19         $fValida = substr($chv, 0, 8);
 20         
 21         // Caractere de controle se é ou não campo requerido (R)
 22         $isReq = substr($fValida, 0, 1);
 23         
 24         // Tipo do valor esperado: (L)etras, (N)úmeros ou (A)mbos
 25         $vTypeExpected = substr($fValida, 1, 1);
 26         
 27         // Número mínimo de caracteres
 28         $rLen = (int)substr($fValida, 2, 2);
 29         
 30         // Tipo de validação
 31         $vType = substr($fValida, 4,4);
 32         
 33         // Valor do campo
 34         $fVal = $val;
 35         
 36         // Número de caracteres do valor digitado pelo usuário
 37         $fLen = strlen($fVal);
 38         
 39         // Define as mensagens de erro
 40         $reqMsg = "> O campo " . $fLabel . " é obrigatório.<BR>";
 41         $minMsg = "> O campo " . $fLabel . " deve conter no mínimo " + rLen + " caractere(s).<BR>";
 42         $invMsg = "> O campo " . $fLabel . " não contém um valor válido.<BR>";
 43         $alfMsg = "> O campo " . $fLabel . " deve conter apenas letras.<BR>";
 44         $numMsg = "> O campo " . $fLabel . " deve conter apenas números.<BR>";
 45         $pncMsg = "> As senhas digitadas não conferem.<BR>";
 46         
 47         // Armazena a primeira senha digitada
 48         if ($fPreVal == '' and $fLen > 0 and substr($vType, 0, 2) == 'PW')
 49         {
 50             $fPreVal = $fVal;
 51         }
 52         
 53         // Campo de preenchimento obrigatório
 54         if ($isReq == 'R')
 55         {
 56             if ($fLen < $rLen)
 57             {
 58                 $errMsg .= $reqMsg;
 59                 $errCnt++;
 60             }
 61         }
 62         
 63         if ($fLen > 0)
 64         {
 65             // Faz a checagem de validade do valor
 66             $resCheckVal = checkVal($vType);
 67             
 68             if ($resCheckVal != 'Ok')
 69             {
 70                 eval('$errMsg .= ' . $resCheckVal . 'Msg;');
 71                 $errCnt++;
 72             }
 73             
 74             // É campo somente letras
 75             if ($vTypeExpected == 'L')
 76             {
 77                 if (preg_match("/$rxNum/", $fVal))
 78                 {
 79                     $errMsg .= $alfMsg;
 80                     $errCnt++;
 81                 }
 82             }
 83             else if ($vTypeExpected == 'N') // É campo somente números
 84             {
 85                 if (preg_match("/$rxLet/", $fVal))
 86                 {
 87                     $errMsg .= $numMsg;
 88                     $errCnt++;
 89                 }
 90             }
 91         }
 92 	}
 93 	
 94 	if ($errCnt > 0)
 95 	{
 96         if ($errCnt > 1)
 97         {
 98             $strPlural = 's';
 99         }
100         else
101         {
102             $strPlural = '';
103         }
104         
105         echo $errCnt . " erro" . $strPlural . " encontrado" . $strPlural .":<BR><BR>" . $errMsg;
106 	}
107     else
108     {
109         echo "Tudo OK!";
110     }
111 }
112 
113 function checkVal($cType)
114 {
115     global $arrMonths;
116     global $fPreVal;
117     global $fVal;
118     global $serverDate;
119     global $rxMail;
120     
121     $vPart1 = substr($cType, 0, 2);
122     $vPart2 = substr($cType, 2, 2);
123     
124     if ($vPart1 == 'DT')
125     {
126         $arrDate = explode('/', $fVal);
127         
128         $clientDate = mktime(0, 0, 0, (int)$arrDate[1], (int)$arrDate[0], (int)$arrDate[2]).'<br>';
129         
130         if ($vPart2 == 'NS')
131         {
132             if ((int)$clientDate >= (int)$serverDate)
133             {
134                 return '$inv';
135             }
136         }
137         else if ($vPart2 == 'FT')
138         {
139             if ($clientDate <= $serverDate)
140             {
141                 return '$inv';
142             }
143         }
144     }
145     else if ($vPart1 == 'EM')
146     {
147 		if (!preg_match("/$rxMail/", $fVal))
148 		{
149 			return '$inv';
150 		}
151     }
152     else if ($vPart1 == 'PW')
153     {
154         if ($fPreVal != '' and $fVal != $fPreVal)
155         {
156             return '$pnc';
157         }
158     }
159     else if ($vPart1 == 'TE')
160     {
161         if (!preg_match('/\d{4}\-\d{4}/', $fVal))
162         {
163             return '$inv';
164         }
165     }
166     
167 	return 'Ok';
168 }
169 ?>

Linha2: Através da função date do PHP definimos a data atual do servidor no formato ‘U’, que nos retorna a data no formato conhecido por Unix Time (ou Unix Timestamp ou ainda POSIX Time), que nada mais é do que o número de segundos desde à meia-noite do dia 1 de janeiro de 1970. Esta forma de capturar a data é especialmente simples de se usar para a validação que precisaremos para Data de Nascimento ou Data Futura, pois poderemos utilizar simples operações matemáticas (veja mais abaixo).

Linha 4: Testamos booleanamente o array especial _POST. Este array será vazio se nosso script estiver sendo acessado diretamente e não através do envio de um formulário. No caso de estar vazio este teste retornará falso, impedindo que nosso script comece um processamento que será inútil, já que não há informação para validar.

Linhas 6 à 14: Este pedaço de código é praticamente idêntico ao JavaScript, com a única diferença na forma de representação das Expressões Regulares: aqui representaremos elas como strings simples, que depois serão utilizadas nas funções de RegEx do PHP.

Linha 16: Esta linha inicia o loop de leitura dos campos de formulários (como a linha 91 em nosso script JavaScript), armazenando cada nome de campo em uma variável chamada chv e cada valor entrado pelo usuário em uma variável chamada val.

Linhas 18 à 45: Também são idênticas ao código JavaScript. Vale notarmos apenas duas diferenças: a primeira é que enquanto o JavaScript trabalha com métodos e propriedades dos objetos DOM (separando cada coisa por um ponto), o PHP trabalha diretamente com funções, no formato nome_função(onde_será_aplicada). A segunda é que como trabalharemos com retorno HTML, ao invés de texto dentro de uma caixa de alerta, convertemos os sinais de maior para entidades HTML, ou seja: >

Linhas 48 à 51: Aqui jogamos o valor da primeira senha digitada para dentro da variável fPreVal, especialmente criada para isso. A diferença para o nosso código JavaScript é que como não temos a informação do tipo de campo, apenas testamos se a variável ainda está vazia e segue a padronização de senha (PW).

Linhas 54 à 111: Novamente fazemos o processamento idêntico ao que fazíamos em JavaScript, mudando apenas a forma sintática de uma linguagem para outra, sendo que as únicas diferenças são a impossibilidade de se checar o tipo de campo e a utilização da função preg_match do PHP no lugar do método match do JavaScript. Ainda incluímos uma mensagem de que tudo está OK caso os dados tenham sido validados corretamente.

Linha 113: Aqui começamos a função de checagem de valores especiais e é onde notaremos diferenças realmente significativas que explicaremos à seguir.

Linhas 115 à 119: Como não estamos lidando com objetos DOM, e sim com variáveis simples, precisamos informar ao interpretador PHP que estas variáveis são globais, ou seja, são exatamente as mesmas que utilizamos no restante do nosso código. Se não o fizermos, o interpretador PHP as considerará variáveis diferentes e exclusivas da própria função, o que chamamos de escopo.

Linhas 121 à 124: Novamente um código conhecido: pegamos os dois primeiros e os dois últimos caracteres de nossa string de especialização, definida na 2ª parte desta matéria e começamos os testes para vermos que tipo de validação especial deverá ser feita.

Linha 126: No caso de um campo do tipo data (‘DT’), primeiramente ‘quebraremos’ a data em seus elementos (dia, mês e ano) e jogaremos para dentro de um array. Tudo isto é feito pela função explode, que separa uma string tendo como base um caractere à ser tratado como indicador de separação.

Linha 128: Através da função mktime utilizaremos estes elementos para calcular o valor que eles representam em Unix Time. Com isso estaremos de posse de dois números inteiros, um representando a data atual (serverDate) e um representando a data entrada pelo usuário (clientDate).

Linhas 130 à 136: Se for uma data de nascimento (DT seguido de NS) é sinal de que o número inteiro gerado pela função mktime tem que ser MENOR do que o gerado pela data do sistema – afinal de contas em uma data passada haviam decorrido menos segundos em relação ao ano de 1970 do que a data presente. Se não for este o caso, retornamos a mensagem de valor inválido.

Linhas 137 à 143: Da mesma forma, se trabalharmos com uma data futura, o número de segundos em relação à 1970 deverá ser obrigatoriamente MAIOR do que a data presente. Caso não o seja, retornamos a mensagem de valor inválido novamente.

Linhas 145 à 151: No caso de um endereço de e-mail utilizamos a função preg_match em conjunto com a expressão regular definida anteriormente para determinar a presença de qualquer número de caracteres, uma e somente uma arroba e novamente qualquer número de caracteres. Usamos o operador de negação (‘!’) para mostramos que nosso if está testando se o valor não está de acordo com a expressão regular.

Linhas 152 à 158: Se estamos tratando de senhas e a primeira senha foi digitada, testamos se elas são diferentes. Em caso afirmativo retornamos a mensagem de erro adequada.

Linhas 159 à 165: E no caso de um número telefônico testamos pela expressão regular que já vimos no artigo anterior: 4 dígitos, um hífen e mais 4 dígitos.

Linha 167: À exemplo de nosso JavaScript, esta linha retorna OK pura e simplesmente, pois se o valor do campo chegou até ela é porque não ‘disparou’ nenhum erro.

Conclusão:

A validação de dados de um formulário é fundamental, tanto para evitar cargas desnecessárias ao servidor, quanto para obter o máximo de garantia possível de que as informações recebidas são o mais próximo possível do esperado.

É um processo de tal importância que seu impacto abrange desde o armazenamento de informações até a rotina organizacional da empresa.

A solução sugerida nesta série de artigos não tem a pretensão de ser perfeita, mas tenta mostrar que, com um pouco de criatividade e conhecimento do que cada linguagem / ferramenta pode nos oferecer, podemos tornar nossas aplicações web muito mais robustas e profissionais.

Até a próxima, pessoal!