Arrays em Profundidade

Arrays em Profundidade

O array do PHP é uma estrutura só que faz o papel de lista, mapa e registro — e a biblioteca em volta dele tem mais de oitenta funções com convenções que nem sempre combinam entre si. Quais preservam as chaves, quais reindexam, quais ordenam no lugar, e onde cada inconsistência costuma cobrar.
PHP

18 min de leitura

Arrays são a estrutura de dados mais usada no PHP. Praticamente tudo que você fará com o PHP — ler dados de um banco, processar um formulário, construir uma resposta de API — vai envolver arrays de alguma forma. Entender arrays com profundidade não é opcional: é o que separa quem apenas usa PHP de quem realmente o domina.

Neste artigo vamos cobrir criação, acesso, modificação, arrays associativos, multidimensionais e o conjunto de funções nativas que tornam os arrays do PHP tão poderosos.

Criando arrays

O PHP suporta duas sintaxes para criar arrays. A sintaxe com colchetes [] é a moderna e preferida:

<?php
declare(strict_types=1);

// Sintaxe moderna — preferida desde PHP 5.4
$frutas = ["maçã", "banana", "laranja"];

// Sintaxe antiga — ainda funciona, mas evite em código novo
$frutas = array("maçã", "banana", "laranja");

// Array vazio — comum para inicializar antes de preencher
$resultado = [];

// Array com índices explícitos
// Os índices não precisam ser sequenciais
$sparse = [0 => "zero", 5 => "cinco", 10 => "dez"];

// PHP atribui o próximo índice inteiro após o maior existente
$sparse[] = "onze"; // receberá índice 11

// Verificando se é array e seu tamanho
var_dump(is_array($frutas)); // bool(true)
echo count($frutas);         // 3
echo count([]);              // 0

Arrays associativos

Arrays associativos usam strings como chaves em vez de índices numéricos. São equivalentes a dicionários ou mapas em outras linguagens:

<?php
declare(strict_types=1);

// Criando com chaves string
$usuario = [
    "nome"    => "Ana Silva",
    "email"   => "ana@email.com",
    "idade"   => 28,
    "ativo"   => true,
];

// Acessando por chave
echo $usuario["nome"];   // Ana Silva
echo $usuario["idade"];  // 28

// Modificando um valor existente
$usuario["idade"] = 29;

// Adicionando uma nova chave
$usuario["perfil"] = "administrador";

// Removendo uma chave
unset($usuario["ativo"]);

// Verificando se uma chave existe
var_dump(isset($usuario["email"]));          // bool(true)
var_dump(isset($usuario["ativo"]));          // bool(false) — removido
var_dump(array_key_exists("email", $usuario)); // bool(true) — mais preciso que isset

// Listando chaves e valores separadamente
$chaves  = array_keys($usuario);   // ["nome", "email", "idade", "perfil"]
$valores = array_values($usuario); // ["Ana Silva", "ana@email.com", 29, "administrador"]

A diferença entre isset() e array_key_exists() é sutil mas importante: isset() retorna false se a chave existir mas seu valor for null. array_key_exists() retorna true independentemente do valor — verifique apenas se a chave existe.

Arrays multidimensionais

Arrays podem conter outros arrays como valores, criando estruturas multidimensionais. Esse é o padrão usado para representar dados relacionais — exatamente o que vem de um banco de dados:

<?php
declare(strict_types=1);

// Array de arrays associativos — estrutura típica de resultado de banco
$usuarios = [
    [
        "id"    => 1,
        "nome"  => "Ana",
        "email" => "ana@email.com",
        "cargo" => ["titulo" => "Desenvolvedora", "nivel" => "Senior"],
    ],
    [
        "id"    => 2,
        "nome"  => "Bruno",
        "email" => "bruno@email.com",
        "cargo" => ["titulo" => "Designer", "nivel" => "Pleno"],
    ],
];

// Acessando dados aninhados com múltiplos colchetes
echo $usuarios[0]["nome"];              // Ana
echo $usuarios[0]["cargo"]["titulo"];   // Desenvolvedora
echo $usuarios[1]["cargo"]["nivel"];    // Pleno

