Dia 6 — Funções de array: PHP e MySQL

Informatica · Conteudo · publicado em 02/10/2026
Dia 6 de 17

Funções de array

Aula 1

array_map e array_filter

array_map e array_filter

São as duas funções que mais substituem laço em PHP. array_map transforma cada elemento e devolve um array do mesmo tamanho; array_filter seleciona os elementos que passam num teste e devolve um array menor. As duas recebem um *callback* — uma função pasada como argumento — que pode ser nomeada, uma closure ou uma arrow function.

Como se usa

$nomes = ['ana', 'bruno', 'carla'];
$maiusculos = array_map('strtoupper', $nomes);
$longos = array_filter($nomes, fn (string $n): bool => strlen($n) > 4);

O callback é chamado uma vez por elemento. Em array_map o retorno substitui o valor original; em array_filter o retorno é um booleano que decide se o elemento fica ou sai.

FunçãoAssinaturaDevolve
array_maparray_map(callable $callback, array $array, array ...$outros): arraytransformação de cada elemento
array_filterarray_filter(array $array, ?callable $callback = null, int $mode = ARRAY_FILTER_USE_BOTH): arraysó os elementos aprovados
array_map com dois arraysarray_map($fn, $a, $b)o callback recebe os dois valores por vez
array_filter com chavesfn ($valor, $chave) => ...o segundo argumento do callback é a chave
array_filter por chavefn ($chave) => ... com array_keys($linhas, $procura, true) antesbusca pela chave

O que cada uma preserva

array_map com um único array mantém as chaves de origem: num array associativo, array_map('strtoupper', $aluno) devolve ['nome' => 'ANA', ...]. array_filter também mantém as chaves, e é por isso que o resultado de um filtro costuma vir com índice furado ([0, 2, 3]). Duas saídas resolvem isso:

$aprovados = array_values(array_filter($turma, fn (array $a): bool => $a['nota'] >= 7));
$maiores   = array_filter($notas, fn (float $n): bool => $n >= 7, ARRAY_FILTER_USE_BOTH);

O terceiro argumento de array_filter é o modo: 0 (padrão) passa só o valor ao callback, ARRAY_FILTER_USE_KEY passa só a chave, e ARRAY_FILTER_USE_BOTH passa os dois. Sem callback, array_filter($array) remove tudo que for falsy — 0, "", null, [], false — o que quase nunca é o que se quer.

Callback é só uma função. 'strtoupper' (string), fn ($v) => $v * 2 (arrow function) e uma função nomeada são a mesma coisa para estas duas funções. O que muda é quem escreve a lógica, não quem a executa.

Exemplo

<?php
// ============================================================
// AULA 1 - array_map transforma, array_filter seleciona
// ============================================================

declare(strict_types=1);

$notas = [7.5, 4.0, 9.0, 6.5, 8.0];

// array_map: cada elemento vira outro. O callback pode ser uma funcao
// interna, uma arrow function ou uma funcao nomeada.
echo "Originais:   " . implode(', ', $notas) . "\n";
echo "Dobro:       " . implode(', ', array_map(fn (float $n): float => $n * 2, $notas)) . "\n";
echo "strtoupper:  " . implode(', ', array_map('strtoupper', ['ana', 'bruno'])) . "\n";
echo "Com teto 10: " . implode(', ', array_map(fn (float $n): float => min($n + 1, 10.0), $notas)) . "\n\n";

// array_map com DOIS arrays: o callback recebe um valor de cada um por vez.
$nomes = ['Ana', 'Bruno', 'Carla', 'Davi'];
$idades = [31, 25, 28, 34];
$rotulos = array_map(fn (string $n, int $i): string => "$n tem $i anos", $nomes, $idades);
echo "Rotulos de dois arrays:\n";
foreach ($rotulos as $rotulo) {
    echo "  $rotulo\n";
}
echo "\n";

