PHP Como Verificar Se Um Array é 100% Sequencial

function isArrayFullySequential(array $array): bool {
    return count(array_filter($array, 'is_int', ARRAY_FILTER_USE_KEY)) === count($array);
}

Então… há algum tempo eu escrevi aqui no blog uma função para testar se um array era 100% associativo e no começo do texto eu comentei que não sabia quando você precisaria usar uma função como essa. Pois é, mordendo a língua eu acabei precisando agora testar o contrário, se um array era composto por índices 100% numéricos.

Ao integrar o sistema com uma API externa estruturada com XML tendo um elemento previsto com até 1000 ocorrências que seria carregado a partir de um array. Decidi por utilizar o índice do array como ID das ocorrências no XML, assim implantei o requisito de ter este array indexado 100% com chaves numéricas.

Ainda estou pensando se vou manter esta abordagem ou não, talvez eu acabe flexibilizando a carga do array e controle apenas o total de ocorrências.

De qualquer forma segue abaixo a função que valida se os índices numéricos do array estão dentro dos limítrofes esperados.

hasItemsIndexOutOfBounds(array $array): bool {
    $keys = array_keys($array);
    return count($keys)
        && (min($keys) < 1 || max($keys) > 1000);
}

Ea í, curtiu? Não sei quando você vai precisar usar usar uma destas funções, mas fica aí o exemplo pra quando aparecer a necessidade.

PHP Flex Search Encontrando Registros Por Múltiplos Argumentos Numa só Tacada

$inputTextString = 'cano pvc 3mm rosca azul tigre pc';
$baseQuery = 'SELECT * FROM product WHERE 1=1 %s';

$flexQuery = ' %1$s (product.code LIKE \'%%%2$s%%\' OR product.long_desc LIKE \'%%%2$s%%\' OR product.short_desc LIKE \'%%%2$s%%\' OR product.brand LIKE \'%%%2$s%%\' OR product.measure_unit LIKE \'%%%2$s%%\')';
            
$flexWhere = '';
$flexArgs = explode(' ',$inputTextString);
foreach($flexArgs as $flexArg) {
  $flexAnd = empty($flexWhere) ? 'AND(' : 'AND';
  $flexWhere .= vsprintf($flexQuery,[$flexAnd, $flexArg]); 
}

$flexWhere .= empty($flexWhere) ? '' : ')';
$flexSearch = sprintf($baseQuery, $flexWhere);

$conn = mysqli_connect('localhost', 'username', 'password', 'database');
$result = mysqli_query($conn, $flexSearch);

Sabe quando você vai àquele material de construção e o vendedor não consegue achar o produto que você pediu dentro do sistema de vendas? Pois é, que falta de sorte né!

No meu caso eu acho que dei bastante sorte quando iniciei na programação WEB no início dos anos 2000… trabalhando com sistemas eCommerce uma das jóias do nosso aplicativo PHP era a busca de produtos simplesmente informando todas as características para pesquisa num único campo string.

Todas as informações como partes do nome, marca, modelo, dimensões, unidade de medida eram informados num único campo TYPE TEXT simplesmente separando as partes por espaço em branco, o separador universal mais rápido de ser digitado.

Como a técnica não tinha sido nomeada até então eu acabei dando o nome de FLEX SEARCH por que é exatamente isto que este conceito de busca faz, flexibiliza a pesquisa de registros para o operador.. o desenvolvedor que se vire pra fazer o código funcionar.

Observe no código que a busca é flexibilizada ao máximo, pois tentamos encontrar os fragmentos de pesquisa informados não somente na descrição do produto, mas também em outros campos na tabela, e esta busca é feita de forma não obstrutiva, ou seja, a busca tentar encontrar os fragmentos onde for possível mas não requer obrigatoriedade de existência em todos os pontos.

Se você imprimir o conteúdo em $flexSearch você vai encontrar uma query como o SELECT logo abaixo. Então basta simplesmente executar a instrução SQL para obter os registros desejados.