// Iterando estrutura multidimensional
foreach ($usuarios as $usuario) {
    // $usuario é um array associativo
    echo "{$usuario['nome']} — {$usuario['cargo']['titulo']}\n";
}
// Ana — Desenvolvedora
// Bruno — Designer

// array_column — extrai uma coluna de um array multidimensional
// Equivale a: SELECT nome FROM usuarios
$nomes = array_column($usuarios, "nome");
print_r($nomes); // ["Ana", "Bruno"]

// Também pode usar uma coluna como índice do resultado
$porId = array_column($usuarios, null, "id");
// Agora $porId[1] é o array do usuário com id=1
echo $porId[1]["nome"]; // Ana

Adicionando e removendo elementos

<?php
declare(strict_types=1);

$pilha = ["a", "b", "c"];

// Adicionando ao final
$pilha[] = "d";          // forma mais comum
array_push($pilha, "e"); // equivalente, mas [] é preferido

// Removendo do final — retorna o elemento removido
$ultimo = array_pop($pilha); // "e"

// Adicionando ao início
array_unshift($pilha, "z"); // $pilha = ["z", "a", "b", "c", "d"]

// Removendo do início — retorna o elemento removido
$primeiro = array_shift($pilha); // "z"

// Removendo por índice específico
// unset remove o elemento mas não reindexia
unset($pilha[1]); // remove "b", índices ficam: 0=>"a", 2=>"c", 3=>"d"

// array_splice — remove e opcionalmente insere, reindexia automaticamente
// array_splice($array, $inicio, $quantidade, $substituicao)
$cores = ["vermelho", "verde", "azul", "amarelo"];
$removidas = array_splice($cores, 1, 2); // remove 2 a partir do índice 1
// $cores = ["vermelho", "amarelo"]
// $removidas = ["verde", "azul"]

// Inserindo no meio com array_splice
array_splice($cores, 1, 0, ["roxo", "laranja"]); // insere sem remover
// $cores = ["vermelho", "roxo", "laranja", "amarelo"]

Funções essenciais de array

O PHP tem mais de 70 funções nativas para arrays. Aqui estão as mais importantes:

Busca e verificação

<?php
declare(strict_types=1);

$numeros = [10, 20, 30, 40, 50];
$frutas  = ["maçã", "banana", "laranja"];

// in_array — verifica se um valor existe
var_dump(in_array(30, $numeros));         // bool(true)
var_dump(in_array("banana", $frutas));    // bool(true)
var_dump(in_array("uva", $frutas));       // bool(false)

// Terceiro argumento true = comparação estrita (===)
var_dump(in_array("30", $numeros));        // bool(true) — coerção!
var_dump(in_array("30", $numeros, true));  // bool(false) — estrito

// array_search — retorna a chave do elemento encontrado, ou false
$posicao = array_search(30, $numeros);
var_dump($posicao); // int(2)

$ausente = array_search(99, $numeros);
var_dump($ausente); // bool(false)

// Cuidado: posição 0 e false são ambos falsy!
// Use === false para verificar ausência
if (array_search(10, $numeros) !== false) {
    echo "Encontrado!\n";
}

Ordenação

<?php
declare(strict_types=1);

$numeros = [3, 1, 4, 1, 5, 9, 2, 6];
$nomes   = ["Carlos", "Ana", "Beatriz", "David"];

// sort — ordena por valor, reindexia (destrói chaves originais)
sort($numeros);    // [1, 1, 2, 3, 4, 5, 6, 9]
sort($nomes);      // ["Ana", "Beatriz", "Carlos", "David"]

// rsort — ordem decrescente
rsort($numeros);   // [9, 6, 5, 4, 3, 2, 1, 1]

// asort — ordena por valor, PRESERVA as chaves
$notas = ["Ana" => 9.5, "Bruno" => 7.0, "Carla" => 8.5];
asort($notas);
// ["Bruno" => 7.0, "Carla" => 8.5, "Ana" => 9.5]

// arsort — ordem decrescente, preserva chaves
arsort($notas);
// ["Ana" => 9.5, "Carla" => 8.5, "Bruno" => 7.0]

