Todas as máquinas consomem entrada, executam algum tipo de trabalho e produzem saída. Um telefone, por exemplo, converte energia sonora em um sinal elétrico e retorna para áudio para possibilitar a conversa. Um motor absorve combustível (vapor, fissão, gasolina ou esforço físico) e transforma-o em trabalho. E um liquidificador devora rum, gelo, limão e curaçau e mistura vigorosamente para produzir um Mai Tai. (Ou, caso prefira algo mais metropolitano, experimente um pouco de champanhe e néctar de pêra para apreciar um Bellini. O liquidificador é uma máquina realmente incrível e flexível.)
Como software transforma dados, cada aplicativo também é uma máquina — apesar de ser uma "virtual", considerando a ausência de partes físicas. Um compilador, por exemplo, espera o código de origem como entrada e transforma-o em código binário adequado para execução. Um modelador de clima produz previsões baseadas em medições históricas. E um editor de imagem consome e emite pixels, aplicando regras a cada pixel ou grupos de pixels para, digamos, tornar uma imagem mais nítida ou estilizá-la.
Exatamente como qualquer outra máquina, um aplicativo de software espera determinadas matérias primas, como uma lista de números, dados encapsulados em um esquema XML ou um protocolo. Se um programa receber o material errado — divergente em tipo ou formato — o resultado será provavelmente imprevisível, até mesmo catastrófico. Como o ditado diz: "Tudo que entra, sai".
Praticamente todos os problemas não triviais requerem que você filtre bons dados dos ruins, rejeite os dados ruins para evitar saída errante ou ambos. Esse é certamente o caso para um aplicativo da Web do PHP. Se a entrada vem de um formulário manual ou de um pedido programático Asynchronous JavaScript + XML (Ajax), o programa deve examinar cuidadosamente as informações recebidas antes de qualquer computação poder ocorrer. Um valor numérico pode precisar estar em um determinado intervalo ou ser restringido a um número inteiro. Um valor pode precisar corresponder a um formato específico, como um código postal de entrega. Por exemplo, um CEP nos EUA tem cinco dígitos mais um qualificador opcional "Mais 4" composto por um hífen e quatro dígitos adicionais. Outras strings podem precisar ter um determinado número de caracteres, também, como duas letras para a abreviação de um estado dos EUA. Strings são especialmente nefandas: um aplicativo PHP deve permanecer vigilante com relação a um ator malicioso integrando consultas SQL, código JavaScript ou outro código capaz de alterar o comportamento do aplicativo ou burlar a segurança.
Mas como um programa sabe se a entrada é numérica ou está em conformidade com uma convenção, como um código de endereçamento postal? Fundamentalmente, para executar uma correspondência é necessário um pequeno analisador — criar uma máquina de estado, ler entrada, processar tokens, monitorar estado e produzir um resultado. No entanto, mesmo um analisador simples pode ser difícil de criar e manter.
Felizmente, análises de correspondência de padrão são tão comumente necessárias na computação que uma taquigrafia especial e, sim, mecanismo evoluíram ao longo do tempo (desde a aurora do UNIX® para tornar leve o trabalho da tarefa. Uma expressão regular (regex) descreve padrões em uma notação concisa e legível. Considerando uma regex e um dado, um mecanismo da regex determina se o dado corresponde a um padrão e, se uma correspondência for encontrada, qual foi a correspondência.
Segue um exemplo resumido da aplicação de uma regex, retirado do utilitário de linha de
comando do UNIX grep, que procura um padrão especificado entre o conteúdo de um ou mais arquivos de texto do
UNIX.
O comando grep -i -E '^Bat' procura a seqüência
beginning-of-line (indicada pelo circunflexo, [^]), seguido, imediatamente por letras maiúsculas ou
minúsculas
b, a e t (a opção -i ignora maiúsculas e minúsculas em
correspondências padrão, portanto, B e b
são equivalentes, por exemplo). Portanto, considerando o arquivo heroes.txt:
Lista 1. heroes.txt
Catwoman
Batman
The Tick
Black Cat
Batgirl
Danger Girl
Wonder Woman
Luke Cage
The Punisher
Ant Man
Dead Girl
Aquaman
SCUD
Blackbolt
Martian Manhunter
|
O comando grep acima mencionado produziria duas correspondências:
Batman Batgirl |
O PHP oferece duas interfaces de programação de regex, uma para Portable Operating System Interface (POSIX) e outra para Perl Compatible Regular Expressions (PCRE). Em geral, a segunda interface é preferida, pois PCRE é muito mais poderosa do que a implementação POSIX, oferecendo todos os operadores encontrados no Perl. Leia a documentação do PHP para saber mais sobre as chamadas de função regex da POSIX (consulte Recursos). Aqui, foco os recursos da PCRE.
A regex da PCRE do PHP contém operadores a serem correspondidos com relação aos caracteres específicos e outros operadores; com relação a um local específico, como o início ou fim de uma string; ou com relação ao início ou fim de uma palavra. Uma regex também pode descrever alternativas, que você pode descrever como "isto" ou "aquilo"; repetição com comprimento fixo, variável ou indefinido; configura os caracteres (por exemplo, "qualquer uma das letras de a até m"); e classes ou tipos de caracteres (caracteres para impressão ou pontuação), entre outras técnicas. Operadores especiais em regexes também permitem agrupamento — uma maneira de aplicar um operador a outros operadores em um grupo.
A Tabela 1 mostra alguns dos operadores comuns da regex. Você pode concatenar e combinar os primitivos da Tabela 1 (e outros operadores) e usá-los em combinação para construir regexes (muito) complexas.
Tabela 1. Operadores Comuns da Regex
| Operador | Propósito |
|---|---|
| . (ponto) | Corresponder a qualquer caractere único |
| ^ (circunflexo) | Corresponder à string vazia que ocorre no início de uma linha ou string |
| $ (símbolo do dólar) | Corresponder à string vazia que ocorre no final de uma linha |
| A | Corresponder a uma letra maiúscula A |
| a | Corresponder a uma letra minúscula a |
| \d | Corresponder a qualquer dígito único |
| \D | Corresponder a qualquer caractere não dígito |
| \w | Corresponder a qualquer caractere alfanumérico; um sinônimo é
[:alnum:]
|
| [A-E] | Corresponder a qualquer letra maiúscula A, B, C, D ou E |
| [^A-E] | Corresponder a qualquer caractere, exceto à letra maiúscula A, B, C, D ou E |
| X? | Corresponder a nenhuma ou a uma letra maiúscula X |
| X* | Corresponder a zero ou mais letras maiúsculas X |
| X+ | Corresponder a uma ou mais letras maiúsculas X |
| X{n} | Corresponder exatamente a n letras maiúsculas X |
| X{n,m} | Corresponder a pelo menos n e não mais do que m letras maiúsculas X ; se você omitir m, a expressão tenta corresponder pelo menos n X |
| (abc|def)+ | Corresponder uma sequência de pelo menos um abc e def; abc e def corresponderiam |
Segue um exemplo de um uso comum de uma regex. Digamos que seu Web site precise que cada
usuário crie um login.
Cada nome de usuário deve ter pelo menos três, mas não mais do que 10 caracteres alfanuméricos e deve começar
com uma letra.
Para impingir essas especificações, você poderia usar a seguinte regex para validar o nome do usuário quando
é enviado a seu aplicativo:
^[A-Za-z][A-Za-z0-9_]{2,9}$.
O circunflexo corresponde ao início da string. O primeiro conjunto,
[A-Za-z], representa qualquer letra. O segundo conjunto, [A-Za-z0-9_]{2,9},
representa uma série de pelo menos duas e até nove de qualquer letra, qualquer dígito e o sublinhado.
E o símbolo de dólar ($) corresponde ao final da string.
À primeira vista, o símbolo de dólar pode parecer desnecessário, mas é crítico. Se você omiti-lo, sua regex corresponderá a qualquer string que comece com uma letra, contenha de dois a nove caracteres alfanuméricos e qualquer número de qualquer outro caractere. Ou seja, sem o símbolo de dólar para fazer a âncora do final da string, uma string muito longa com um prefixo correspondente, como "martin1234-cruft", produziria um falso positivo.
O PHP fornece funções para localizar correspondências em texto, substituir cada correspondência por outro texto (através de procura e substituição) e para localizar correspondências entre os elementos de uma lista. As funções são:
-
preg_match() -
preg_match_all() -
preg_replace() -
preg_replace_callback() -
preg_grep() -
preg_split() -
preg_last_error() -
preg_quote()
Para demonstrar as funções, vamos escrever um pequeno aplicativo PHP que procure em uma lista
de palavras um padrão específico, onde as palavras e a regex são fornecidas por um formulário da Web
tradicional e os resultados são ecoados no navegador usando a função
simple print_r() . Esse pequeno programa é útil se você quiser testar ou refinar
uma regex.
A Lista 2 mostra o código do PHP. Toda a entrada é fornecida através de um formulário HTML simples. (O formulário correspondente não é mostrado e o código para efetuar trap dos erros no código do PHP foi omitido por brevidade.)
Lista 2. Comparar Texto a um Padrão
<?php
//
// divida a lista separada por vírgula em palavras individuais
// o terceiro parâmetro, -1, permite um número ilimitado de correspondências
// o quarto parâmetro, PREG_SPLIT_NO_EMPTY, ignora correspondências vazias
//
$words = preg_split( '/,/', $_REQUEST[ 'words' ], -1, PREG_SPLIT_NO_EMPTY ); //
// remova os espaços à esquerda e à direita de cada elemento
//
foreach ( $words as $key => $value ) {
$words[ $key ] = trim( $value );
} //
// localize as palavras que correspondem à expressão regular
//
$matches = preg_grep( "/${_REQUEST[ 'regex' ]}/", $words ); print_r( $_REQUEST['regex' ] ); echo(
'<br /><br />' ); print_r( $words ); echo( '<br /><br />' ); print_r( $matches
); exit; ?>
|
Primeiro, a string de palavras separadas por vírgula é dividida em elementos individuais
usando a função
preg_split() . Essa função divide a string em todos os pontos que correspondem à
regex fornecida.
Aqui, a regex é simplesmente , (uma vírgula, o delimitador epônimo de uma lista
separada por vírgula).
As barras à esquerda e à direita mostradas no código simplesmente indicam o início e o fim da regex.
O terceiro e o quatro argumentos de preg_split() são opcionais, mas
cada um é útil.
Forneça um número inteiro, n, para que o terceiro argumento retorne somente as
n primeiras correspondências; ou forneça -1 para todas as correspondências.
Se você especificar um quarto argumento, o sinalizador PREG_SPLIT_NO_EMPTY,
preg_split() elimina quaisquer resultados vazios.
Em seguida, cada elemento da lista de palavras separadas por vírgula é reduzido (os espaços em
branco à esquerda e à direita são eliminados) através da função
trim() , em seguida, comparada à regex fornecida.
A função, preg_grep(), facilita muito o processamento de uma lista: simplesmente
forneça o padrão como o primeiro argumento e uma array de palavras para corresponder como o segundo
argumento.
A função retorna uma array de correspondências.
Por exemplo, se você digitar a regex ^[A-Za-z][A-Za-z0-9_]{2,9}$
como o padrão e uma lista de palavras de comprimentos variados, poderá obter algo semelhante à Lista 3.
Lista 3. Resultado de uma Regex Simples
^[A-Za-z][A-Za-z0-9_]{2,9}$
Array ( [0] => martin [1] => 1happy [2] => hermanmunster )
Array ( [0] => martin )
|
Por sinal, você pode inverter a operação preg_grep() e localizar
elementos que
não correspondem ao padrão (o mesmo que grep -v na linha de comando) com o
sinalizador opcional PREG_GREP_INVERT. Substituir a linha 22 por
$matches = preg_grep( "/${_REQUEST[ 'regex' ]}/", $words, PREG_GREP_INVERT ) e reutilizar a entrada da
Lista 3 produz
Array ( [1] => 1happy [2] => hermanmunster ).
As funções preg_split() e preg_grep()
são excelentes pequenas funções. A primeira pode decompor uma string em substrings se as substrings forem
separadas por um padrão previsível.
A função
preg_grep() também pode filtrar uma lista rapidamente.
Mas o que acontece se uma string precisar ser decomposta usando uma ou mais regras complexas?
Por exemplo, os números de telefone nos EUA frequentemente aparecem como "(305) 555-1212", "305-555-1212" ou
"305.555.1212". Se você remover a pontuação, tudo é reduzido para 10 dígitos, o que é fácil de ser
reconhecido como uso da regex \d{10}. No entanto, o código de área de três dígitos
e o prefixo de três dígitos dos números de telefone nos Estados Unidos não podem iniciar por zero nem por um
(pois ambos são prefixos para chamadas não locais).
Em vez de dividir a sequência numérica em dígitos individuais e escrever código complexo, uma regex pode
testar a validade.
A Lista 4 mostra um trecho de código para executar essa tarefa.
Lista 4. Determinar se um Número de Telefone é um Número de Telefone Válido dos EUA
<?php $punctuation = preg_quote( "().-" ); $number = preg_replace( "/[$punctuation]/", '', $_REQUEST[
'number' ] ); $valid = "/[2-9][0-9]{2}[2-9][0-9]{2}[0-9]{4}/"; if ( preg_match( $valid, $number ) == 1 ) {
echo( "${_REQUEST[ 'number' ]} is valid<br />" ); } exit; ?>
|
Vamos percorrer o código:
- Conforme mostrado na Tabela 1, regexes usam um pequeno conjunto de
operadores, como colchetes (
[ ]), para denominar um conjunto. Se quiser corresponder tal operador em texto de assunto, você deve "escapar" o operador na regex com uma barra invertida anterior (\). Após escapar o operador, corresponde como qualquer outro literal. Por exemplo, se quiser corresponder um ponto literal, digamos, conforme encontrado em um nome de host completo, escreva\.. Como opção, você pode passar uma string parapreg_quote()para escapar automaticamente qualquer operador da regex localizado, como na linha 1. Se você usarecho() $punctuationapós a linha 1, deverá ver\(\)\.-. - A linha 2 remove toda a pontuação do número do telefone. A função
preg_replace()substitui qualquer ocorrência de um caractere em$punctuation— portanto, os operadores configurados[ ]— com a string vazia, eliminam de forma efetiva os caracteres. A nova string é retornada e designada para$number. - A linha 4 define o padrão para um número de telefone válido dos EUA.
- A linha 5 executa a correspondência, comparando o número de telefone agora somente com
dígitos ao padrão.
A função
preg_match()retorna 1 se houver uma correspondência. Se nenhuma correspondência for localizada,preg_match()retorna um zero. Se tiver ocorrido um erro durante o processamento, a função retorna False. Assim, para verificar o sucesso, consulte se o valor de retorno é 1. Caso contrário, verifique o resultado depreg_last_error()(se você usar o PHP V5.2.0 ou posterior). Se não for zero, você pode ter excedido um limite de computação, como até onde uma regex pode ocorrer novamente. Você pode encontrar uma discussão sobre as constantes e os limites usados com as regexes do PHP na página Funções de Expressões Regulares da PCRE (consulte Recursos).
Há muitas instâncias em que um teste "Isso corresponde?" é tudo de que se precisa — como em validação de dados. Mais frequentemente, no entanto, uma regex é usada para provar uma correspondência e para extrair informações sobre a correspondência.
Retornando ao exemplo do número de telefone, se uma correspondência for feita, você pode
querer armazenar o código de área, o prefixo e o número da linha em campos separados de um banco de dados.
Regexes podem se lembrar do que foi correspondido com capture.
O operador capture é o parêntese e o operador pode aparecer em qualquer parte
da regex. Você também pode aninhar capturas para localizar sub-segmento de capturas maiores.
Por exemplo, para capturar o código de área, o prefixo e o número da linha do telefone de 10 dígitos, você
pode usar:
/([2-9][0-9]{2})([2-9][0-9]{2})([0-9]{4})/
|
Se ocorrer uma correspondência, os três primeiros dígitos são capturados no primeiro conjunto
de parênteses, os três dígitos seguintes no segundo conjunto e os quatro dígitos finais no operador restante.
Uma variação da chamada de preg_match() recupera as capturas.
Lista 5. Como
preg_match()
Recupera as Capturas
$valid = "/([2-9][0-9]{2})([2-9][0-9]{2})([0-9]{4})/";
if ( preg_match( $valid, $number, $matches ) == 1 ) {
echo( "${_REQUEST[ 'number' ]} is valid<br />" );
echo( "Entire match: ${matches[0]}<br />" );
echo( "Area code: ${matches[1]}<br />" );
echo( "Prefix: ${matches[2]}<br />" );
echo( "Number: ${matches[3]}<br />" );
}
|
Se você fornecer uma variável como o terceiro argumento para preg_match()
, como $matches aqui, é configurada para uma lista de resultados de capturas.
O elemento zeroth (indexado com 0) é a correspondência inteira; o primeiro
elemento é a correspondência associada ao primeiro conjunto de parênteses, etc., respectivamente.
As capturas aninhadas capturam segmentos e sub-segmentos para praticamente qualquer
profundidade. O truque com capturas aninhadas é prever onde cada correspondência aparece em uma array de
correspondência, como
$matches. Aqui está a regra a seguir: conte o número de parênteses esquerdos a
partir do início da regex — a contagem é o índice para a array de correspondência.
A Lista 6 fornece um exemplo (um tanto quanto planejado) para extrair partes de um endereço.
Lista 6. Código para Extrair um Endereço
$address = "123 Main, Warsaw, NC, 29876";
$valid = "/((\d+)\s+(\w+)),\s+(\w+),\s+([A-Z]{2}),\s+(\d{5})/";
if ( preg_match( $valid, $address, $matches ) == 1 ) {
echo( "Street: ${matches[1]}<br />" );
echo( "Street number: ${matches[2]}<br />" );
echo( "Street name: ${matches[3]}<br />" );
echo( "City: ${matches[4]}<br />" );
echo( "State: ${matches[5]}<br />" );
echo( "Zip: ${matches[6]}<br />" );
}
|
Novamente, toda a correspondência está localizada no índice 0. Onde se encontra o número da rua? Contando a partir da esquerda, o número da rua é correspondido por
\d+. O parêntese esquerdo é o segundo da esquerda; portanto,
$matches[2] é 123. $matches[4] contém o nome da cidade, enquanto $matches[6] captura o CEP.
O processamento de texto é muito comum e o PHP fornece alguns recursos que facilitam grandes números de operações. Seguem alguns atalhos a serem lembrados:
- A função
preg_replace()pode operar um uma única string ou em uma array de strings. Se você chamarpreg_replace()com uma array de strings em vez de uma string, todos os elementos da array são processados para fazerem substituições. Nesse caso,preg_replace()retorna uma array de strings modificadas. - Como com outras implementações PCRE, você pode fazer referência a uma correspondência de subpadrão a partir da substituição, permitindo que uma operação seja uma auto-referência. Para demonstrar, considere o problema de unificar um formato de número de telefone. Toda a pontuação é removida, substituída por pontos. Uma solução é mostrada na Lista 7.
Lista 7. Substituindo a Pontuação por Pontos
$punctuation = preg_quote( "().-" );
$number = preg_replace( "/[$punctuation]/", '', $_REQUEST[ 'number' ] );
$valid = "/([2-9][0-9]{2})([2-9][0-9]{2})([0-9]{4})/";
$standard = preg_replace( $valid, "\\1.\\2.\\3", $number );
if ( strcmp ($standard, $number) ) {
echo( "The standard number is $standard<br />" );
}
|
O teste com relação ao padrão e a transformação em um número de telefone padrão se as correspondências de padrão ocorrerem em uma etapa.
Os aplicativos PHP gerenciam cada vez mais grandes quantidades de dados. Se você precisa validar entrada de formulário ou decompor conteúdo, expressões regulares podem fazer o truque.
Aprender
-
Leia "
Como Usar Expressões Regulares no PHP
."
-
Verifique Funções de Expressões
Regulares de PCRE
na PHP.net.
-
PHP.net é o recurso central para desenvolvedores de PHP.
-
Verifique a "
Lista de Leitura Recomendada de PHP."
-
Navegue por todo o
conteúdo de PHP em developerWorks.
-
Expanda suas qualificações em PHP verificando, no IBM developerWorks,
Recursos de Projeto PHP.
-
Para ouvir entrevistas e discussões interessantes para desenvolvedores de software, verifique os
podcasts do developerWorks.
-
Usando um banco de dados com PHP? Verifique
Zend Core para IBM, um ambiente de desenvolvimento e produção pronto, transparente e de fácil instalação
que suporta o IBM DB2 V9.
-
Mantenha-se atualizado com
Eventos Técnicos e Webcasts do developerWorks.
-
Verifique conferências, feiras comerciais, webcasts e outros
Eventos futuros no mundo que sejam de interesse de desenvolvedores de software livre da IBM.
-
Visite Zona de
Software Livre do developerWorks para obter informações extensivas sobre como executar ações, sobre ferramentas e atualizações de projetos para ajudá-lo a se desenvolver com tecnologias de software livre e
usá-las com produtos IBM.
-
Assista e aprenda sobre as tecnologias IBM e de software livre e funções de produtos gratuitamente com os
Demos On Demand do developerWorks.
Obter produtos e tecnologias
-
Inove seu próximo projeto de desenvolvimento de software livre com
software de avaliação da IBM, disponível para download ou em DVD.
-
Faça download do
versões de avaliação de produtos IBMe entre em contato com ferramentas de desenvolvimento de aplicativos
e produtos de middleware do DB2®, Lotus®, Rational®, Tivoli®e WebSphere®.
Discutir
-
Participe de Blogs do
developerWorks e envolva-se na comunidade developerWorks.
-
Participe do
PHP Forum: Developing PHP applications with IBM Information Management products (DB2, IDS) do developerWorks.