SELECT * FROM product WHERE 1=1  AND( (product.code LIKE '%cano%' OR product.long_desc LIKE '%cano%' OR product.short_desc LIKE '%cano%' OR product.brand LIKE '%cano%' OR product.measure_unit LIKE '%cano%') AND (product.code LIKE '%pvc%' OR product.long_desc LIKE '%pvc%' OR product.short_desc LIKE '%pvc%' OR product.brand LIKE '%pvc%' OR product.measure_unit LIKE '%pvc%') AND (product.code LIKE '%3mm%' OR product.long_desc LIKE '%3mm%' OR product.short_desc LIKE '%3mm%' OR product.brand LIKE '%3mm%' OR product.measure_unit LIKE '%3mm%') AND (product.code LIKE '%rosca%' OR product.long_desc LIKE '%rosca%' OR product.short_desc LIKE '%rosca%' OR product.brand LIKE '%rosca%' OR product.measure_unit LIKE '%rosca%') AND (product.code LIKE '%azul%' OR product.long_desc LIKE '%azul%' OR product.short_desc LIKE '%azul%' OR product.brand LIKE '%azul%' OR product.measure_unit LIKE '%azul%') AND (product.code LIKE '%tigre%' OR product.long_desc LIKE '%tigre%' OR product.short_desc LIKE '%tigre%' OR product.brand LIKE '%tigre%' OR product.measure_unit LIKE '%tigre%') AND (product.code LIKE '%pc%' OR product.long_desc LIKE '%pc%' OR product.short_desc LIKE '%pc%' OR product.brand LIKE '%pc%' OR product.measure_unit LIKE '%pc%'))

Resumindo, a busca só vai falhar se algum fragmento de pesquisa informado não existir em nenhuma das fontes possíveis. O PHP ajuda muito na construção de queries dinâmicas, e este SQL é bastante padrão ANSI e deveria rodar fácil em pelo menos uma dúzia de diferentes bancos de dados.

Da próxima vez que for ao comércio e se deparar com um vendedor em dificuldades, você vai lembrar… vai aí um Flex Search?!!!

PHP Array Entenda como Obter Vantagens com as Funções Array Prontas

function bestProducts($products, $sales): array {
  $ySalesProducts = explode(' ', $sales);

  $yFiltered = array_filter(
    $ySalesProducts,
    function ($item) use ($products) {
      return in_array($item, $products, true);
    });
 
  $yCount = array_count_values($yFiltered);
  arsort($yCount);
  $ySlice = array_slice($yCount, 0, 2);
  [$first, $second] = array_values($ySlice);

  ($first === $second)
    ? ksort($ySlice)
    : null;

    return $ySlice;
}

$products = [ 'phone', 'mobile', 'game', 'headset', 'hdd', 'ssd' ];
$context = 'phone ssd headset ssd headset mobile headset phone game headset hdd ssd game ssd ssd headset game box box box box box box';

$result = bestProducts($products, $context);
var_dump($result);

Há algum tempo eu estava num desafio de codificação na Plataforma Hacker Rank e apareceu uma questão que usava muitos recursos de ARRAY… pensei comigo, tá no papo! Só que não…

Meu principal erro não foi subestimar o problema, mas sim subestimar a memorização das funções array e o porquê delas existirem. Como de costume, eu sempre recorro as documentações do PHP.net quando estou programando, só que durante uma maratona ou num desafio de programação ter estas funcionalidades prontamente memorizadas faz uma tremenda falta.

Churumelas postas, vamos ao enunciado do problema.

Dada uma lista de produtos ($products) previamente definida, contabilize a quantidade vendida de cada produto dada uma listagem de produtos vendidos ($context). Somente os produtos constantes na lista previamente definida devem ser contabilizados. Ao final selecione apenas os dois produtos mais vendidos, e caso haja um empate classifique os produtos em ordem alfabética. O retorno deve ser um array associativo produto x quantidade.