// ksort — ordena por chave
$config = ["z_timeout" => 30, "a_host" => "localhost", "m_port" => 3306];
ksort($config);
// ["a_host" => ..., "m_port" => ..., "z_timeout" => ...]

// usort — ordenação customizada com função de comparação
$usuarios = [
    ["nome" => "Carlos", "idade" => 35],
    ["nome" => "Ana",    "idade" => 28],
    ["nome" => "Bruno",  "idade" => 42],
];

// Ordena por idade usando spaceship operator
usort($usuarios, fn(array $a, array $b): int => $a["idade"] <=> $b["idade"]);
// Ana(28), Carlos(35), Bruno(42)

Transformação

<?php
declare(strict_types=1);

$numeros = [1, 2, 3, 4, 5];
$precos  = [10.0, 25.5, 8.0, 42.0, 15.0];

// array_map — aplica função a cada elemento, retorna novo array
$dobrados = array_map(fn(int $n): int => $n * 2, $numeros);
// [2, 4, 6, 8, 10]

// array_map com múltiplos arrays
$a = [1, 2, 3];
$b = [10, 20, 30];
$soma = array_map(fn(int $x, int $y): int => $x + $y, $a, $b);
// [11, 22, 33]

// array_filter — mantém apenas os elementos que passam no teste
$pares = array_filter($numeros, fn(int $n): bool => $n % 2 === 0);
// [2, 4] — note: chaves originais são preservadas (1 e 3)

// array_values reindexia após filter
$pares = array_values(array_filter($numeros, fn(int $n): bool => $n % 2 === 0));
// [2, 4] com índices 0 e 1

// array_reduce — reduz o array a um único valor
$total = array_reduce($precos, fn(float $carry, float $item): float => $carry + $item, 0.0);
echo $total; // 100.5

// Calculando média com array_reduce
$media = array_reduce($precos, fn(float $carry, float $item): float => $carry + $item, 0.0) / count($precos);
echo $media; // 20.1

// array_unique — remove valores duplicados
$comDuplicatas = [1, 2, 2, 3, 3, 3, 4];
$unicos = array_unique($comDuplicatas);
// [1, 2, 3, 4]

Fatiamento e combinação

<?php
declare(strict_types=1);

$letras = ["a", "b", "c", "d", "e", "f"];

// array_slice — extrai uma porção do array
$parte = array_slice($letras, 1, 3);       // ["b", "c", "d"]
$final = array_slice($letras, -2);          // ["e", "f"] — índice negativo
$reindexa = array_slice($letras, 2, 3, true); // preserva chaves originais

// array_merge — une dois ou mais arrays
$a = ["maçã", "banana"];
$b = ["laranja", "uva"];
$c = array_merge($a, $b); // ["maçã", "banana", "laranja", "uva"]

// Com arrays associativos — chaves repetidas: o segundo sobrescreve
$padroes = ["tema" => "claro", "idioma" => "pt-BR", "itens" => 10];
$config   = ["tema" => "escuro", "debug" => true];
$final    = array_merge($padroes, $config);
// ["tema" => "escuro", "idioma" => "pt-BR", "itens" => 10, "debug" => true]

// Operador + — alternativo ao merge: a chave do primeiro vence
$final2 = $padroes + $config;
// ["tema" => "claro", "idioma" => "pt-BR", "itens" => 10, "debug" => true]

// array_combine — cria associativo a partir de dois arrays (chaves + valores)
$chaves  = ["nome", "email", "cidade"];
$valores = ["Ana", "ana@email.com", "São Paulo"];
$pessoa  = array_combine($chaves, $valores);
// ["nome" => "Ana", "email" => "ana@email.com", "cidade" => "São Paulo"]

// array_chunk — divide em pedaços
$numeros = [1, 2, 3, 4, 5, 6, 7];
$grupos  = array_chunk($numeros, 3);
// [[1,2,3], [4,5,6], [7]]

Funções de contagem e matemática

<?php
declare(strict_types=1);

$valores = [5, 3, 8, 1, 9, 2, 7, 4, 6];
$precos  = [10.5, 25.0, 8.75, 42.0];

