PHP Array Walk Sanitizar Objeto de forma recursiva

function sanitizeObject($object): void {
  array_walk($object, function($value, $key) use($object) {
    if (is_object($value) || is_array($value)) {
      self::sanitizeObject($value);
    }

    if ($value === null
        || (is_string($value) && empty($value))
        || (is_array($value) && !$value)
        || (is_object($value) && !(array)$value)
    ) {
      unset($object->{$key});
    }
  });
}

Bom pessoal, ao invés de explicar o que este código faz vou tentar explicar qual era o problema que eu tinha de resolver, acho que dessa forma o artigo fará mais sentido pra quem estiver lendo.

Eu estava trabalhando numa integração entre sistemas e a API que recebia as informações era bastante sensível quanto ao conteúdo da request. Por exemplo, o comportamento da API poderia variar muito entre eu enviar uma propriedade com conteúdo NULL ou então com a propriedade com conteúdo vazio. Em ambos os casos o melhor cenário seria não enviar a propriedade na request.

Já no início recebi uma dica de usar o array_filter para eliminar as propriedades nulas ou vazias, como este código aqui por exemplo:

function clearNullFields($metadata) {
  return (object)array_filter((array)$metadata, static function ($value) {
    return !(null === $value || trim($value) === '');
  });
}

O array_filter até poderia resolver a questão, isso se o objeto tivesse um único nível. Mas não era esse o caso, uma request de API normalmente é composta por vários outros objetos em múltiplos níveis.

Depois de pesquisar um pouco me deparei com um artigo onde a solução era dada por um array_walk. Bingo!

Bastou esta centelha e um pouco de criatividade para adicionar a recursividade fazendo com que todas as propriedades nulas ou vazias fossem sistematicamente removidas com unset.

Até mesmo um objeto mais interno cujas propriedades eram todas nulas ou vazias acabou sendo completamente removido. Isto porque a recursividade resolve as remoções do nível mais baixo para o nivel mais alto.

Assim após remover todas as propriedades escalares de um objeto, ele mesmo será um objeto vazio. Consequentemente ao retornar da recursividade inferior o objeto estará vazio e será removido exatamente por este motivo.

Mas um aviso importante!

O array_walk opera sobre o objeto fornecido alterando suas características originais. Assim para não comprometer nenhum processo que utilizasse o objeto original a solução foi criar um objeto totalmente novo para ser sanitizado. Como?

$sanitize = \json_decode(\json_encode($original));
sanitizeObject($sanitize);

Fantástico, funciona melhor do que o programado!

PHP DateTime Diff Calculando diferença de dias entre datas

function calculateDiffInDays(string $date1, string $date2) {
  $datetime1 = new \DateTime($date1);
  $datetime2 = new \DateTime($date2);
  
  return $datetime1->diff($datetime2)->days;
}

echo calculateDiffInDays('1970-08-25', '2020-12-30');
// 18390
echo calculateDiffInDays('2020-12-30', '1970-08-25');
// 18390

Recentemente precisei ajustar um processo que já fazia um cálculo de diferença de dias entre datas e me deparei com um código bem similar a este, só que não funcionava.

Daí fui pesquisar a boa e velha documentação do PHP e encontrei a falha.

Primeiro vamos esclarecer que o DateTime::diff retornar um DateInterval o qual então pode ser formatado de várias formas… https://www.php.net/manual/en/class.dateinterval.php

Observe que days representa o total de número de dias entre a primeira e segunda datas e isso significa dizer que o resultado será um número inteiro.

Enquanto isso d representa simplesmente um possível dia, ou seja, será um número entre 0 e 30 pois o cálculo é feito levando em conta apenas a porção dia. Assim a hipótese para o maior resultado seria o dia 31 menos o dia 01 sendo o resultado 30.

Então, o código original com o qual me deparei formatava o resultado usando a tag ->d quando na verdade deveria estar utilizando ->days para obter a diferença total de dias.

return $datetime1->diff($datetime2)->d;

Confesso que apenas lendo a documentação fica difícil de ter certeza do resultado assim de imediato. Como sempre com PHP um teste rápido é o seu melhor aliado.

Mas todos nós sabemos que o PHP faz o que é preciso fazer ou faz o que a gente quer que ele faça, não importa.

O que algumas pessoas costumam usar para criticar o PHP, na verdade é o que eu mais gosto nele!

PHP Criando DateTime sem a porção horas

$datetime_without_hours = \DateTime::createFromFormat('!Y-m-d', '1970-08-25');
// 1970-08-25 00:00:00.000000

$datetime_with_hours = \DateTime::createFromFormat('Y-m-d', '1970-08-25');
// 1970-08-25 05:07:32.000000

$datetime_with_hours->setTime(0,0);
// 1970-08-25 00:00:00.000000

Esta semana mesmo eu estive trabalhando num projeto onde tive que incorporar diversos campos de data num integração de sistemas com PHP.

Decidi que enviar os campos com o tipo DateTime para garantir que o aplicativo que recebia a informação pudesse utilizar as datas de forma segura, mas acabei me deparando com a questão de introspecção de horas para uma informação genuinamente Date.

$datetime_with_hours = \DateTime::createFromFormat('Y-m-d', '1970-08-25');
// 1970-08-26 05:07:32.000000

Na primeira tentativa utilizei a máscara Y-m-d simples e verifiquei que a porção Horas:Minutos:Segundos foi inserida na composição da data. O PHP utilizou o Time corrente do Server e incorporou na data.

Depois de uma rápida pesquisa na internet identifiquei que este é o comportado esperado da função. Para evitar a porção horas na composição da data podemos fazer o uso da exclamação na máscara durante a criação do DateTime.