Observando o enunciado podemos perceber alguns recursos que serão necessários… explode para transformar a lista de produtos vendidos em array, array_filter para excluir quem não está na listagem de produtos pré-definidos, count para contar, sort para classificar, array_shift para pegar os produtos mais vendidos…

Podemos parar por aqui, boa parte destes recursos vão dar na trave, sabe por que? Porque provavelmente você não lembraria de cabeça a diferença entre sort, rsort, asort, arsort, ksort, krsort, usort, uasort, uksort… Talvez você também não lembre algumas funções prontas utilizam exclusivamente índices numéricos e/ou não preservam os índices originais associativos. Outra grande questão é a aplicação em arrays multi-níveis, uma coisa é trabalhar com array simples de um único nível e outra coisa processar array multi-nível em profundidade.

Assim, eu não vou tentar explicar o algoritmo do problema pra vocês; ele é muito simples depois de resolvido e pode facilmente ser entendido apenas você lendo o código. Conforme o enunciado também podemos perceber que os arrays aplicados são de nível simples onde as funções array prontas dão um show. Então eu vou explicar a seguir o que você deveria ter mente ou ter decorado sobre algumas funções array prontas, por que esse é o verdadeiro diferencial que você pode obter.

SORT – sort ordena um array pelo valor do elemento, mas ele despreza completamente os índices originais do array. Sort opera sobre o array original modificando-o completamente, assim bye bye array original depois do sort. Então sort não serve pra você se você precisa manter os índices associativos preservados depois da ordenação.

$y = (['fruit'=>'lemon','salad'=>'lettuce','protein'=>'eggs','carbohydrate'=>'pasta']);
sort($y);
print_r($y);
// [0] => eggs
// [1] => lemon
// [2] => lettuce
// [3] => pasta

RSORT – o mesmo que sort, só que ordena em ordem inversa. Repare que o R a frente de sort significa REVERSE. Lembre-se deste R a frente do sort, ele vai aparecer em outros comandos e terá a mesma semântica.

$y = ['fruit'=>'lemon','salad'=>'lettuce','protein'=>'eggs','carbohydrate'=>'pasta'];
rsort($y);
print_r($y);
// [0] => pasta
// [1] => lettuce
// [2] => lemon
// [3] => eggs

ASORT – o mesmo que sort, só que este aqui preserva os índices originais intactos. Repare que o A a frente de sort significa ASSOCIATIVE, indicando que o comando aplica-se a operações com arrays associativos. Os índices numéricos também serão preservados após a ordenação.

$y = (['fruit'=>'lemon',3=>'bread','salad'=>'lettuce',0=>'rice','protein'=>'eggs',1=>'beans','carbohydrate'=>'pasta']);
asort($y);
print_r($y);
// [1] => beans
// [3] => bread
// [protein] => eggs
// [fruit] => lemon
// [salad] => lettuce
// [carbohydrate] => pasta
// [0] => rice

ARSORT – o mesmo que asort só que a ordenação é feita em ordem inversa. Repare novamente o R antes do sort indica REVERSE ou seja, ordem inversa.

$y = (['fruit'=>'lemon',3=>'bread','salad'=>'lettuce',0=>'rice','protein'=>'eggs',1=>'beans','carbohydrate'=>'pasta']);
arsort($y);
print_r($y);
// [0] => rice
// [carbohydrate] => pasta
// [salad] => lettuce
// [fruit] => lemon
// [protein] => eggs
// [3] => bread
// [1] => beans

KSORT – ordena um array pelo índice e não pelo valor do elemento como faz o sort. Repare que K a frente do sort significa KEY. Aqui nos deparamos com uma primeira situação inusitada, como é comum termos índices mistos compostos por números e strings o tipo de ordenação padrão pode não atender as nossas expectativas. Para corrigir este comportamento basta ativar o segundo parâmetro informando SORT_STRING ou SORT_NATURAL, isso deve resolver a questão.