// array_map em um unico array MANTEM as chaves do original.
$usuario = ['nome' => 'ana souza', 'cidade' => 'recife', 'nivel' => 'admin'];
$maiusculo = array_map('strtoupper', $usuario);
echo "array_map preservou as chaves: " . json_encode($maiusculo, JSON_UNESCAPED_UNICODE) . "\n\n";

// array_filter: o callback devolve bool. O que for true fica.
$aprovados = array_filter($notas, fn (float $n): bool => $n >= 7.0);
echo "Aprovados (>= 7): " . implode(', ', $aprovados) . "\n";
echo "Quantos: " . count($aprovados) . " de " . count($notas) . "\n";

// CUIDADO: array_filter preserva as chaves, entao os indices ficam com buraco.
echo "Indices do resultado: " . implode(', ', array_keys($aprovados)) . "\n";
$reindexado = array_values($aprovados);
echo "Depois do array_values: " . implode(', ', $reindexado) . "\n\n";

// O segundo argumento do callback e a chave (ARRAY_FILTER_USE_BOTH).
$precos = ['arroz' => 22.50, 'feijao' => 8.90, 'carne' => 45.00, 'leite' => 6.50];
$caros = array_filter($precos, fn (float $v, string $k): bool => $v > 20.0, ARRAY_FILTER_USE_BOTH);
echo "Itens acima de 20 reais: " . json_encode($caros, JSON_UNESCAPED_UNICODE) . "\n\n";

// ARRAY_FILTER_USE_KEY passa so a chave ao callback.
$somenteNumerico = array_filter(
    ['10', '20', 'a', 'b', '30'],
    fn (string $k): bool => is_numeric($k),
    ARRAY_FILTER_USE_KEY
);
echo "Chaves numericas: " . implode(', ', $somenteNumerico) . "\n";

// Sem callback, array_filter remove tudo que e falsy: 0, '', null, [], false.
$misturado = [0, 1, '', 'texto', null, [], false, 2.5];
echo "\nSem callback sobra: " . json_encode(array_values(array_filter($misturado))) . "\n";

// Os dois juntos: primeiro filtra, depois transforma.
$turma = [
    ['id' => 1, 'nome' => 'Ana',   'nota' => 8.5],
    ['id' => 2, 'nome' => 'Bruno', 'nota' => 6.0],
    ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5],
    ['id' => 4, 'nome' => 'Davi',  'nota' => 7.0],
];

$aprovadosTurma = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0);
$nomesAprovados  = array_map(fn (array $a): string => $a['nome'], $aprovadosTurma);
echo "\nAprovados: " . implode(', ', array_values($nomesAprovados)) . "\n";

Saída real

Originais:   7.5, 4, 9, 6.5, 8
Dobro:       15, 8, 18, 13, 16
strtoupper:  ANA, BRUNO
Com teto 10: 8.5, 5, 10, 7.5, 9

Rotulos de dois arrays:
  Ana tem 31 anos
  Bruno tem 25 anos
  Carla tem 28 anos
  Davi tem 34 anos

array_map preservou as chaves: {"nome":"ANA SOUZA","cidade":"RECIFE","nivel":"ADMIN"}

Aprovados (>= 7): 7.5, 9, 8
Quantos: 3 de 5
Indices do resultado: 0, 2, 4
Depois do array_values: 7.5, 9, 8

Itens acima de 20 reais: {"arroz":22.5,"carne":45}

Chaves numericas: 10, 20, a, b, 30

Sem callback sobra: [1,"texto",2.5]

Aprovados: Ana, Carla, Davi
Aula 2

array_reduce

array_reduce

array_reduce percorre a lista acumulando um valor até o fim, como um foreach que termina em um echo de uma conta. Ela serve para soma total, contagem, produto, concatenação e qualquer agregação que termine em um único valor. É a função que substitui o foreach de acumulador com $x = $x + $y.

Como se usa

