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' ],
];
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; }
}
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];
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 */ ])
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'];
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.
Discover more from Coursity
Subscribe to get the latest posts sent to your email.