Expressões Regulares em PHP

Artigo que explica como utilizar expressões regulares em PHP.

Introdução

Expressões regulares são expressões que permitem especificar um padrão de string, ou seja, como os caracteres de uma string ficam dispostos em sequência. Com este padrão, é possível realizar checagens para saber se uma string segue determinado padrão, capturar pedaços da string com base no padrão, ou ainda substituir pedaços de uma string por outra sequência de caracteres.

As expressões regulares são um recurso extremamente útil não apenas para PHP, mas para várias outras linguagens, inclusive JavaScript. Porém, como possui uma sintaxe própria, nem todos programadores PHP tem familiaridade em montar ou entender expressões regulares. Neste artigo veremos um compilado de informações úteis para se aprender a usar expressões regulares em PHP.


Onde usar expressões regulares?

Antes de saber montar as expressões regulares, é preciso saber em que situações elas podem ser usadas. Para cada utilização existe uma função própria no PHP. Então veja abaixo os tipos de utilização e as respectivas funções. Por enquanto, não se preocupe com a expressão regular, que está em vermelho.

1 - Checar se uma string segue determinado padrão

// Exemplo
$string = 'ABC-1234';

// Checar se a string segue o padrao de uma placa de carro
if (preg_match('/^[A-Z]{3}\-[0-9]{4}$/', $string)) {
    // A string eh uma placa de carro valida
} else {
    // A string nao eh uma placa de carro valida
}

2 - Capturar pedaços de uma string

// Exemplo
$cpf = '123.456.789-01';

// Capturar o digito verificador do CPF
if (preg_match('/^[0-9]{3}\.[0-9]{3}\.[0-9]{3}\-([0-9]{2})$/', $cpf, $partes)) {
    $digito_verificador = $partes[1];
}

3 - Substituir pedaços de uma string por outra sequência de caracteres

// Exemplo 1: substituir vogais por "x":
$string = 'abcdefgh123';
$string2 = preg_replace('/[aeiou]/', 'x', $string);

// Exemplo 2: remover numeros de uma string
$string = 'abc123';
$string2 = preg_replace('/[0-9]/', '', $string);

// Exemplo 3: transformar um CPF com numeros em um CPF formatado
$string = '12345678901';
$string2 = preg_replace('/^([0-9]{3})([0-9]{3})([0-9]{3})([0-9]{2})$/', '$1.$2.$3-$4', $string);

// Exemplo 4: transformar uma data no formato YYYY-MM-DD em DD/MM/YYYY
$string = '2015-02-01';
$string2 = preg_replace('/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/', '$3/$2/$1', $string);

// Exemplo 5: Converter as vogais de uma string para maiuscula
$string = 'abcdefgh123';
$string2 = preg_replace_callback(
    '/[aeiou]/',
    function($partes) {
        return strtoupper($partes[0]);
    },
    $string
);

4 - Quebrar uma string em um array utilizando uma expressão regular

// Exemplo: quebrar a string quando encontrar ponto ou virgula
$string = 'abc, def. ghi';
$array = preg_split('/[,\.]/', $string);

Neste caso, a função tem a o mesmo comportamento da função explode do PHP, a diferença é que ao invés de usar um delimitador fixo, é usada uma expressão regular.


Como montar expressões regulares?

As expressões regulares do PHP possuem a sintaxe similar às expressões regulares da linguagem Perl, com pequenas diferenças e limitações. Por isso as funções citadas anteriormente fazem parte do módulo PCRE do PHP, que significa "Perl-Compatible Regular Expressions".

Como vimos anteriormente, as expressões regulares são montadas na forma de string do PHP e passadas para funções preg_..., assim como ocorre por exemplo na execução de queries SQL.

Passo 1 - Escolher um delimitador