$soma = array_reduce($notas, fn (float $acumulado, float $n): float => $acumulado + $n, 0.0);

O callback recebe dois argumentos: o acumulador (o que já foi somado) e o elemento atual. O terceiro argumento da função, o valor inicial, entra como o acumulador na primeira chamada.

FunçãoAssinaturaExemplo
array_reducearray_reduce(array $array, callable $callback, mixed $initial = null): mixedsoma, produto, contagem
array_sumarray_sum(array $array): int ou floatsoma numérica direta, sem callback
array_productarray_product(array $array): int ou floatmultiplica todos os elementos
maxmax(mixed $valor, mixed ...$valores): mixedmax($array) devolve o maior
minmin(mixed $valor, mixed ...$valores): mixedmin($array) devolve o menor

Quando o cálculo é só soma, produto ou contagem, use array_sum, array_product e count: são mais rápidos e não dão erro de tipo. array_reduce é para o resto — total de desconto, string concatenada, menor preço com uma condição.

O valor inicial importa

O valor inicial é obrigatório na prática: sem ele, o array_reduce começa com o primeiro elemento e o callback recebe $array[1] em diante, o que quebra código que espera começar em 0.0 ou em ''. Além disso, é ele que define o tipo de saída: somar int com um inicial int devolve int; somar float com inicial 0.0 devolve float.

SituaçãoValor inicial
Soma de decimais0.0
Concatenar texto''
Contagem de um filtro0
Multiplicar preços1
Concatenar com separadora string vazia, e o separador é tratado dentro do callback

Se o array estiver vazio, array_reduce devolve o valor inicial sem nunca chamar o callback. É por isso que $soma / count($notas) quebra com lista vazia: o array_reduce devolve 0.0 e o count devolve 0, e a divisão é por zero.

Exemplo

<?php
// ============================================================
// AULA 2 - array_reduce: acumular um resultado item a item
// ============================================================

declare(strict_types=1);

$notas = [7.5, 4.0, 9.0, 6.5, 8.0];

// O callback recebe o ACUMULADO e o ELEMENTO ATUAL, nessa ordem.
// O terceiro argumento de array_reduce e o valor inicial do acumulado.
$soma = array_reduce($notas, fn (float $acumulado, float $n): float => $acumulado + $n, 0.0);
echo "Soma com array_reduce: $soma\n";
echo "Soma com array_sum:    " . array_sum($notas) . "\n\n";

// O valor inicial define o tipo da saida e evita o erro do primeiro item.
$somaInt = array_reduce([1, 2, 3], fn (int $acc, int $n): int => $acc + $n, 0);
echo "Soma de inteiros (inicial 0): $somaInt\n";
echo "Media: " . round($soma / count($notas), 2) . "\n\n";

// array_sum e array_product resolvem os casos simples sem callback.
echo "array_sum:    " . array_sum($notas) . "\n";
echo "array_product: " . array_product([1, 2, 3, 4]) . "\n";
echo "max:           " . max($notas) . "\n";
echo "min:           " . min($notas) . "\n\n";

// Concatenar e uma agregacao valida: o acumulado e uma string.
$linguagens = ['PHP', 'SQL', 'HTML', 'CSS'];
$frase = array_reduce($linguagens, fn (string $acc, string $item): string => $acc === '' ? $item : "$acc, $item", '');
echo "Frase montada: $frase\n\n";

// Contar com condicao: e o agregador mais usado em relatorio.
$turma = [
    ['id' => 1, 'nome' => 'Ana',   'nota' => 8.5, 'cidade' => 'Recife'],
    ['id' => 2, 'nome' => 'Bruno', 'nota' => 6.0, 'cidade' => 'Olinda'],
    ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5, 'cidade' => 'Recife'],
    ['id' => 4, 'nome' => 'Davi',  'nota' => 7.0, 'cidade' => 'Jaboatao'],
    ['id' => 5, 'nome' => 'Elena', 'nota' => 5.5, 'cidade' => 'Recife'],
];