$y = ['fruit'=>'lemon',3=>'bread','salad'=>'lettuce',0=>'rice','protein'=>'eggs',1=>'beans','carbohydrate'=>'pasta'];
ksort($y);
print_r($y);
// [carbohydrate] => pasta
// [fruit] => lemon
// [salad] => lettuce
// [0] => rice
// [protein] => eggs
// [1] => beans
// [3] => bread

$y = ['fruit'=>'lemon',3=>'bread','salad'=>'lettuce',0=>'rice','protein'=>'eggs',1=>'beans','carbohydrate'=>'pasta'];
ksort($y, SORT_STRING);
print_r($y);
// [0] => rice
// [1] => beans
// [3] => bread
// [carbohydrate] => pasta
// [fruit] => lemon
// [protein] => eggs
// [salad] => lettuce

KRSORT – o mesmo que ksort só que ordenado em ordem inversa.

$y = ['fruit'=>'lemon',3=>'bread','salad'=>'lettuce',0=>'rice','protein'=>'eggs',1=>'beans','carbohydrate'=>'pasta'];
krsort($y, SORT_NATURAL);
print_r($y);
// [salad] => lettuce
// [protein] => eggs
// [fruit] => lemon
// [carbohydrate] => pasta
// [3] => bread
// [1] => beans
// [0] => rice

ARRAY_SLICE – esse aqui é animal, se você precisa quebrar um array em pedaços, sacar a ponta da frente ou até mesmo a ponta de trás do array, mas precisa que esta quebra preserve a combinação key => value do array original?! array_slice é a sua pedida.

$y = ['fruit'=>'lemon',3=>'bread','salad'=>'lettuce',0=>'rice','protein'=>'eggs',1=>'beans','carbohydrate'=>'pasta'];
krsort($y, SORT_STRING);
print_r($y);
// [salad] => lettuce
// [protein] => eggs
// [fruit] => lemon
// [carbohydrate] => pasta
// [3] => bread
// [1] => beans
// [0] => rice

$ys = array_slice($y, 0, 2);
print_r($ys);
// [salad] => lettuce
// [protein] => eggs

ARRAY_PUSHARRAY_POPARRAY_SHIFTARRAY_UNSHIFT – Lembre-se que estas funções não objetivam trabalhar com índices associativos então nenhuma destas funções servirão para o nosso caso. Todos esses quatro comandos operam em nível de elemento valor sem se importar com índices. A cada interação por estes comandos os índices do array são sistematicamente resequenciados.

$y = [];
array_push($y, 'first');
array_push($y, 'second');
array_push($y, 'third');
print_r($y);
// [0] => first
// [1] => second
// [2] => third
$ys = array_shift($y);
print_r($ys);
// first
array_unshift($y, 'zero');
print_r($y);
// [0] => zero
// [1] => second
// [2] => third
$yp = array_pop($y);
print_r($yp);
// third
print_r($y);
// [0] => zero
// [1] => second

ARRAY_COUNT_VALUES – fantástico recurso para contabilizar as ocorrências dos valores em um array. Pense no array de produtos vendidos, usando array_count_values teremos como retorno um array tendo os produtos como chave e as ocorrências de cada produto sumarizadas como valor.

$c = 'phone ssd headset ssd headset mobile headset phone game headset hdd ssd game ssd ssd headset game box box box box box box';
$y = explode(' ', $c);
print_r($y);
// [0] => phone              // [12] => game
// [1] => ssd                // [13] => ssd
// [2] => headset            // [14] => ssd
// [3] => ssd                // [15] => headset
// [4] => headset            // [16] => game   
// [5] => mobile             // [17] => box
// [6] => headset            // [18] => box
// [7] => phone              // [19] => box
// [8] => game               // [20] => box
// [9] => headset            // [21] => box
// [10] => hdd               // [22] => box
// [11] => ssd

