Você está na página 1de 9

Dominando Expressões Regulares em PHP, Parte 1: Perl

Pode Ser o Rei da Regex, mas PHP Pode Reorganizar


Entrada Rapidamente, Também
Martin Streicher (martin.streicher@gmail.com) 01/Jan/2008
Software Developer
#####

A correspondência de padrão é uma tarefa tão comum para software que uma taquigrafia especial —
expressões regulares — evoluiu para tornar leve o trabalho da tarefa. Aprenda como usar essa taquigrafia
em seu código aqui na Parte 1 desta série " Dominando Expressões Regulares em PHP".
Visualizar mais conteúdo nesta série

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

© Copyright IBM Corporation 2008. Todos os direitos reservados. Marcas Registradas


Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 1 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
developerWorks® ibm.com/developerWorks/br/

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

Expressões Regulares
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

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 2 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
ibm.com/developerWorks/br/ developerWorks®

é 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 nX

(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}$.

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 3 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
developerWorks® ibm.com/developerWorks/br/

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.

Programando PHP e Regexes


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
//
// divide the comma-separated list into individual words
// the third parameter, -1, permits a limitless number of matches
// the fourth parameter, PREG_SPLIT_NO_EMPTY, ignores empty matches
//
$words = preg_split( '/,/', $_REQUEST[ 'words' ], -1, PREG_SPLIT_NO_EMPTY );

//
// remove the leading and trailing spaces from each element
//
foreach ( $words as $key => $value ) {
$words[ $key ] = trim( $value );
}

//
// find the words that match the regular expression

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 4 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
ibm.com/developerWorks/br/ developerWorks®

//
$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 ).

Decompondo strings
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",

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 5 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
developerWorks® ibm.com/developerWorks/br/

"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 para preg_quote() para escapar automaticamente qualquer operador da regex
localizado, como na linha 1. Se você usar echo() $punctuation apó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 de preg_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).

Capturas
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.

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 6 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
ibm.com/developerWorks/br/ developerWorks®

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()


$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
" );
echo( "Entire match: ${matches[0]}
" );
echo( "Area code: ${matches[1]}
" );
echo( "Prefix: ${matches[2]}
" );
echo( "Number: ${matches[3]}
" );
}

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 />" );
}

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 7 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
developerWorks® ibm.com/developerWorks/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.

Técnicas de Poder
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ê
chamar preg_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.

Expresse-se
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.

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 8 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também
ibm.com/developerWorks/br/ developerWorks®

Sobre o autor
Martin Streicher

Martin Streicher é chefe executivo em tecnologia da McClatchy Interactive, editor chefe da


Linux Magazine, um desenvolvedor da Web e um colaborador regular do developerWorks.
Ele possui mestrado em ciência da computação pela Purdue University e programa sistemas
semelhantes ao UNIX desde 1986.

© Copyright IBM Corporation 2008. Todos os direitos reservados.


(www.ibm.com/legal/copytrade.shtml)
Marcas Registradas
(www.ibm.com/developerworks/br/ibm/trademarks/)

Dominando Expressões Regulares em PHP, Parte 1: Perl Pode Ser o Rei Página 9 de 9
da Regex, mas PHP Pode Reorganizar Entrada Rapidamente, Também

Você também pode gostar