$aprovados = array_reduce($turma, fn (int $acc, array $a): int => $acc + ($a['nota'] >= 7.0 ? 1 : 0), 0);
echo "Quantos aprovados (>= 7): $aprovados de " . count($turma) . "\n";

$maiorNota = array_reduce($turma, fn (float $recorde, array $a): float => max($recorde, $a['nota']), 0.0);
echo "Maior nota da turma: $maiorNota\n";

$somaNotas = array_reduce($turma, fn (float $acc, array $a): float => $acc + $a['nota'], 0.0);
echo "Media da turma: " . round($somaNotas / count($turma), 2) . "\n\n";

// Reduzir para um associative: o acumulado vira chave, o resto vira valor.
$porCidade = array_reduce($turma, function (array $acc, array $a): array {
    $acc[$a['cidade']] = ($acc[$a['cidade']] ?? 0) + 1;
    return $acc;
}, []);
echo "Alunos por cidade: " . json_encode($porCidade, JSON_UNESCAPED_UNICODE) . "\n\n";

// Array vazio: o callback nunca e chamado e o valor inicial volta intacto.
// Por isso media sobre lista vazia quebra na divisao por zero.
$semAlunos = [];
$somaVazia = array_reduce($semAlunos, fn (float $acc, float $n): float => $acc + $n, 0.0);
echo "Soma de array vazio: $somaVazia\n";
echo "count de array vazio: " . count($semAlunos) . " (dividir por isso quebra)\n";
echo "Media protegida: " . round($somaVazia / max(count($notas), 1), 2) . "\n";

Saída real

Soma com array_reduce: 35
Soma com array_sum:    35

Soma de inteiros (inicial 0): 6
Media: 7

array_sum:    35
array_product: 24
max:           9
min:           4

Frase montada: PHP, SQL, HTML, CSS

Quantos aprovados (>= 7): 3 de 5
Maior nota da turma: 9.5
Media da turma: 7.3

Alunos por cidade: {"Recife":3,"Olinda":1,"Jaboatao":1}

Soma de array vazio: 0
count de array vazio: 0 (dividir por isso quebra)
Media protegida: 0
Aula 3

Ordenação: sort, usort e spaceship

Ordenação: sort, usort e spaceship

Existem duas famílias de ordenação. sort e afins reordenam os valores ou as chaves com a comparação padrão do PHP. usort, uasort e uksort reordenam com um callback que você escreve, e é por isso que eles resolvem o caso real: ordenar lista de registros por um campo, sem perder a associação entre os campos.

O operador spaceship

$a <=> $b   // devolve -1, 0 ou 1

É o comparador de três vias, e o usort espera exatamente um retorno assim:

RetornoSignificado
-1$a vem antes de $b (é menor)
0$a e $b são equivalentes
1$a vem depois de $b (é maior)

O callback recebe sempre dois elementos e devolve int. Para ordem crescente, o operador vai direto ($a['nota'] <=> $b['nota']); para decrescente, os operandos invertem ($b['nota'] <=> $a['nota']). Ele também funciona direto em if, onde devolve booleano por coerção: if ($a <=> $b) { /* a > b */ }.

Cada função reordena uma coisa diferente

FunçãoAssinaturaO que é reordenadoAssociações
sortsort(array &$array, int $flags = SORT_REGULAR): boolos valoresperdidas, chaves renumeradas
rsortrsort(array &$array, int $flags = SORT_REGULAR): boolos valores, decrescenteperdidas
usortusort(array &$array, callable $callback): boolos valores, pelo callbackperdidas
asortasort(array &$array, int $flags = SORT_REGULAR): boolos valorespreservadas
uasortuasort(array &$array, callable $callback): boolos valores, pelo callbackpreservadas
uksortuksort(array &$array, callable $callback): boolas chaves, pelo callbackpreservadas
ksortksort(array &$array, int $flags = SORT_REGULAR): boolas chavespreservadas
array_multisortarray_multisort(array &$array, mixed ...$rest): boolvários arrays em paralelopor posição