echo count($valores);       // 9 — número de elementos
echo array_sum($precos);    // 86.25 — soma de todos os elementos
echo array_product([1,2,3,4]); // 24 — produto de todos os elementos
echo min($valores);          // 1 — menor valor
echo max($valores);          // 9 — maior valor

// Funções que funcionam com arrays
$arredondados = array_map(fn(float $p): float => round($p, 1), $precos);

// compact e extract — atalhos para criar arrays de variáveis
$nome   = "Ana";
$idade  = 28;
$cidade = "São Paulo";

// compact cria array associativo a partir de variáveis
$dados = compact("nome", "idade", "cidade");
// ["nome" => "Ana", "idade" => 28, "cidade" => "São Paulo"]

// extract faz o inverso — cria variáveis a partir de array
// Use com cuidado: pode sobrescrever variáveis existentes
extract($dados); // cria $nome, $idade, $cidade no escopo atual

Padrões práticos com arrays

<?php
declare(strict_types=1);

// Agrupando dados por critério
$transacoes = [
    ["tipo" => "entrada", "valor" => 500.0],
    ["tipo" => "saida",   "valor" => 120.0],
    ["tipo" => "entrada", "valor" => 300.0],
    ["tipo" => "saida",   "valor" => 80.0],
];

$grupos = [];
foreach ($transacoes as $t) {
    // Agrupa pelo campo "tipo"
    $grupos[$t["tipo"]][] = $t["valor"];
}

// $grupos = [
//   "entrada" => [500.0, 300.0],
//   "saida"   => [120.0, 80.0]
// ]

$totalEntradas = array_sum($grupos["entrada"]); // 800.0
$totalSaidas   = array_sum($grupos["saida"]);   // 200.0

// Pipeline funcional — filtrar, transformar, reduzir
$produtos = [
    ["nome" => "Teclado",  "preco" => 250.0, "ativo" => true],
    ["nome" => "Mouse",    "preco" => 80.0,  "ativo" => false],
    ["nome" => "Monitor",  "preco" => 1200.0,"ativo" => true],
    ["nome" => "Headset",  "preco" => 350.0, "ativo" => true],
];

// Soma o preço dos produtos ativos com preço abaixo de R$500
$totalAtivosBaratos = array_reduce(
    array_filter(
        $produtos,
        fn(array $p): bool => $p["ativo"] && $p["preco"] < 500.0
    ),
    fn(float $carry, array $p): float => $carry + $p["preco"],
    0.0
);

echo $totalAtivosBaratos; // 600.0 (Teclado 250 + Headset 350)

Boa parte do trabalho diário em PHP é transformar array: o que vem do banco vira o que a tela precisa, o que chega do formulário vira o que a regra de negócio entende. Quem conhece bem esse punhado de funções escreve menos laço e erra menos. O cuidado que sobra é sempre o mesmo — saber se a chave foi preservada ou descartada —, porque é daí que vêm os defeitos que não dão erro: a lista que virou objeto no JSON, o vendedor que sumiu da ordenação, o índice que deixou de ser sequencial.

Fontes e leituras recomendadas

Exercícios

Exercício 1

O front-end espera uma lista JSON e recebe um objeto, quebrando o .map() dele. O PHP não deu erro nenhum. O que aconteceu?

<?php
$notas = [8, 0, 7, 9];
$aprovadas = array_filter($notas, fn($n) => $n >= 7);

echo json_encode($aprovadas);   // {"0":8,"2":7,"3":9}
Ver resposta