Para começar, as expressões regulares precisam ser delimitadas com algum caractere que é usado no início e no fim da expressão. Este caractere não pode ser letra, número, barra invertida (\) ou espaço. Normalmente é utilizado o caractere barra (/), o caractere cerquilha (#) ou o caractere arroba (@). Também é permitido, mas menos comum, usar abre/fecha parênteses, abre/fecha colchetes, abre/fecha chaves, ou sinal menor e sinal maior.

Uma observação importante é que quando você escolhe determinado caractere como delimitador da expressão regular, este caractere precisa ser escapado quando for utilizado na expressão. O escape é feito com a barra invertida, e também deve ser usado para escapar alguns caracteres que possuem significado especial nas expressões regulares. Veja alguns exemplos:

// Usando / como delimitador, precisa escapar o / usado na expressao
preg_match('/http:\/\//', $string);

// Neste caso, nao precisamos escapar o /, pois usamos # como delimitador
preg_match('#http://#', $string);

Outra observação é que, como a expressão é montada dentro de uma string em PHP, é preciso usar a barra invertida para escapar sequências que precisam ser escapadas para que a string seja válida no PHP, tais como a aspas duplas, as aspas simples, o caractere de abre chave, o caractere de fecha chave, o caractere de barra invertida, etc.

Passo 2 - Montar o padrão de caracteres

Esta etapa é a mais complexa, pois exige conhecer toda a sintaxe de expressões regulares.

Primeiramente, vamos ver uma expressão simples que checa se uma string possui determinada sequência de caracteres. Para isso, basta usar o delimitador da expressão com a sequência de caracteres desejada, lembrando de escapar possíveis caracteres que são de uso reservado em expressões regulares:

// Exemplo: checar se determinada string possui a sequencia de caracteres "rato"
if (preg_match('/rato/', $string)) {
    // A string possui "rato", por exemplo: "rato", "barato", "baratos", "O rato do rei"
} else {
    // A string nao possui "rato", por exemplo: "Rato", "RATO", "BARATO"
}

O exemplo acima é apenas ilustrativo, pois para checar se determinada string é encontrada em outra, basta usar a função strpos, que seria mais rápida que usando expressão regular.

A função acima vai retornar 0 (zero) se a string não casar com a expressão, 1 (um) se a string casar com a expressão ou false se ocorrer algum erro ao processar a expressão regular.

Início de string

Para que a expressão case apenas as strings que comecem com "rato", precisamos usar o meta-caractere "^" no início da expressão:

// Exemplo: checar se determinada string comeca com a sequencia "rato"
if (preg_match('/^rato/', $string)) {
    // A string comeca com "rato", por exemplo: "rato", "ratos"
} else {
    // A string nao comeca com "rato", por exemplo: "barato", "o rato", "Rato"
}

Observe que a expressão é case-sensitive por padrão.

Término de string

Para que a expressão case apenas as string que terminem com "rato", precisamos usar o meta-caractere "$" no final da expressão:

// Exemplo: checar se determinada string termina com a sequencia "rato"
if (preg_match('/rato$/', $string)) {
    // A string termina com "rato", por exemplo: "rato", "barato", "o rato"
} else {
    // A string nao termina com "rato", por exemplo: "ratos", "baratos", "Rato", "o rato sumiu"
}

Também podemos usar o meta-caracteres "^" e "$" em conjunto. Neste caso, a expressão precisa começar e terminar com "rato":

// Exemplo: checar se determinada string eh exatamente "rato"
if (preg_match('/^rato$/', $string)) {
    // A string eh exatamente "rato", por exemplo: "rato"
} else {
    // A string nao eh exatamente "rato", por exemplo: "ratos", "baratos", "Rato", "o rato"
}

Uma expressão OU outra

Para fazer uma expressão casar strings com determinada sequência de caracteres OU outra, usa-se o meta-caractere "|" para indicar a opção OU:

// Exemplo: checar se determinada string possui "rato" ou "gato"
if (preg_match('/rato|gato/', $string)) {
    // A string possui "rato" ou gato, por exemplo: "rato", "gato", "ratos", "gatos", "barato", "rato e gato"
} else {
    // A string nao possui "rato" nem "gato", por exemplo: "Rato", "Gato", "cavalo"
}

// Exemplo: checar se determinada string eh exatamente "rato" ou exatamente "gato"
if (preg_match('/^rato$|^gato$/', $string)) {
    // A string eh "rato" ou gato, por exemplo: "rato", "gato"
} else {
    // A string nao eh "rato" nem "gato", por exemplo: "Rato", "Gato", "cavalo", "ratos"
}

Grupo de caracteres

Para especificar um grupo de caracteres utilizamos os delimitadores abre/fecha colchetes. Dentro dos colchetes, podemos colocar os grupos de caracteres aceitos naquela posição:

// Exemplo: checar se a string comeca com "r" ou "g", segue com "at" e termina com "a" ou "o"
if (preg_match('/^[rg]at[ao]$/', $string)) {
    // Por exemplo: "rato", "gato", "rata", "gata"
} else {
    // Por exemplo: "Rato", "Gato", "cavalo", "ratos", "rgatao"
}

Note que na posição onde estão os grupos de caracteres só é aceito um único caractere. No primeiro caso, ou é "r" ou é "g", mas não pode ser "rg". No segundo caso, ou é "a" ou é "o", mas não pode ser "ao" nem "oa".

Segue algumas possibilidades a serem usadas dentro dos colchetes:

  • 0-9: qualquer dígito
  • \d: qualquer dígito (também aceito fora dos colchetes)
  • a-z: qualquer letra minúscula
  • A-Z: qualquer letra maiúscula
  • a-c: qualquer letra minúscula entre "a" e "c"
  • [:lower:]: qualquer letra minúscula
  • [:upper:]: qualquer letra maiúscula
  • [:alpha:]: qualquer letra
  • [:alnum:]: qualquer letra ou dígito
  • [:digit:]: qualquer dígito
  • \w: qualquer caractere para montar palavras (também aceito fora dos colchetes)
  • \s: qualquer caractere de espaço ou quebra de linha (também aceito fora dos colchetes)

Caso queira representar qualquer caractere, pode ser usado o meta-caractere . (ponto final), que indica "qualquer caractere exceto quebra de linha", e só pode ser definido fora dos colchetes, já que não faz sentido usar o colchetes pra especificar um conjunto de caracteres, mas aceitar qualquer caractere.

Para conhecer todas possibilidades, consulte a lista de classes de caracteres e lista de caracteres genéricos.

Note que existem várias formas para expressar o mesmo grupo de caracteres. Eles podem ser usados conforme o gosto. Além disso, podem ser usados em conjunto:

// Exemplo: checar se a string comeca com "x", seguido por uma letra minuscula, depois um numero, e termina com uma letra ou numero ou @
if (preg_match('/^[x][[:lower:]][\d][a-z0-9@]$/', $string)) {
    // Por exemplo: "xa0a", "xb11", "xb1@"
}

// Mesmo exemplo, mas usando apenas sequencias de escape
if (preg_match('/^x\w\d[\w\d@]$/', $string)) {
    // Por exemplo: "xa0a", "xb11", "xb1@"
}

Uma observação é que as sequências com escape (barra invertida) podem ser usadas fora dos colchetes.

Note que o sinal de menos tem significado especial dentro de colchetes, portanto precisa ser escapado caso o caractere "-" seja aceito no grupo de caracteres.

Para indicar um grupo de caracteres negativo, ou seja, qualquer caractere exceto os especificados, utiliza-se o sinal "^" após a abertura de colchetes:

// Exemplo: a string precisa comecar com "a" ou "b", depois seguir com qualquer caractere, exceto o "x" e "y"
if (preg_match('/^[ab][^xy]$/', $string)) {
    // Por exemplo: "a1", "bd"
} else {
    // Por exemplo: "ax", "ay", "bx", "by", "k1"
}

Grupo de expressões (sub-expressões)

Para especificar um grupo de expressões usamos os delimitadores abre/fecha parênteses. Um grupo de expressões é usado para capturar partes de uma string.

// Capturar o prefixo de uma placa de carro (3 letras) e o sufixo (4 numeros)
$string = 'ABC-1234';
if (preg_match('/^([A-Z][A-Z][A-Z])-([0-9][0-9][0-9][0-9])$/', $string, $partes)) {
    echo $partes[0]; // ABC-1234
    echo $partes[1]; // ABC
    echo $partes[2]; // 1234
}

É possível especificar um nome da sub-expressão através da notação (?P<nome>expressão). A partir do PHP 5.2.2, também pode ser pela notação (?<nome>expressão) ou (?'nome'expressão). Desta forma, é possível obter as partes da string de forma mais legível. Veja o exemplo anterior com as sub-expressões nomeadas:

// Capturar o prefixo de uma placa de carro (3 letras) e o sufixo (4 numeros)
$string = 'ABC-1234';
if (preg_match('/^(?P<prefixo>[A-Z][A-Z][A-Z])-(?P<sufixo>[0-9][0-9][0-9][0-9])$/', $string, $partes)) {
    echo $partes[0];         // ABC-1234
    echo $partes[1];         // ABC
    echo $partes[2];         // 1234
    echo $partes['prefixo']; // ABC
    echo $partes['sufixo'];  // 1234
}

Também é possível informar que uma sub-expressão não será capturada. Para isso, é usada a notação (?:expressão).

// Capturar o prefixo de uma placa de carro (3 letras)
$string = 'ABC-1234';
if (preg_match('/^([A-Z][A-Z][A-Z])-(?:[0-9][0-9][0-9][0-9])$/', $string, $partes)) {
    echo $partes[0];         // ABC-1234
    echo $partes[1];         // ABC
}

Por fim, também podemos especificar que uma sub-expressão irá capturar uma das sub-sub-expressões com a notação (?|expressões):

// Capturar a sigla de segunda-feira ou terça feira
$string = 'terça-feira';
if (preg_match('/^(?|(seg)unda|(ter)ça)-feira$/', $string, $partes)) {
    echo $partes[0];         // terça-feira
    echo $partes[1];         // ter
}

No exemplo anterior, caso usássemos ?: ao invés de ?|, o array $partes sempre viria com a posição 1 e 2, só que apenas uma delas estaria preenchida.

Especificação de repetições

Podemos especificar a quantidade de repetições de um caractere, um grupo de caracteres ou uma sub-expressão. Existem 6 formas de especificar esta quantidade, que é colocada após o elemento que possui repetição:

  • * Representa 0 ou mais ocorrências
  • + Representa 1 ou mais ocorrências
  • ? Representa 0 ou 1 ocorrência
  • {5} Representa exatamente 5 ocorrências
  • {6,} Representa 6 ou mais ocorrências
  • {3,7} Representa 3 a 7 ocorrências

Com as repetições, podemos deixar a expressão do exemplo anterior de forma mais simples, ou seja, especificar que o grupo de caracteres [A-Z] deve se repetir exatamente 3 vezes e o grupo de caracteres [0-9] deve se repetir exatamente 4 vezes.

// Capturar o prefixo de uma placa de carro (3 letras) e o sufixo (4 numeros)
$string = 'ABC-1234';
if (preg_match('/^([A-Z]{3})-([0-9]{4})$/', $string, $partes)) {
    echo $partes[0]; // ABC-1234
    echo $partes[1]; // ABC
    echo $partes[2]; // 1234
}

Outros exemplos

// Checar se a string eh um CPF formatado
if (preg_match('/^\d{3}\.\d{3}\.\d{3}-\d{2}$/', $string, $partes)) {
    // O CPF esta formatado
}

// Checar se a string eh formada por 3 ou mais digitos, seguido ou nao de hifen,
// seguido de um ou mais digitos, seguido de 0 ou mais letras
if (preg_match('/^\d{3,}-?\d+[a-z]*$/', $string, $partes)) {
    // Por exemplo: "1234", "123-4", "12345", "1234-567", "1234ab", "123-4a"
} else {
    // Por exemplo: "12", "1-2", "12-3", "123-", "123-a"
}

Passo 3 - Especificar as opções da expressão

As opções da expressão são colocadas após o caractere que delimita o final da expressão (escolhido no passo 1). Cada letra representa uma opção. As principais opções são:

  • i para tornar a expressão case-insensitive.
  • m para indicar que a expressão é multi-line, portanto "^" e "$" passam a significar "início da string ou início de linha" e "fim da string ou fim de linha" respectivamente.
  • s o meta-caractere "." passa a significar "todos caracteres, inclusive quebra de linha".
  • u para indicar que a expressão trabalha sobre strings codificadas em UTF-8.

Consulte a lista de opções de expressões regulares.

Exemplo da opção "i":

if (preg_match('/^a$/i', $string)) {
    // Casa se a string conter exatamente "a" ou "A"
}

Exemplo da opção "m":

if (preg_match('/^a$/', $string)) {
    // A string contem exatamente "a"
}

if (preg_match('/^a$/m', $string)) {
    // A string possui uma linha contendo exatamente "a"
}

Também é possível habilitar e desabilitar as opções dentro da expressão. Para habilitar, é usada a notação (?opções) enquanto para desabilitar é usada a notação (?-opções).

// Exemplo em que apenas um pedaco da expressao eh case-insensitive
if (preg_match('/^a(?i)b(?-i)C$/', $string)) {
    // A string contem "a" minusculo, seguido de "b" (maiusculo ou minusculo), seguido de "c" maiusculo
}

Consulte a lista de opções internas de expressões regulares.


Conclusão

Neste artigo, tentei resumir as principais características para se conseguir montar e entender uma expressão regular em PHP. Existem alguns conceitos mais avançados, como back-references, sub-expressões condicionais, expressões recursivas, etc. Como o artigo já está um pouco longo, vou deixar estes conceitos para outro artigo.

Para mais informações, confira os Recursos Avançados de Expressões Regulares em PHP.

5 comentários

Unknown disse...

Cara, você é muito didático, escreve bem, claro e objetivo. Sou designer e mexo com programação no esquema de 'autodidatismo'. Agora entendi!