A diferença entre usort e uasort é a mesma entre sort e asort, com callback no lugar da comparação padrão:

usort($turma,  fn (array $a, array $b): int => $a['nota'] <=> $b['nota']);  // reordena tudo
uasort($turma, fn (array $a, array $b): int => $a['nota'] <=> $b['nota']);  // reordena, mantém chave/valor juntos
uksort($turma, fn (int $a, int $b): int => $b <=> $a);                     // ordena pelo id

Em $turma (lista de linhas), usort e uasort produzem a mesma ordem, porque a chave é o índice automático. A diferença aparece quando a chave é a identificação: uksort($porNome, ...) ordena mantendo cada nome preso à sua nota; usort($porNome, ...) ordenaria as notas e jogaria os nomes fora.

Escolhendo em três passos

  1. O array tem valores soltos (nomes, ids, números)? sort ou rsort.
  2. É um array associativo e a chave identifica o elemento? asort, arsort, ksort ou krsort.
  3. Precisa de uma regra que o PHP não tem (por um campo, por texto, por vários campos)? usort, uasort ou uksort com callback.

Todas as oito recebem o array por referência e devolvem bool. Por isso não funciona sort(minhaFuncao()) nem sort($a + $b): o argumento precisa ser uma variável.

Todas usam ordenação instável (empates podem trocar de lugar entre versões do PHP). Quando o empate importa, acrescente um segundo critério no callback: $a['nota'] <=> $b['nota'] ?: $a['nome'] <=> $b['nome']. O ?: trata a comparação como 0 no empate, e o PHP 8.0 em diante também oferece usort com array de callback para desempate explícito.

Exemplo

<?php
// ============================================================
// AULA 3 - Ordenacao: sort, usort e o operador spaceship
// ============================================================

declare(strict_types=1);

// O operador <=> (spaceship) devolve -1, 0 ou 1. E o que o usort espera:
// menor que -> -1 | igual -> 0 | maior que -> 1
echo "spaceship: 3 <=> 7 = " . (3 <=> 7) . "  (-1: o da esquerda vem antes)\n";
echo "spaceship: 7 <=> 7 = " . (7 <=> 7) . "  ( 0: equivalentes)\n";
echo "spaceship: 9 <=> 7 = " . (9 <=> 7) . "  ( 1: o da esquerda vem depois)\n";
echo "No if, o <=> vira booleano: " . var_export(9 <=> 7, true) . "\n\n";

// sort reordena os VALORES e renumera as chaves.
$notas = [7.5, 4.0, 9.0, 6.5];
sort($notas);
echo "sort (crescente):    " . implode(', ', $notas) . "\n";
rsort($notas);
echo "rsort (decrescente): " . implode(', ', $notas) . "\n\n";

// sort joga as chaves fora. Com chave que identifica o elemento,
// quem serve e asort, que reordena mantendo o par junto.
$porAluno = ['Ana' => 8.5, 'Bruno' => 6.0, 'Carla' => 9.5];
$copia = $porAluno;
sort($copia);
echo "sort em associativo perde a chave: " . json_encode($copia, JSON_UNESCAPED_UNICODE) . "\n";
asort($porAluno);
echo "asort mantem o par:                " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n";
arsort($porAluno);
echo "arsort mantem o par, invertido:     " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n\n";

// ksort ordena pelas CHAVES, nao pelos valores.
ksort($porAluno);
echo "ksort por chave: " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n";
krsort($porAluno);
echo "krsort por chave: " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n\n";

// usort: o COMPARADOR e voce. Ordena o array inteiro, sem preservar chave.
$turma = [
    ['id' => 1, 'nome' => 'Ana',   'nota' => 8.5],
    ['id' => 2, 'nome' => 'Bruno', 'nota' => 6.0],
    ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5],
    ['id' => 4, 'nome' => 'Davi',  'nota' => 7.0],
];