$yc = array_count_values($y);
print_r($yc);
// [phone] => 2
// [ssd] => 5
// [headset] => 5
// [mobile] => 1
// [game] => 3
// [hdd] => 1
// [box] => 6

ARRAY_KEYS – retorna os índices de um array como um array de valores. Este novo array retornado será chaveado por índice numérico sequencial.

$y = ['phone'=>2,'ssd'=>5,'headset'=>5,'mobile'=>1,'game'=>3,'hdd'=>1,'box'=>6];
$yk = array_keys($y);
print_r($yk);
// [0] => phone
// [1] => ssd
// [2] => headset
// [3] => mobile
// [4] => game
// [5] => hdd
// [6] => box

ARRAY_VALUES – retorna os valores de um array como um array de valores desprezando os índices originais. O novo array será chaveado por índice numérico sequencial. O array_values trabalha apenas com um nível de array, assim arrays com multi-níveis contendo outros arrays ou objetos serão processados no seu primeiro nível, mas estes subarrays e objetos permanecerão intactos em sua estrutura.

$y = ['phone'=>2,'ssd'=>5,'headset'=>5,'mobile'=>1,'game'=>3,'hdd'=>1,'box'=>6,'sub'=> ['ps5'=>10]];
$yv = array_values($y);
print_r($yv);
// [0] => 2
// [1] => 5
// [2] => 5
// [3] => 1
// [4] => 3
// [5] => 1
// [6] => 6
// [7] => Array
// (
//   [ps5] => 10
// )

ARRAY_FLIP – inverte a posição de índices e valores, ou seja, os valores passam a ser índices e os índices passam a ser valores. Mas o array_flip trabalha somente com arrays simples com índices e valores do tipo string e/ou numérico. Caso seu array contenha um outro array ou um objeto o array flip vai emitir um warning mas ainda assim vai funcionar, porém os subarrays e objetos serão ignorados e não farão parte da nova array retornada.

$y = ['phone'=>2,'ssd'=>5,'headset'=>5,'mobile'=>1,'game'=>3,'hdd'=>1,'box'=>6,'sub'=> ['ps5'=>10]];
$yf = array_flip($y);
print_r($yf);
// [2] => phone
// [5] => headset
// [1] => hdd
// [3] => game
// [6] => box

ARRAY_SUM – sumariza ou contabiliza os elementos valores de um array desde que os valores possam ser convertidos para número. Repare que no exemplo o produto box que contém o valor 6txt será convertido para o número 6, pois este é o comportamento de conversão padrão esperado do PHP. Qualquer elemento que não possua um valor numérico, como subarray, objeto, ou texto, não será contabilizado. O retorno é um escalar com o total da somatória dos elementos numéricos.

$y = ['phone'=>'two','ssd'=>5,'headset'=>5,'mobile'=>1,'game'=>3,'hdd'=>1,'box'=>'6txt','sub'=> ['ps5'=>10]];
echo 'total: ' . array_sum($y);
// total: 21

ARRAY_FILTER – O famigerado array_filter é um dos seus maiores aliados. O simples fato de parsear um array com array_filter já elimina todos os elementos com valor null, sem necessidade de nenhuma função callback. Combinado com uma função callback então o poder de sanitização é exponencial. Mas lembre-se, array_filter não é multilevel e não serve para sanitização em profundidade.

$y = ['phone'=>'two','ssd'=>null,'headset'=>'','mobile'=>1,'game'=>3,'hdd'=>null,'box'=>6,'sub'=> ['ps5'=>10, 'ps3'=>null]];
$yt = array_filter($y);
print_r($yt);
// [phone] => two
// [mobile] => 1
// [game] => 3
// [box] => 6
// [sub] => Array
// (
//   [ps5] => 10
//   [ps3] =>
// )

$ytc = array_filter(
  $y,
  function($value) {
    return !empty($value);
  });