✓ Resposta: O array_filter preserva as chaves originais. Ao remover o elemento de índice 1, sobram as chaves 0, 2 e 3 — e um array PHP com chaves numéricas não sequenciais deixa de ser uma lista. O json_encode então não tem escolha: precisa preservar os índices, e a única forma de fazer isso em JSON é gerando um objeto. Do lado do JavaScript, {"0":8,"2":7,"3":9} não tem .map(), e o erro aparece lá, a três camadas de distância de onde foi causado. A correção é reindexar: array_values(array_filter(...)), que descarta as chaves e devolve uma lista de 0 a n. Vale a pena saber quais funções têm esse comportamento, porque a inconsistência é histórica e não segue regra: array_filter e array_unique preservam as chaves; array_map com um array preserva, mas com dois ou mais arrays reindexa; array_merge reindexa as chaves numéricas e mantém as de texto. Em PHP 8.1 existe array_is_list($a), que responde direto se o array ainda é uma lista — é a verificação que vale colocar num teste, ou antes de serializar uma resposta de API. E no json_encode há a saída de emergência JSON_FORCE_OBJECT para o caso inverso, quando você quer objeto sempre.

Exercício 2

Esta verificação de permissão liberava acesso indevido em PHP 7 e parou de liberar em PHP 8. O que mudou — e por que o código continua errado mesmo agora?

<?php
$permissoes = ['ler', 'escrever', 'publicar'];
$nivel = 0;

if (in_array($nivel, $permissoes)) {
    echo "acesso liberado";
}
Ver resposta

✓ Resposta: O in_array compara com == por padrão. Até o PHP 7, comparar o número 0 com a string 'ler' convertia a string para número, 'ler' virava 0, e a comparação dava verdadeira — ou seja, o nível 0 "tinha" a permissão de ler, e também a de escrever, e a de publicar. Um erro de autorização causado por uma regra de coerção. No PHP 8, a comparação entre número e string não numérica passou a converter o número para string, e '0' == 'ler' é falso: o acesso deixou de ser liberado. Mas o código continua errado por dois motivos. Primeiro, ele ainda depende da comparação frouxa, e vai voltar a surpreender no dia em que $nivel chegar como '0' ou a lista contiver algo numérico — a correção é o terceiro parâmetro: in_array($nivel, $permissoes, true), que compara com === e deveria ser o padrão em qualquer verificação de segurança. Segundo, e mais grave, ele compara níveis com nomes de permissão: são conceitos diferentes, e nenhum valor de $nivel numérico faz sentido nessa lista. Verificação de autorização é justamente onde comparação frouxa não tem lugar — e onde vale considerar enum em vez de string solta, para que o próprio tipo impeça a confusão.

Exercício 3

Dois arrays precisam ser combinados. Qual a diferença entre as duas linhas, e quando cada uma é a correta?

<?php
$a = ['config' => 'padrão', 10, 20];
$b = ['config' => 'do usuário', 30, 40, 50];

$x = array_merge($a, $b);
$y = $a + $b;
Ver resposta

✓ Resposta: As duas tratam chave de texto e chave numérica de formas opostas. O array_merge faz a chave de texto do segundo vencer ('config' fica 'do usuário') e reindexa as numéricas, concatenando: sobram 10, 20, 30, 40, 50. Já o operador + é união de chaves, e nele o primeiro array vence em tudo o que já existe: 'config' continua 'padrão', os índices 0 e 1 continuam 10 e 20, e do segundo array só entra o índice 2, que não existia no primeiro — resultado [10, 20, 50], que quase nunca é o que alguém queria. Daí a regra prática: array_merge para juntar listas, + para aplicar valores padrão$opcoes = $informado + $padrao; lê-se "use o que foi informado e complete com o padrão", e é um idioma comum e legítimo. Duas notas para fechar. Para juntar arrays associativos aninhados, nenhuma das duas serve: array_merge é raso e sobrescreve o sub-array inteiro; o que se quer nesse caso é array_replace_recursive, ou uma função própria, porque array_merge_recursive tem o comportamento surpreendente de transformar valores duplicados em array. E em PHP 7.4 ou superior, o operador ... também junta arrays ([...$a, ...$b]), com as mesmas regras do array_merge — só que aceitando chaves de texto apenas a partir do PHP 8.1.

Exercício 4

Uma listagem precisa ser ordenada pelo nome do cliente. Diga o que cada função faz e qual delas serve para um array de arrays vindo do banco: sort, asort, ksort, usort.

Ver resposta