usort($turma, fn (array $a, array $b): int => $a['nota'] <=> $b['nota']);
echo "usort por nota (crescente):\n";
foreach ($turma as $linha) {
    echo "  {$linha['nome']}: {$linha['nota']}\n";
}

// Invertendo os operandos do spaceship, a ordem fica decrescente.
usort($turma, fn (array $a, array $b): int => $b['nota'] <=> $a['nota']);
echo "\nusort por nota (decrescente):\n";
foreach ($turma as $linha) {
    echo "  {$linha['nome']}: {$linha['nota']}\n";
}
echo "Chaves depois do usort: " . implode(', ', array_keys($turma)) . " (renumeradas)\n";

// Com dois criterios: quando as notas empatam, o nome decide.
usort($turma, fn (array $a, array $b): int => $b['nota'] <=> $a['nota'] ?: $a['nome'] <=> $b['nome']);
echo "\nusort com desempate por nome:\n";
foreach ($turma as $linha) {
    echo "  {$linha['nome']}: {$linha['nota']}\n";
}

// uasort e usort, mas mantendo a chave ligada ao valor.
$porNome = ['Ana' => 8.5, 'Bruno' => 6.0, 'Carla' => 9.5];
uasort($porNome, fn (float $a, float $b): int => $b <=> $a);
echo "\nuasort (preserva a associacao): " . json_encode($porNome, JSON_UNESCAPED_UNICODE) . "\n";
usort($porNome, fn (float $a, float $b): int => $a <=> $b);
echo "usort (perde a associacao):     " . json_encode($porNome, JSON_UNESCAPED_UNICODE) . "\n";

// uksort ordena pelas CHAVAS: util para deixar o menu na ordem do banco.
$menu = ['sobre' => 3, 'contato' => 1, 'home' => 2, 'blog' => 4];
uksort($menu, fn (string $a, string $b): int => $a <=> $b);
echo "\nuksort por chave: " . json_encode($menu, JSON_UNESCAPED_UNICODE) . "\n\n";

// Todas devolvem bool, entao nunca use o resultado direto como valor.
// E o array precisa ser uma variavel: sort(minhaFuncao()) nao compila.
$falha = ['c', 'a', 'b'];
$ok = sort($falha);
echo "sort devolveu " . var_export($ok, true) . " e a variavel ficou: " . implode(', ', $falha) . "\n";

Saída real

spaceship: 3 <=> 7 = -1  (-1: o da esquerda vem antes)
spaceship: 7 <=> 7 = 0  ( 0: equivalentes)
spaceship: 9 <=> 7 = 1  ( 1: o da esquerda vem depois)
No if, o <=> vira booleano: 1

sort (crescente):    4, 6.5, 7.5, 9
rsort (decrescente): 9, 7.5, 6.5, 4

sort em associativo perde a chave: [6,8.5,9.5]
asort mantem o par:                {"Bruno":6,"Ana":8.5,"Carla":9.5}
arsort mantem o par, invertido:     {"Carla":9.5,"Ana":8.5,"Bruno":6}

ksort por chave: {"Ana":8.5,"Bruno":6,"Carla":9.5}
krsort por chave: {"Carla":9.5,"Bruno":6,"Ana":8.5}

usort por nota (crescente):
  Bruno: 6
  Davi: 7
  Ana: 8.5
  Carla: 9.5

usort por nota (decrescente):
  Carla: 9.5
  Ana: 8.5
  Davi: 7
  Bruno: 6
Chaves depois do usort: 0, 1, 2, 3 (renumeradas)

usort com desempate por nome:
  Carla: 9.5
  Ana: 8.5
  Davi: 7
  Bruno: 6

uasort (preserva a associacao): {"Carla":9.5,"Ana":8.5,"Bruno":6}
usort (perde a associacao):     [6,8.5,9.5]