Mas atenção, a inclusão da exclamação na máscara !Y-m-d fará com que apenas um reset da data e hora corrente seja executado antes do datetime ser criado. No entanto, todos os elementos explicitamente definidos na máscara serão tomados em consideração no momento da criação do objeto datetime.

$datetime_with_hours = \DateTime::createFromFormat('!Y-m-d H:i:s', '1970-08-25 13:14:15');
// 1970-08-25 13:14:15.000000

E caso você já possua um objeto que tenha sido criado com a porção horas na composição, você pode resetar este conteúdo para algo qualquer inclusive a nossa desejada hora ZERO, conforme demonstrado abaixo.

$datetime_with_hours = \DateTime::createFromFormat('Y-m-d', '1970-08-25');
// 1970-08-25 05:07:32.000000

$datetime_with_hours->setTime(0,0);
// 1970-08-25 00:00:00.000000

Também, se a data a ser criada seja algo próxima da data corrente você pode utilizar parâmetros no construtor do objeto DateTime para criar o objeto já sem a porção horas.

$datetime_without_hours = new \DateTime("midnight");
// 2021-11-13 00:00:00.000000

$datetime_without_hours = new \DateTime("today");
// 2021-11-13 00:00:00.000000

$datetime_without_hours = new \DateTime("yesterday");
// 2021-11-12 00:00:00.000000

$datetime_without_hours = new \DateTime("tomorrow");
// 2021-11-14 00:00:00.000000

E aí? Curtiu estes recursos para criação de datas?

PHPStorm Instale os Plugins Code With Me e PHP Inspections hoje mesmo

Em se tratando de produtividade nada melhor que uma IDE especialmente configurada para suas necessidades.

Para quem usa o PHPStorm muitos plugins já vem instalados por padrão, mas recentemente recebi a indicação destes dois plugins que são uma mão na roda no dia a dia.

O PHP Inspections é um analisador de códigos estático, ou seja, ele sinaliza em tempo real o que você pode melhorar no código, como indicar o tipo de dado no retorno de funções, variáveis sem uso, conflitos de tipos de dados e muito mais.

Certamente você enviará códigos muito mais limpos e sem erros básicos para a fase de Code Review, então não perca tempo, instale este ajudante agora mesmo.

Já o plugin Code With Me é uma ferramenta para desenvolvimento colaborativo e Pair Programming.

Para quem está acostumado a compartilhar a tela pelo ZOOM sabe que neste tipo de pair programming uma das partes vira espectadora, e para fazer o switch de um para outro é bem trabalhoso.

Mas com o Code With Me ambos desenvolvedores são ativos, por que ao iniciar uma sessão a IDE do Hoster é compartilhada em tempo real, tornando a experiência do pair programming remoto muito melhor e com melhores resultados.

Então, na próxima vez já sabe, vai de Code With Me!

PHPStorm Aumentando o tamanho da fonte usando command+mouse wheel

Nos momentos atuais onde o trabalho remoto está sendo extensivamente utilizado, quem aí não precisou aumentar fonte da IDE e teve que usar o famigerado View/Presentation Mode?

Para quem usa o PHPStorm esta acaba sendo a principal forma de configurar a IDE quando está compartilhando o código pelo ZOOM ou em um monitor ou televisão para trabalhos em grupos.

Mas o View/Presentation Mode tem uma série de desvantagens e complicadores que os developers não precisam e não querem enfrentar.

Mas agora seus problemas acabaram, pois o PHPStorm tem uma funcionalidade nativa para aumentar ou reduzir o tamanho da fonte “on the go”, no melhor estilo Photoshop combinando simplesmente a tecla Command com o Mouse Wheel.

Basta acessar Preferences, Editor, General e então ativar o Checkbox da opção Change font size with Command+Mouse Wheel.

Prontinho! Adeus View/Presentation Mode…

GIT Renomear uma Branch Local que já foi enviada para o Servidor Remoto

git checkout nome-da-branch-que-eu-nao-quero-mais
git branch -m nome-novo-que-eu-quero-para-branch

git push origin -u nome-novo-que-eu-quero-para-branch
git push origin --delete nome-da-branch-que-eu-nao-quero-mais

Você já deve ter passado por isso, ter criado uma branch no GIT e feito o Push para o servidor e só depois se deu conta que o nome que escolheu não ficou bom ou não representa o que está implementado por ela.

Pois bem, saiba que para corrigir isto é muito simples, independentemente se a branch já foi enviada ao servidor remoto ou não. Vamos explicar o passo a passo.

Primeiro, posicione-se na branch que deseja renomear usando o comando git checkout. Claro que é possível renomear uma branch mesmo se ela não for a branch corrente na sua working copy, mas isto tornaria os comandos mais complexos e mais difíceis de decorar… deixa pra lá!

Em seguida com o comando git branch -m você simplesmente informa o novo nome que deseja para sua branch.

Voilà! Sua branch foi renomeada, mas isso foi feito somente no seu GIT Local. Caso a sua branch ainda não tenha sido enviada para o servidor com o git push você pode parar por aqui. Mas caso já tenha feito o git push antes de renomear ainda temos mais dois comandos para deixar tudo em ordem.

O próximo passo é enviar a branch renomeada para o servidor usando o git push origin -u que indica que estamos resetando o upstream da branch. Se você não fizer isso a sua branch local continuará sendo enviada para o mesmo ponto no servidor remoto, ou seja, a branch com o nome antigo.

Agora, para finalizar temos que nos livrar da branch antiga que ainda está lá no servidor remoto. Para isso basta eliminarmos completamente ela fazendo um git push origin –delete indicando o nome antigo.

Prontinho, a branch antiga sumiu do servidor remoto e a branch renomeada agora existe no servidor remoto, no ambiente local e está na sua área de working copy.

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!