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!

PHP Como verificar se um Array é 100% ASSOC

function isFullyAssoc($array) {
  return count(array_filter($array, 'is_string', ARRAY_FILTER_USE_KEY)) === count($array);
}

echo isFullyAssoc(['x' => 'xis', 'banana', 1, true]); // false
echo isFullyAssoc(['x' => 'xis', 'y' => 1, 'z' => true]); // true

Pois é, não sei quando você vai precisar validar se um array possui todos os elementos indexados com chave associativa, mas eu precisei hoje.

Independentemente da necessidade, achei legal apresentar esta solução por que não encontrei muito material navegando pela internet.

Como o PHP não possui uma função nativa para esta validação o jeito foi improvisar mesmo, então vamos explicar o conceito do enjambre.

Utilizamos ARRAY_FILTER para segregar todos os elementos cujos índices sejam do tipo STRING. Para isto basta informar a própria função IS_STRING do PHP como CALLBACK function.

Além disto temos que especificar para ARRAY_FILTER que queremos observar os índices, e não os valores, do array no processo de filtragem. Assim devemos informar o terceiro argumento ARRAY_FILTER_USE_KEYS.

No final basta comparar a quantidade de elementos filtrados com a quantidade de elementos da array original. Se houver divergência nesta comparação isto indica seguramente que algum índice do array original não era ASSOC.

Afinal de contas, ARRAY é masculino ou feminino? Vai saber, depois de escrito o texto percebi que mesclei os gêneros, mas no review final ficou menino mesmo!

PHP Array Destructuring… retornando array associativa para variáveis escalares

function func_return_array_assoc() {
  return [ 
    'index1' => 'content1',
    'index2' => true,
    'index3' => 1001,
    'index4' => 0.115,
    'index5' => null,
    'index6' => array(),
  ]; 
}

[
  'index2' => $my_bool_var,
  'index3' => $my_int_var,
  'index1' => $my_string_var,
] = func_return_array_assoc();

echo $my_int_var; // 1001 
echo $my_bool_var; // true
echo $my_string_var; // content1

Pois é pessoal, recentemente esta técnica de programação me chamou a atenção.

No início eu não conseguia compreender muito bem como isto funcionava, então tive de me aprofundar mais e acabei encontrando algo chamado Array Destructuring e o que apresento aqui neste artigo é apenas uma parte do que realmente é abrangido por este tema.

Primeiro vamos entender como funciona este mecanismo. Quando você iguala dois arrays associativos o PHP vai fazer um match dos índices nas duas arrays. Os índices que fizerem match serão igualados e os demais serão desprezados. Observe também que o match não é posicional, assim o array pode ser construído de forma diferente de cada lado.

A grande sacada vem na utilização de variáveis escalares na parte VALUE em um dos lados da array, desta forma durante o processo do match antes de tentar processar a igualdade o PHP primeiro atribui os valores nas variáveis.

Não conseguiu visualizar onde poderia usar esta técnica de forma eficiente?

Então responda, como é que uma função pode retornar mais de um valor? Certo, com um array ou um objeto. Mas e se construir um objeto está fora de cogitação (esforço desnecessário), onde recai a solução? Certo novamente, ARRAY.

E como você faz para receber o resultado de uma função que é um ARRAY? Boa! Você atribui o resultado a uma $variavel_array. Ou então, pode usar o famoso list para receber posicionalmente, certo?

Acho que agora deve ter caído a ficha… ao invés de usar o LIST do PHP para receber o array e atribuindo a variáveis escalares de forma posicional, esta técnica permite fazer isto através do match dos índices atribuindo somente os valores desejados sem se preocupar com o posicionamento.

Que loco! E pensar que já falei com muito dev que critica muito o ARRAY do PHP. Mais loco ainda….

Para quem quiser se aprofundar mais no tema recomendo dar uma olhada no blog Stitcher.io, segue o link:

https://stitcher.io/blog/array-destructuring-with-list-in-php

PHP Array Anônimo, uma técnica não tão conhecida assim…

echo [
    'index1' => 'content1',
    'index2' => 'content2',
    'index3' => 'content3',
    'index4' => 'content4',
]['index3'] ?? 'Conteúdo não encontrado';

Você consegue definir qual a saída obtida pelo código acima? Talvez não, mas não se preocupe você não está sozinho.

Esta técnica de programação nem possui um nome bem definido, o melhor que consegui identificar na internet seria Array Anônimo, mas ainda assim com muitas divergências de opiniões sobre o tema. Não importa, vamos entender como tirar proveito disto.

Primeiro vamos entender o código acima postando um exemplo mais simples.

echo [ 'index1' => 'content1']['index1'];

Melhorou? Que tal simplificar para algo mais usual, mais conhecido pelos devs.

$y = array('index1' => 'content1');
echo $y['index1'];

E agora? Ficou mais familiar? Reconhece esta sintaxe do PHP Array?

Então, Array Anônimo nada mais é do que utilizar o conteúdo de um Array sem ter que definir este conteúdo em uma variável nomeada. Vamos a mais um exemplo possível.

foreach ([
  'index1' => 'content1',
  'index2' => 'content2',
  'index3' => 'content3',
  'index4' => 'content4'
] as $index => $value) {
  echo 'Index: ' . $index . ' Value: ' . $value;
}

Até aqui o objetivo foi descrever o mecanismo do array anônimo. Acredito que foi possível entender o funcionamento do mecanismo através dos exemplos simplificados.

Mas qual a aplicação prática real desta técnica de programação? Para que serve? Como posso tirar proveito disto? Bom, a resposta é simples.

  • Qualidade de código (clean code)
  • Programação sem ramificações (branchless programming)

Para exemplificar esta aplicabilidade, vamos considerar o seguinte cenário. Temos uma aplicação que roda em múltiplos servidores e precisamos obter uma chave secreta dada uma lista de servidores.

class ServerSecret {
  public static function mySecret($servername) {
    return [ 
      'server1' => 'xyz',
      'server2' => 'wsx',
      'server3' => 'oiu',
    ][$servername] ?? self::throwInvalidServer($servername);
  }

  private static function throwInvalidServer(string $servername) {
    throw new \LogicException(
      sprintf('Servidor %s não foi encontrado', $servername);
    );
}

Pronto, temos aí uma aplicação concreta para o uso do Array Anônimo. Você não precisa se ater muito ao que o código está fazendo, até por que é possível obter o mesmíssimo resultado codificando sintaxes diferentes.

O mais importante de se observar é como o código foi organizado, limpeza, clareza, funcionalidade e principalmente a ausência de IFs e ramificações de códigos.

Para quem quiser se aprofundar um pouco mais no assunto recomendo pesquisar por Branchless Programming.