print_r($ytc);
// [phone] => two
// [mobile] => 1
// [game] => 3
// [box] => 6
// [sub] => Array
// (
//   [ps5] => 10
//   [ps3] =>
// )

ARRAY_REVERSE – e sempre teremos o simples reverse para inverter a ordem de um array. Quando você precisa transformar uma pilha LIFO and uma fila FIFO uma ordenação por sort não é seja desejada, então simplesmente aplica-se a inversão da ordem do array com o array_reverse.

$y = (['fruit'=>'lemon',3=>'bread','salad'=>'lettuce',0=>'rice','protein'=>'eggs',1=>'beans','carbohydrate'=>'pasta']);
$yr = array_reverse($y);
print_r($yr);
// [carbohydrate] => pasta
// [0] => beans
// [protein] => eggs
// [1] => rice
// [salad] => lettuce
// [2] => bread
// [fruit] => lemon

ARRAY_COLUMN – esta função é muito útil para manipular dados no formato tipo tabela composto por linhas e colunas. Assim, um array simples não vai funcionar com array_column por que ele precisa receber um array de arrays e é o segundo nível que deve conter os arrays associativos composto pelas colunas de cada linha. Até mesmo objetos são suportados pelo array_column.

Os índices do primeiro nível são irrelevantes para o processo, podem ser numéricos sequenciais ou associativos, eles serão desprezados de qualquer forma.

Além de buscar os valores da coluna desejada, o terceiro parâmetro ainda indica o nome de uma possível coluna na mesma linha cujo valor possa servir de índice para chavear o valor retornado. No mundo real uma chave única como ID seria o indicado. Lembrando que a coluna desejada não precisa existir em todas as linhas de qualquer linear, as linhas que não possuírem esta coluna serão desprezadas durante o processamento.

$y = [
['id'=>'0000','first'=>'Jonh','last'=>'Doe','email'=>'jonh.doe@email'],
['id'=>1000,'first'=>'Mary','last'=>'Loe','email'=>'mary.loe@email'],
['id'=>2000,'first'=>'Marilyn','last'=>'Moe','email'=>'marilyn.moe@email'],
'one' => ['id'=>1,'first'=>'Jonh','last'=>'Doe','eeeemail'=>'jonh.doe@email'],
'two' => ['id'=>2,'first'=>'Mary','last'=>'Loe','email'=>'mary.loe@email'],
'three' => ['id'=>3,'first'=>'Marilyn','last'=>'Moe','email'=>'marilyn.moe@email'],];
$yc = array_column($y,'email','id');
print_r($yc);
// [0000] => jonh.doe@email
// [1000] => mary.loe@email
// [2000] => marilyn.moe@email
// [2] => mary.loe@email
// [3] => marilyn.moe@email

ARRAY_MERGE – O array_merge é bastante controverso e as vezes não produz o resultado que você está querendo, mas ele funciona conforme foi projetado e as regras são bem claras.

O array_merge usa um array como base e um outro array adicional para ser mergido no array base. Caso um índice associativo no array adicional exista no array base o valor do array adicional será sobrescrito no array base. Caso o índice associativo no array adicional não exista no array base ele será adicionado no array base. Todos os índices numéricos no array adicional serão adicionados no array base com resequenciamento automático, ou seja, não importa se o índice numérico exista ou não no array base, ele será inserido independentemente de qualquer condição.

$ybase = ['fruit'=>'apple','drink'=>'water','snack'=>'chips',0=>'juice',1=>'banana'];
$yadd = ['fruit'=>'naranja','drink'=>'leche',0=>'manzana',1=>'pera'];
$yg = array_merge($ybase, $yadd);
print_r($yg);
// [fruit] => naranja
// [drink] => leche
// [snack] => chips
// [0] => juice
// [1] => banana
// [2] => manzana
// [3] => pera

ARRAY MERGE + SUGAR SYNTAX – Uma outra forma de você fazer um merge de arrays é utilizando o operador plus (+). Só que o comportamento desta sintaxe é um pouco diferente da função array_merge.