uksort por chave: {"blog":4,"contato":1,"home":2,"sobre":3}

sort devolveu true e a variavel ficou: a, b, c
Aula 4

Combinando funções de array

Combinando funções de array

Encadear funções de array é o que substitui o laço aninhado. Cada etapa pega o array anterior e entrega um novo, e o conjunto inteiro lê como uma frase: filtra, transforma, ordena, resume. Quando o foreach tem três níveis e um if dentro, o encadeamento quase sempre fica menor e mais claro.

Como se usa

$busca = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7);
usort($busca, fn (array $a, array $b): int => $b['nota'] <=> $a['nota']);
$nomes = array_column($busca, 'nome');

Cada função devolve um array novo; a original nunca é alterada por array_map, array_filter, array_column e array_slice. Só as que recebem por referência (sort, usort, array_push, array_pop) mexem no array passado.

O pipeline mais usado

EtapaFunçãoO que faz
1. selecionararray_filtermantém o que interessa
2. ordenarusort com <=>põe em ordem
3. extrairarray_columnvira lista simples
4. deduzirarray_sum, count, maxreduz a um valor
$pagas = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0);
$ordem = array_column($pagas, 'nome');
usort($ordem, 'strcmp');   // o callback tambem pode ser strcmp

O callback não precisa ser closure: usort($nomes, 'strcmp'), usort($nomes, 'strcasecmp') e usort($nomes, 'strnatcmp') ordenam texto, com a diferença de diferenciar maiúsculas, ignorar a diferença ou interpretar "item2" depois de "item10".

Agrupar e contar

O agrupamento não tem função pronta. A forma curta é um foreach com array de acumulação; a forma com array_reduce devolve o mesmo resultado e deixa a intenção explícita:

AbordagemTrecho
foreachforeach ($linhas as $l) { $por[$l['cidade']][] = $l['nome']; }
array_reducearray_reduce($linhas, fn ($acc, $l) => [...$acc, $l['cidade'] => [...($acc[$l['cidade']] ?? []), $l['nome']]], [])
contar por grupoarray_count_values(array_column($linhas, 'cidade'))
somar por grupoarray_reduce com $acc[$chave] = ($acc[$chave] ?? 0) + $valor

array_count_values é o atalho para contar ocorrências de um campo — é o GROUP BY do PHP, válido porque a lista já está em memória.

Encadeamento longo vira ilegível. A partir de três ou quatro etapas, ou quando alguma delas tem mais de uma linha de callback, o foreach passo a passo com nomes descritivos ($aprovados, $ordenados, $nomes) é mais fácil de depurar do que uma expressão de dez linhas.

Exemplo

<?php
// ============================================================
// AULA 4 - Combinando funcoes de array: pipeline, agrupamento e deduzir
// ============================================================

declare(strict_types=1);

$turma = [
    ['id' => 1, 'nome' => 'Ana',   'nota' => 8.5, 'cidade' => 'Recife'],
    ['id' => 2, 'nome' => 'bruno', 'nota' => 6.0, 'cidade' => 'Olinda'],
    ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5, 'cidade' => 'Recife'],
    ['id' => 4, 'nome' => 'Davi',  'nota' => 7.0, 'cidade' => 'Jaboatao'],
    ['id' => 5, 'nome' => 'elena', 'nota' => 5.5, 'cidade' => 'Recife'],
];

// --- Pipeline 1: filtrar, ordenar, extrair ---
$aprovados = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0);
usort($aprovados, fn (array $a, array $b): int => $b['nota'] <=> $a['nota']);
$nomesAprovados = array_column($aprovados, 'nome');

echo "Aprovados, do maior para o menor: " . implode(', ', $nomesAprovados) . "\n";
echo "Quantidade: " . count($aprovados) . " de " . count($turma) . "\n\n";

