PHP Regex para Validação de Senhas

function isValidPassword($password) {
    $pattern = '/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\d].\S{8,36}$/';

    return preg_match($pattern, $password) ? true : false;
}

Você é bom em REGEX? Sabe o que é ou para que serve? Já precisou aplicar regras de validação de senhas e não usou REGEX? Então você pode ler este artigo para começar a explorar mais sobre este recurso.

Primeiramente vou deixar claro que o objetivo do artigo não é ensinar REGEX amplamente, ao invés disso vamos aplicar o recurso num problema prático explicando o processo de validação de senhas segundo um cenário de regras, aqui vai…

Regra 1 – A senha deve ter no mínimo 8 e no máximo 36 caracteres.

Regra 2 – A senha deve ser composta de ao menos 1 número, ao menos uma letra maiúscula, e ao menos uma letra minúscula.

Regra 3 – A senha deve ser composta de apenas letras e números e não deve possuir nenhum tipo de caracteres especiais.

No PHP para verificarmos um texto com REGEX podemos usar o recurso preg_match. Como o nome já diz o comando vai verificar o conteúdo do texto segundo uma combinação de máscaras para encontrar a combinação desejada.

Confuso? Então vamos a parte prática, observe primeiramente a máscara postada com esta sintaxe.

'/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\d].\S{8,36}$/'

Para entender como a regra funciona temos que desmontá-la e explicar em partes.

Entenda que com REGEX você monta uma máscara aos poucos, assim tente não pensar em aplicar a regra 1 primeiro, depois a regra 2 e no final a regra 3. Geralmente temos que começar aplicar o que é mais fácil, mais simples e ir aumentando a complexidade gradativamente.

'/(?=.*\d)/'

Esta máscara garante que a senha contenha ao menos 1 número, o que é parte da exigência da regra 2.

Pense nesta máscara como sendo uma varredura completa do texto onde ? é cada um dos caracteres da senha podendo ser igual a qualquer coisa (.*) desde que seja numérico (\d).

'/(?=.*\d)(?=.*[a-z])/'

Esta segunda parte garante que a senha contenha ao menos 1 letra minúscula, que também é parte da exigência da regra 2.

Pense que cada máscara entre parênteses é uma varredura individual em que o resultado da varredura deve ser TRUE quando fizer o match desejado.

'/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/'

O terceiro parênteses contém a mesma validação que o segundo, só que como o REGEX é case sensitive por padrão temos que validar com os dois formatos separadamente, letras minúsculas e letras maiúsculas separamente.

'/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\d]/'

A próxima máscara adicionada quase garante que todos os caracteres do texto sejam letras e números apenas. Sim, eu disse quase, mas ainda temos que garantir que a combinação desejada seja validada do início ao fim do texto, caso contrário as extremidades (bordas) do texto poderão conter caracteres indesejadados.

'/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\d]$/'

Agora sim, o acento circunflexo ˆ garante que a validação vai ser realizada a partir do primeiro caracter da senha e o dólar $ garante que a combinação desejada será feita até o último caracter da senha.

Regra 3 concluída? Parece que sim, só que não… deixa pa lá, resolvemos depois.

'/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\d]{8,36}$/'

Olha que fácil, para determinar as quantidades mínimas e máximas de caracteres basta inserir estes limitadores entre chaves {} sendo o primeiro número a quantidade mínima de caracteres requerida e o segundo número a quantidade máxima requerida.

Parece que a regra está completa, não parece? Então onde está o BUG?

Seguinte, espaços em brancos passam batidos na validação de letras e números, justamente por que espaço em branco não é nada. Não é letra, não é número, não é caracter especial, nada.

'/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\d].\S{8,36}$/'

Pronto, com o .\S garantimos que espaços em brancos não estejam presentes na senha. Maravilha!

Mas você ainda pode estar se perguntando sobre as barras / no ínicio e fim da máscara e também o recobrimento com as aspas, certo?

O REGEX requer o recobrimento com delimitadores, no meu caso escolhi o mais trivial que são barras normais SLASHES. Mas você poderia utilizar outros caracteres a seu critério como PIPE | ou NUMBER #. O interessante é que você use como delimitador algum caracter que você não vá utilizar na composição da máscara.

Por fim, uma máscara é uma string, assim temos que recobri-la com aspas, que podem ser simples ” ou duplas “”, tanto faz.

Então, gostou do REGEX? Isso é um mundo a parte, pegar proficiência neste campo requer muito empenho mas vale a pena.

Eu nunca encontrei um treinamento que te ensina como pensar em REGEX, mas acho que isso é algo que somente se adquire com tempo e experiência.

Então, boa sorte!