PHP - arrays e valores como chave

alyatek

alyatek

Posted on April 13, 2024

PHP - arrays e valores como chave

Qual a maneira mais eficaz, tanto de escrita como de processamento, de alcançarmos um key by no PHP?

O problema

Na minha pequena carreira de programador (já estou perto dos 6 anos nisto), um dos maiores problemas que encontro, com certa frequ*ência, a trabalhar com arrays é o típico *key by - que em termos gerais consiste em utilizar um valor do array multidimensional como a chave para ele mesmo.

Um exemplo de um array que tipicamente podemos encontrar no dia a dia (embora reduzido para exemplificar).

<?php

$products = [
    [
        'id' => 437,
        'name' => 'Sapatilhas Amarelas',
        'price' => 3500,
        'slug' => 'sapatilhas-amarelas'
    ],
    [
        'id' => 548,
        'name' => 'Sapatilhas Azuis',
        'price' => 7500,
        'slug' => 'sapatilhas-azuis'
    ],
];
Enter fullscreen mode Exit fullscreen mode

Agora independentemente do motivo, imaginemos que precisamos de encontrar um dos artigos através da chave slug.

Encontrar um array baseado na key do valor

Existe duas formas comuns de alcançar isso, sendo a mais comum e amadora, efetuar um foreach até encontrarmos o valor que pretendemos - pode-se ver isto como efetuar um brute force attack ao array.

Outra abordagem é o array_filter, que de certa forma é um foreach que valida se um condição é verdade e decide excluir ou manter o registo do array que proporcionamos. A vantagem deste meio é que podemos atribuir o resultado diretamente a uma variável e usar uma função anónima ou um função de arrow.

A desvantagem é que se pretendemos obter só um 1 registo teremos que chamar a primeira posição do array - pois apenas foi removido do array os valores que não pretendemos.

Tipicamente podiamos fazer um foreach até encontrar o que queremos, da seguinte forma:

<?php

$slug = 'sapatilhas-azuis';
$store = [];

foreach($products as $product) {
    if($product['slug'] === $slug) {
        $store = $slug;
        continue;
    }
}
Enter fullscreen mode Exit fullscreen mode

Usando o onlinephp.io para efetuar os benchmarks, com 50 000 registos no array, este metódo de efetuar um foreach demorou em média 0,0055 segundos o que podemos considerar como "nada".

Por outro lado se efetuarmos um array_filter com o mesmo número de artigos:

<?php

$slug = 'sapatilhas-azuis';
$store = [];

$filteredProducts = array_filter(
    $products, 
    fn($product) => $product['slug'] === $slug
);

$product = $filteredProducts[0];
Enter fullscreen mode Exit fullscreen mode

Conluimos que, infelizmente apesar de ser mais elegante não parece o mais eficiente, pois a média resulta em 0,0107 segundos, o que representa um acréscimo de ~50% de tempo.

Estes processos são insignificantes quando apenas queremos ir buscar uma key. Mas quando precisamos de repetir a busca no mesmo processo, e aumentamos a complexidade da condição, já começa a ser bastante custuoso tanto em termos de código como processamento desnecessário.

Qual a forma mais correta de efetuar o key by?

Podemos simplesmente dizer que o mais eficiente é fazer um loop ao array e colocar a propria key como o valor que pretendemos.

<?php

function keyBy(string $key, array $values): array {
    $array = [];

    foreach($values as $value){
        // antes de cada associamente deviamos
        // primeiro validar se a key existe
        // no array disponibilizado
        $array[$value[$key]] = $value;
    }

    return $array;
}

$formated = keyBy('slug', [ /* array de produtos */ ])
Enter fullscreen mode Exit fullscreen mode

Este processo demorou em média 0,0123 segundos. Mais uma vez, nada de mais.

Resumidamente podemos concluir que este processo é o melhor e mais rápido deles todos, no qual até a framework Laravel utiliza este mecanismo para alcançar o keyBy - apesar de existir mais algumas validações para garantir consistência no resultado.

Isto é a solução mais correta quando temos a disponibilidade de ter uma função global para isto.

Mas nem sempre isso é o caso - recentemente estive a implementar uma funcionalidade num produto relativamente antigo, onde não me sinto à vontade de começar a criar funções a torto e a direito no código - então queria uma solução mais elegante/"bonita" que podesse repetir sem caso fosse necessário:

<?php
$products = [ /* ... */ ];

$keys = array_column($products, 'slug');
$array = array_combine($keys, $products);

$product = $array['sapatilhas-azuis'];
Enter fullscreen mode Exit fullscreen mode

Esta solução tem como duração média 0,0068 segundos - significativamente mais rápido que o foreach.

Passando a explicar:

Com o array_column extraimos todos os valores que estão associados à key slug do array products.

De seguida tiramos todo o partido do array_combine que vai utilizar os valores do array_column para associar esse resultado às keys do array dos produtos.

Mas há um pequeno inconveniente com esta solução! Ao contrário do foreach, no qual podemos ter um fallback caso o campo não exista no array, com esta solução não é possível.

A falta do valor irá resultar num missmatch de contagem de keys:

Fatal error: Uncaught ValueError: array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements in...

Conclusão

Pode-se concluir que, a maneira mais correta é de facto utilizar um foreach, mas se a maneira mais correta for uma impossibilidade - tanto em termos de rapidez de execução e desenvolvimento do programador - podemos utilizar a solução do array_combine + array_combine - que é uma extremamente intuitiva e rápida de compreender.

💖 💪 🙅 🚩
alyatek
alyatek

Posted on April 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related