Considere o array base e o array adicional, com a sugar syntax os índices do array adicional que não existirem no array base serão adicionados no array base, e ponto final. Assim se o objetivo é simplesmente adicionar elementos omissos no array base a sugar syntax provê um resultado melhor.

$ybase = ['fruit'=>'apple','drink'=>'water','snack'=>'chips',0=>'juice',1=>'banana'];
$yadd = ['fruit'=>'naranja','drink'=>'leche',0=>'manzana',1=>'pera',3=>'frambuesa'];
$ysg = $ybase + $yadd;
print_r($ysg);
// [fruit] => apple
// [drink] => water
// [snack] => chips
// [0] => juice
// [1] => banana
// [3] => raspberry

ARRAY_REPLACE – Ao invés de array_merge ou merge sugar syntax considere utilizar array_replace para fazer o merge de arrays. Acontece que o array_replace no final das contas produz o resultado mais esperado na maioria das situações.

O array_replace faz o match entre array base e array adicional para qualquer tipo de índice, associativo e numérico. Os índices que fazem match serão sobrescritos na array base e os índices omissos serão adicionados no array base.

Simples, prático e objetivo!

$ybase = ['fruit'=>'apple','drink'=>'water','snack'=>'chips',0=>'juice',1=>'banana'];
$yadd = ['fruit'=>'naranja','drink'=>'leche',0=>'manzana',1=>'pera',3=>'frambuesa'];
$yr = array_replace($ybase, $yadd);
print_r($yr);
// [fruit] => naranja
// [drink] => leche
// [snack] => chips
// [0] => manzana
// [1] => pera
// [3] => frambuesa

Resumindo, as funções array prontas funcionam como uma caixa de ferramentas, se você não sabe como aplicá-las corretamente acabará usando somente o martelo e a talhadeira! Ou em último caso, só a MARRETA mesmo…

PHP Utilizando Operador Ternário para Executar Ações IF / ELSE

$high_consuming_resources = true;
($high_consuming_resources)
  ? set_time_limit(0)
  : set_time_limit(30);

$high_consuming_resources = false;
($high_consuming_resources)
  ? init_set('memory_limit', '2048M')
  : null;

Depois que a gente começa a trabalhar com Clean Code umas das tendências é tentar ao máximo eliminar os famigerados if / else com brackets.

Então estes dias eu estava adicionando novas features em uma classe e conforme o escopo eu poderia ou não executar um processo de acordo com os parâmetros recebidos, mas não queria dar o braço a torcer… então decidi experimentar algo diferente.

Usando um operador ternário eu simplesmente desprezei a variável para armazenar o resultado da operação. Como as minhas operações eram VOID elas não produziam nenhum retorno mesmo.

Depois foi simples, na parte TRUE / FALSE do ternário coloquei as instruções para executar as operações desejadas. Tem que ser uma operação única, ou então uma operação em cadeia tipo um objeto que retorna self e permite encadear as operações.

Caso você tenha duas ou mais operações você pode encapsulá-las em um novo método e então no ternário teria uma única operação a ser invocada.

Também surgiu a situação onde eu tinha uma operação somente para a parte TRUE do ternário. Então a solução mais lógica foi apenas adicionar um NULL na parte FALSE fechando o ciclo do operador.

O mesmo código acima poderia ser escrito desta forma com o tradicional IF / ELSE…

$high_consuming_resources = true;
if ($high_consuming_resources) {
    set_time_limit(0);
} else {
    set_time_limit(30);
}

$high_consuming_resources = false;
if ($high_consuming_resources) {
    init_set('memory_limit', '2048M');
}

E aí? Qual destas formas você acha mais legível ou mais agradável para debugar mentalmente?

Confesso que acabei me acostumando com o ternário, na hora de ler o código parece que flui melhor o raciocínio.

Sei lá, pode ser só preferência pessoal mesmo!

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?

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