✓ Resposta: sort ordena pelos valores e descarta as chaves, reindexando de 0 a n — serve para lista simples, e é destrutivo se as chaves importavam. asort ordena pelos valores mantendo a associação chave-valor, o que é o certo para um array associativo do tipo ['ana' => 30, 'bruno' => 25]. ksort ordena pelas chaves. E usort ordena por um critério que você escreve, sendo a única das quatro que serve ao caso do enunciado: com registros vindos do banco, cada elemento é um array, e não existe ordem natural entre arrays — é preciso dizer qual campo compara. A forma idiomática usa o operador spaceship, que já devolve o -1, 0 ou 1 que o usort espera: usort($clientes, fn($a, $b) => $a['nome'] <=> $b['nome']);. Para ordenar por dois critérios, compare arrays: [$a['cidade'], $a['nome']] <=> [$b['cidade'], $b['nome']] — o spaceship compara elemento a elemento e só passa ao segundo em caso de empate. Duas armadilhas comuns: todas essas funções recebem o array por referência e alteram no lugar, devolvendo apenas true$ordenado = sort($a) guarda true, não o array; e comparar nomes com acento por <=> usa a ordem dos bytes, colocando "Ávila" depois de "Zanetti". Quando a ordem alfabética precisa estar correta para o usuário, o caminho é Collator, da extensão intl, ou deixar a ordenação a cargo do banco, com a collation apropriada.

Exercício 5

Desafio: você recebe uma lista plana de vendas e precisa do total por vendedor, ordenado do maior para o menor. Escreva a transformação e explique cada escolha.

<?php
$vendas = [
    ['vendedor' => 'ana',   'valor' => 1200],
    ['vendedor' => 'bruno', 'valor' =>  800],
    ['vendedor' => 'ana',   'valor' =>  300],
    ['vendedor' => 'carla', 'valor' => 1500],
];
Ver resposta

✓ Resposta: O agrupamento é um foreach que acumula em um array indexado pela chave de grupo — e a clareza aqui vale mais que a esperteza:

<?php

$totais = [];
foreach ($vendas as $v) {
    // ??= cria a chave com 0 na primeira vez que o vendedor aparece,
    // e não faz nada nas seguintes — evita o isset() antes de somar
    $totais[$v['vendedor']] ??= 0;
    $totais[$v['vendedor']] += $v['valor'];
}

// arsort: ordena pelo VALOR, decrescente, PRESERVANDO a chave.
// sort() aqui jogaria fora o nome do vendedor, que é justamente o resultado.
arsort($totais);

print_r($totais);
// ['carla' => 1500, 'ana' => 1500, 'bruno' => 800]

Três escolhas merecem defesa. A primeira é ??= em vez de isset(): sem ele, somar numa chave inexistente emite Warning: Undefined array key — que não quebra nada e por isso passa despercebido, até alguém ligar error_reporting em produção e encontrar milhares dele no log. A segunda é arsort e não sort: o nome do vendedor é a chave, e sort descartaria exatamente a informação que dá sentido ao número. A terceira é o foreach em vez de array_reduce: a versão com array_reduce existe e é uma linha, mas exige recriar o acumulador a cada passo, o que a torna mais lenta e menos legível — agrupamento é um dos poucos casos em que o laço explícito ganha da função de ordem superior.

Repare no empate entre Carla e Ana, ambas com 1500: arsort não garante qual vem primeiro em caso de empate (a ordenação não é estável por natureza, embora o PHP 8 tenha passado a garantir estabilidade nas funções de ordenação). Se o desempate importa — ordem alfabética, por exemplo —, ele precisa ser explícito: uasort($totais, fn($a, $b) => $b <=> $a); com um critério secundário, ou ordenar por array de pares.

Comentários

Mais em PHP

Variáveis, Tipos de Dados e Operadores
Variáveis, Tipos de Dados e Operadores

Toda variável em PHP guarda um valor de algum tipo, e a linguagem converte…

Design Patterns: Singleton, Factory e Builder
Design Patterns: Singleton, Factory e Builder

Três padrões de criação e o que cada um custa: o Singleton, que garante…

Exceções Avançadas
Exceções Avançadas

Uma hierarquia de exceções bem desenhada diz ao chamador o que ele pode tratar…