Expressões regulares com preg

Expressões regulares (regex) são padrões de busca e manipulação de texto extremamente poderosos. No PHP, a família de funções preg_* implementa o PCRE (Perl Compatible Regular Expressions), oferecendo ferramentas robustas para validação, extração, substituição e divisão de strings. Diferentemente de implementações mais simples como ereg (obsoleta), o PCRE oferece sintaxe moderna, alta performance e suporte completo a metacaracteres e modificadores.

As principais funções são: preg_match(), preg_match_all(), preg_replace(), preg_replace_callback(), preg_split() e preg_grep(). Cada uma atende a um propósito específico no processamento de strings.

Sintaxe Básica de Padrões em PHP

Todo padrão regex em PHP deve ser delimitado por caracteres especiais. Os delimitadores mais comuns são /, # e ~. A escolha do delimitador é importante para evitar a necessidade de escapar caracteres dentro do padrão.

// Delimitadores comuns
$padrao1 = '/[a-z]+/';
$padrao2 = '#[a-z]+#';
$padrao3 = '~[a-z]+~';

Os metacaracteres fundamentais incluem:
- . — qualquer caractere (exceto nova linha)
- ^ — início da string
- $ — fim da string
- * — zero ou mais ocorrências
- + — uma ou mais ocorrências
- ? — zero ou uma ocorrência
- [] — classe de caracteres
- () — grupo de captura
- | — alternância (OU)
- \ — escape

Classes predefinidas simplificam padrões comuns:
- \d — dígito (0-9)
- \w — caractere alfanumérico + underscore
- \s — espaço em branco
- \D, \W, \S — negações das classes acima

Modificadores alteram o comportamento do padrão:
- i — case-insensitive
- m — multiline (^ e $ passam a considerar quebras de linha)
- s — dotall (ponto passa a capturar nova linha)
- u — unicode (necessário para caracteres acentuados)

$texto = "Olá Mundo\nolá mundo";
preg_match('/^olá/im', $texto, $matches); // Encontra ambas as ocorrências

Busca e Verificação com preg_match e preg_match_all

preg_match() retorna 1 se o padrão for encontrado, 0 caso contrário, e false em caso de erro. O terceiro parâmetro opcional recebe as correspondências.

// Validação de e-mail simples
$email = "usuario@exemplo.com";
$padrao = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';

if (preg_match($padrao, $email)) {
    echo "E-mail válido";
}

preg_match_all() captura todas as ocorrências do padrão em uma string.

// Extrair todas as URLs de um texto
$texto = "Visite https://exemplo.com e http://teste.org";
$padrao = '/https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/';

preg_match_all($padrao, $texto, $urls);
print_r($urls[0]); // Array com as URLs encontradas

Para validação de CPF (apenas formato, não dígitos verificadores):

$cpf = "123.456.789-00";
$padrao = '/^\d{3}\.\d{3}\.\d{3}-\d{2}$/';

if (preg_match($padrao, $cpf)) {
    echo "Formato de CPF válido";
}

Substituição e Manipulação com preg_replace e preg_replace_callback

preg_replace() substitui padrões por strings fixas. Aceita referências retroativas como \1 ou $1 para usar grupos capturados.

// Mascarar parte do e-mail
$email = "joao.silva@exemplo.com";
$padrao = '/(\w+)@/';
$substituicao = '$1***@';
echo preg_replace($padrao, $substituicao, $email);
// Resultado: joao.silva***@exemplo.com

preg_replace_callback() permite substituições mais complexas usando funções anônimas.

// Converter Markdown simples para HTML
$markdown = "**negrito** e *itálico*";
$html = preg_replace_callback(
    '/\*\*(.+?)\*\*|\*(.+?)\*/',
    function($matches) {
        if (!empty($matches[1])) {
            return "<strong>{$matches[1]}</strong>";
        }
        return "<em>{$matches[2]}</em>";
    },
    $markdown
);
echo $html; // <strong>negrito</strong> e <em>itálico</em>

O parâmetro $limit controla o número máximo de substituições:

$texto = "um dois três quatro";
echo preg_replace('/\s+/', ', ', $texto, 2); // um, dois, três quatro

Divisão e Filtragem com preg_split e preg_grep

preg_split() é uma alternativa avançada ao explode(), permitindo padrões complexos para delimitar a divisão.

// Dividir texto em palavras, ignorando pontuação
$texto = "Olá, mundo! Como vai?";
$palavras = preg_split('/[^\wáéíóúàèìòùâêîôûãõç]+/u', $texto, -1, PREG_SPLIT_NO_EMPTY);
print_r($palavras); // Array com as palavras limpas

Flags úteis incluem:
- PREG_SPLIT_NO_EMPTY — remove elementos vazios
- PREG_SPLIT_DELIM_CAPTURE — captura os delimitadores
- PREG_SPLIT_OFFSET_CAPTURE — retorna também a posição de cada elemento

preg_grep() filtra arrays, retornando apenas elementos que correspondem ao padrão.

// Extrair linhas de log com erro
$logs = [
    "2024-01-01 INFO: Conexão estabelecida",
    "2024-01-01 ERROR: Falha na autenticação",
    "2024-01-01 WARNING: Disco quase cheio",
    "2024-01-01 ERROR: Timeout"
];

$erros = preg_grep('/ERROR/', $logs);
print_r($erros); // Array com as linhas de erro

Boas Práticas e Otimização de Performance

preg_quote() escapa caracteres especiais automaticamente, essencial quando o padrão inclui dados dinâmicos.

$termo_busca = "preço (R$)";
$padrao = '/' . preg_quote($termo_busca, '/') . '/';

Evite backtracking catastrófico usando âncoras e quantificadores possessivos. Prefira (?>...) para grupos atômicos quando possível.

// Padrão potencialmente catastrófico
$ruim = '/(a+)+b/'; // Pode travar em strings longas sem 'b'

// Padrão otimizado
$bom = '/a++b/'; // Quantificador possessivo

Use grupos não-capturadores (?:...) quando não precisar do conteúdo capturado, economizando memória.

// Captura desnecessária
$ruim = '/(https?):\/\/([a-z]+)/';

// Otimizado
$bom = '/(?:https?):\/\/([a-z]+)/';

Sempre verifique erros com preg_last_error():

$resultado = preg_match($padrao, $texto);
if ($resultado === false) {
    $erro = preg_last_error();
    echo "Erro na expressão regular: $erro";
}

Exemplos Práticos e Casos de Uso Comuns

Validação de telefone brasileiro:

$telefone = "(11) 98765-4321";
$padrao = '/^\(\d{2}\)\s\d{4,5}-\d{4}$/';

if (preg_match($padrao, $telefone)) {
    echo "Telefone válido";
}

Extração de tags HTML:

$html = "<a href='https://site.com'>Link</a> <img src='foto.jpg'>";
preg_match_all('/<a\s+href=[\'"]([^\'"]+)[\'"]>([^<]+)<\/a>/', $html, $links);
print_r($links[1]); // URLs dos links

Sanitização de entrada (remover caracteres não numéricos):

$entrada = "CPF: 123.456.789-00";
$apenas_numeros = preg_replace('/\D/', '', $entrada);
echo $apenas_numeros; // 12345678900

Conversão de data:

$data = "2024-01-15";
$padrao = '/(\d{4})-(\d{2})-(\d{2})/';
echo preg_replace($padrao, '$3/$2/$1', $data); // 15/01/2024

Expressões regulares com preg são ferramentas indispensáveis no PHP moderno. Dominar seu uso permite escrever código mais limpo, eficiente e seguro para manipulação de texto.

Referências