// --- Pipeline 2: a mesma coisa em uma expressao, com array_column e sum ---
$soma = array_sum(array_column(
    array_filter($turma, fn (array $a): bool => $a['cidade'] === 'Recife'),
    'nota'
));
echo "Soma das notas dos alunos do Recife: $soma\n\n";

// --- O original nao e alterado por map, filter, column e slice ---
echo "A turma original continua com " . count($turma) . " linhas e nota inicial " . $turma[0]['nota'] . "\n\n";

// --- Ordenar texto: o callback pode ser uma funcao interna ---
$linguagens = ['SQL', 'php', 'HTML', 'css', 'Java'];
usort($linguagens, 'strcasecmp');
echo "strcasecmp (ignora maiusculas): " . implode(', ', $linguagens) . "\n";

$itens = ['item10', 'item2', 'Item1'];
usort($itens, 'strnatcmp');
echo "strnatcmp (ordem natural):      " . implode(', ', $itens) . "\n\n";

// --- Agrupar com foreach: a forma mais legivel ---
$porCidade = [];
foreach ($turma as $aluno) {
    $porCidade[$aluno['cidade']][] = $aluno['nome'];
}
echo "Nomes por cidade:\n";
foreach ($porCidade as $cidade => $nomes) {
    echo "  $cidade: " . implode(', ', $nomes) . "\n";
}

// --- Agrupar com array_reduce: mesmo resultado, intencao explicita ---
$porCidadeReduce = array_reduce($turma, function (array $acc, array $a): array {
    $acc[$a['cidade']] = ($acc[$a['cidade']] ?? 0) + 1;
    return $acc;
}, []);
echo "\nQuantos por cidade (array_reduce): " . json_encode($porCidadeReduce, JSON_UNESCAPED_UNICODE) . "\n";

// --- array_count_values e o GROUP BY quando a lista ja esta em memoria ---
echo "array_count_values:              " . json_encode(array_count_values(array_column($turma, 'cidade')), JSON_UNESCAPED_UNICODE) . "\n\n";

// --- Somar por grupo: a nota media de cada cidade ---
$somaPorCidade = array_reduce($turma, function (array $acc, array $a): array {
    $acc[$a['cidade']] = ($acc[$a['cidade']] ?? 0) + $a['nota'];
    return $acc;
}, []);
$qtdPorCidade = array_count_values(array_column($turma, 'cidade'));

echo "Media por cidade:\n";
foreach ($somaPorCidade as $cidade => $soma) {
    printf("  %-9s %5.2f\n", $cidade, $soma / $qtdPorCidade[$cidade]);
}

// --- Deduzir varios numeros de uma vez ---
echo "\nResumo da turma:\n";
echo "  maior nota: " . max(array_column($turma, 'nota')) . "\n";
echo "  menor nota: " . min(array_column($turma, 'nota')) . "\n";
echo "  media:      " . round(array_sum(array_column($turma, 'nota')) / count($turma), 2) . "\n";
echo "  aprovados:  " . count(array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0)) . "\n";

Saída real

Aprovados, do maior para o menor: Carla, Ana, Davi
Quantidade: 3 de 5

Soma das notas dos alunos do Recife: 23.5

A turma original continua com 5 linhas e nota inicial 8.5

strcasecmp (ignora maiusculas): css, HTML, Java, php, SQL
strnatcmp (ordem natural):      Item1, item2, item10

Nomes por cidade:
  Recife: Ana, Carla, elena
  Olinda: bruno
  Jaboatao: Davi

Quantos por cidade (array_reduce): {"Recife":3,"Olinda":1,"Jaboatao":1}
array_count_values:              {"Recife":3,"Olinda":1,"Jaboatao":1}

Media por cidade:
  Recife     7.83
  Olinda     6.00
  Jaboatao   7.00

Resumo da turma:
  maior nota: 9.5
  menor nota: 5.5
  media:      7.3
  aprovados:  3