PHP Arrays Multidimensionais
Um array pode guardar qualquer valor, e isso inclui outro array. Quando os itens de um array são arrays, ele vira uma espécie de tabela, com linhas e colunas. É assim que o PHP representa a lista de alunos de uma turma, os produtos de uma loja ou as linhas que vêm de um banco de dados. O nome assusta (multidimensional = "de várias dimensões"), mas a ideia é simples: uma gaveta que tem um armário dentro.
- O que é um array de arrays e como criá-lo.
- Como ler e mudar um valor com dois colchetes:
$alunos[0]["nome"]. - Como acrescentar uma linha inteira à tabela.
- Como percorrer com um laço, e com laços aninhados (um dentro do outro).
- Como transformar o array numa tabela HTML de verdade.
- Os erros mais comuns ao errar o nível do array.
Antes, é bom ter feito Arrays Indexados, Arrays Associativos e foreach: esta aula junta os três.
O mais simples: uma grade de jogo da velha
Um tabuleiro de jogo da velha tem 3 linhas, e cada linha tem 3 casas. Em PHP: um array com 3 itens, e cada item é um array com 3 casas.
<?php
$velha = [
["X", "O", "X"], // linha 0
[" ", "X", "O"], // linha 1
["O", " ", "X"], // linha 2
];
echo "Centro: ", $velha[1][1], "\n";
echo "Canto de baixo, à direita: ", $velha[2][2], "\n";
$velha[1][0] = "O"; // a jogada do O na casa vazia da linha 1
- $velha
- O array de fora: tem 3 itens (índices 0, 1 e 2). Cada item é uma linha.
- ["X", "O", "X"]
- Um array de dentro: uma linha, com 3 casas (índices 0, 1 e 2).
- $velha[1]
- A linha 1 inteira: o array
[" ", "X", "O"]. - $velha[1][1]
- Da linha 1, a casa 1:
"X". Leia da esquerda para a direita: primeiro escolhe a linha, depois a casa dentro dela. - $velha[1][0] = "O";
- Muda uma casa só, com a mesma escrita.
Pense nos dois colchetes como um endereço: "rua 1, casa 1". O primeiro número escolhe a rua (a linha); o segundo, a casa naquela rua (a coluna).
| coluna 0 | coluna 1 | coluna 2 | |
|---|---|---|---|
| linha 0 | [0][0] X | [0][1] O | [0][2] X |
| linha 1 | [1][0] (vazia → O) | [1][1] X | [1][2] O |
| linha 2 | [2][0] O | [2][1] (vazia) | [2][2] X |
Mostrar a grade: um laço dentro do outro
Para passar por todas as casas, você precisa de dois laços: o de fora anda pelas linhas; o de dentro, pelas casas de cada linha. Isso se chama laços aninhados (um dentro do outro, como ninhos).
for ($linha = 0; $linha < 3; $linha++) {
for ($coluna = 0; $coluna < 3; $coluna++) {
echo " ", $velha[$linha][$coluna], " ";
if ($coluna < 2) {
echo "|";
}
}
echo "\n";
if ($linha < 2) {
echo "---+---+---\n";
}
}
$ php velha.php Centro: X Canto de baixo, à direita: X X | O | X ---+---+--- O | X | O « a casa [1][0] agora tem O ---+---+--- O | | X
Siga o caminho: com $linha = 0, o laço de dentro roda inteiro (colunas 0, 1 e 2) e só então a linha passa para 1, e o laço de dentro roda inteiro de novo. São 3 × 3 = 9 voltas no total. Os laços for estão explicados na aula for.
A tabela de alunos da Ana
O caso mais comum no dia a dia é uma lista de fichas: um array indexado (a lista) em que cada item é um array associativo (a ficha). A Ana dá aulas de informática e guarda a turma assim:
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "cidade" => "Caruaru", "nota" => 8.5],
["nome" => "Diego", "idade" => 41, "cidade" => "Petrolina", "nota" => 6.0],
];
echo $alunos[0]["nome"], "\n";
echo $alunos[2]["cidade"], "\n";
echo "Alunos: ", count($alunos), "\n";
echo "Campos de cada aluno: ", count($alunos[0]), "\n";
print_r($alunos[1]);
Veja como uma tabela no papel:
| Índice | "nome" | "idade" | "cidade" | "nota" |
|---|---|---|---|---|
| 0 | Ana | 29 | Recife | 9.5 |
| 1 | Bruno | 34 | Olinda | 7.0 |
| 2 | Carla | 22 | Caruaru | 8.5 |
| 3 | Diego | 41 | Petrolina | 6.0 |
Cada linha é um aluno (escolhida pelo número); cada coluna é um campo (escolhida pelo nome).
- $alunos[0]
- A ficha inteira da Ana:
["nome" => "Ana", "idade" => 29, …]. - $alunos[0]["nome"]
- Da ficha 0, o campo
"nome":"Ana". - count($alunos)
- Conta só o nível de fora: quantas fichas (4).
- count($alunos[0])
- Conta os campos de uma ficha (4).
$ php alunos.php
Ana
Caruaru
Alunos: 4
Campos de cada aluno: 4
Array
(
[nome] => Bruno
[idade] => 34
[cidade] => Olinda
[nota] => 7 « 7.0 aparece como 7 no print_r
)
Mudar um campo, acrescentar um campo, acrescentar um aluno
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
];
$alunos[1]["nota"] = 7.5; // muda um campo de uma ficha
$alunos[0]["email"] = "ana@exemplo.com"; // acrescenta um campo só na ficha 0
$alunos[] = ["nome" => "Elisa", "idade" => 19, "cidade" => "Garanhuns", "nota" => 10.0];
print_r($alunos);
$ php alunos-mudar.php
Array
(
[0] => Array
(
[nome] => Ana
[idade] => 29
[cidade] => Recife
[nota] => 9.5
[email] => ana@exemplo.com
)
[1] => Array
(
[nome] => Bruno
[idade] => 34
[cidade] => Olinda
[nota] => 7.5
)
[2] => Array
(
[nome] => Elisa
[idade] => 19
[cidade] => Garanhuns
[nota] => 10
)
)
No print_r, cada nível de array ganha mais recuo, e o array de dentro aparece como Array ( … ) no lugar do valor. A última linha do código usa o [] vazio que você conhece: acrescenta no fim do array de fora, e o que entra é uma ficha inteira.
Repare também que a Ana agora tem um campo "email" e os outros não. O PHP permite: cada ficha é um array independente. Mas é fonte de avisos (veja Quando dá errado); numa tabela, prefira que todas as fichas tenham os mesmos campos.
Percorrer a tabela
Um foreach: uma ficha por volta
Na maior parte do tempo, um laço basta: a cada volta, a variável recebe uma ficha inteira, e você lê os campos dela pelo nome.
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "cidade" => "Caruaru", "nota" => 8.5],
["nome" => "Diego", "idade" => 41, "cidade" => "Petrolina", "nota" => 6.0],
];
foreach ($alunos as $aluno) {
echo $aluno["nome"], " (", $aluno["cidade"], "): ", $aluno["nota"], "\n";
}
$soma = 0;
foreach ($alunos as $aluno) {
$soma += $aluno["nota"];
}
echo "Média da turma: ", $soma / count($alunos), "\n";
$ php alunos-foreach.php Ana (Recife): 9.5 Bruno (Olinda): 7 Carla (Caruaru): 8.5 Diego (Petrolina): 6 Média da turma: 7.75
O nome no singular ($aluno) para o item e no plural ($alunos) para a lista é uma convenção que ajuda muito: dentro do laço, $aluno["nome"] se lê quase como português. Para pegar uma coluna inteira de uma vez (todas as notas, todos os nomes), existe a função array_column, na aula Funções de Array.
Acrescente um aluno, mude a nota de outro e veja o resultado. Tente também mostrar a idade de cada um, ou só os alunos de uma cidade (com um if dentro do foreach).
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "cidade" => "Caruaru", "nota" => 8.5],
];
$alunos[] = ["nome" => "Diego", "idade" => 41, "cidade" => "Petrolina", "nota" => 6.0];
$alunos[1]["nota"] = 7.5;
foreach ($alunos as $aluno) {
echo $aluno["nome"], " mora em ", $aluno["cidade"], " e tirou ", $aluno["nota"], "\n";
}
Dois foreach: passar por todos os campos
Quando você não sabe (ou não quer escrever) o nome de cada campo, coloque um segundo foreach dentro do primeiro. O de fora pega cada ficha; o de dentro percorre os pares chave => valor daquela ficha.
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
];
foreach ($alunos as $numero => $aluno) {
echo "Aluno nº $numero\n";
foreach ($aluno as $campo => $valor) {
echo " $campo: $valor\n";
}
}
$ php alunos-aninhado.php Aluno nº 0 nome: Ana idade: 29 cidade: Recife nota: 9.5 Aluno nº 1 nome: Bruno idade: 34 cidade: Olinda nota: 7
| Laço | Percorre | A variável recebe |
|---|---|---|
de fora: foreach ($alunos as $numero => $aluno) | a lista | $numero: 0, 1…; $aluno: uma ficha (array) |
de dentro: foreach ($aluno as $campo => $valor) | uma ficha | $campo: "nome", "idade"…; $valor: o dado |
Se o laço de dentro reaproveitar a variável do de fora (por exemplo, foreach ($aluno as $aluno)), ele apaga a ficha que estava sendo usada. Dê a cada laço os seus próprios nomes.
Mais níveis e outras combinações
Não existe limite de níveis, e qualquer combinação vale. Duas muito comuns:
Um campo que é uma lista
<?php
$aluna = [
"nome" => "Ana",
"notas" => [9.0, 8.5, 10.0],
];
echo "Segunda nota: ", $aluna["notas"][1], "\n";
echo "Quantidade de notas: ", count($aluna["notas"]), "\n";
$aluna["notas"][] = 7.5;
foreach ($aluna["notas"] as $n) {
echo $n, " ";
}
echo "\n";
$ php notas3.php Segunda nota: 8.5 Quantidade de notas: 3 9 8.5 10 7.5
$aluna["notas"] é um array comum: dá para usar count, [] e foreach nele. Uma turma inteira com várias notas por aluno teria três níveis: $alunos[0]["notas"][1] (aluno 0, campo notas, nota 1).
Grupos com nome
<?php
$turmas = [
"manhã" => ["Ana", "Bruno"],
"noite" => ["Carla", "Diego", "Elisa"],
];
foreach ($turmas as $turno => $nomes) {
echo "Turma da $turno (", count($nomes), "): ", implode(", ", $nomes), "\n";
}
echo $turmas["noite"][2], "\n";
$ php turmas.php Turma da manhã (2): Ana, Bruno Turma da noite (3): Carla, Diego, Elisa Elisa
Aqui o array de fora é associativo (chaves "manhã" e "noite") e os de dentro são listas. O endereço $turmas["noite"][2] se lê "da turma da noite, o terceiro nome".
Quase todo dado que circula pela internet tem esse formato de "listas de fichas", e o formato JSON é exatamente isso escrito como texto. Na aula JSON, você vai transformar um array como $alunos em JSON e de volta com uma linha de código.
Gerar uma tabela HTML
Este é o motivo pelo qual arrays multidimensionais aparecem em todo site em PHP: a lista de fichas vira, quase sem esforço, uma tabela na página. A Ana quer publicar a turma no site do curso.
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "cidade" => "Caruaru", "nota" => 8.5],
];
?>
<table border="1">
<tr><th>Nome</th><th>Idade</th><th>Cidade</th><th>Nota</th></tr>
<?php foreach ($alunos as $aluno): ?>
<tr>
<td><?= htmlspecialchars($aluno["nome"]) ?></td>
<td><?= $aluno["idade"] ?></td>
<td><?= htmlspecialchars($aluno["cidade"]) ?></td>
<td><?= number_format($aluno["nota"], 1, ",", ".") ?></td>
</tr>
<?php endforeach; ?>
</table>
| Trecho | O que faz |
|---|---|
?> depois do array | Sai do modo PHP: o que vem depois é HTML, enviado como está (aula PHP Dentro do HTML). |
<?php foreach ($alunos as $aluno): ?> | O foreach na sintaxe alternativa: dois-pontos no lugar de {. Tudo até o endforeach se repete uma vez por aluno, inclusive o HTML. |
<?= … ?> | Atalho para <?php echo … ?>: escreve o valor ali no meio do HTML. |
htmlspecialchars(…) | Protege a página: se um nome tiver <, > ou &, eles aparecem como texto e não viram HTML. Sempre use em textos que vêm de dados (aula Escapar a Saída). |
number_format(…, 1, ",", ".") | Nota com uma casa e vírgula: 9,5. |
<?php endforeach; ?> | Fecha o laço. |
Rodando no terminal, você vê o HTML que o PHP gera, que é o que o navegador recebe:
$ php tabela.php
<table border="1">
<tr><th>Nome</th><th>Idade</th><th>Cidade</th><th>Nota</th></tr>
<tr>
<td>Ana</td>
<td>29</td>
<td>Recife</td>
<td>9,5</td>
</tr>
<tr>
<td>Bruno</td>
<td>34</td>
<td>Olinda</td>
<td>7,0</td>
</tr>
<tr>
<td>Carla</td>
<td>22</td>
<td>Caruaru</td>
<td>8,5</td>
</tr>
</table>
Três fichas no array, três <tr> no HTML. Se a Ana acrescentar um aluno ao array, a tabela ganha uma linha sem ela mexer em nada do HTML.
A outra forma: tudo com echo e dois laços
Também dá para gerar a tabela inteira de dentro do PHP. Com dois foreach, ela nem precisa saber o nome dos campos: o cabeçalho sai das chaves da primeira ficha (array_keys devolve a lista de chaves).
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
];
echo "<table border=\"1\">\n";
echo " <tr>";
foreach (array_keys($alunos[0]) as $campo) {
echo "<th>", htmlspecialchars($campo), "</th>";
}
echo "</tr>\n";
foreach ($alunos as $aluno) {
echo " <tr>";
foreach ($aluno as $valor) {
echo "<td>", htmlspecialchars((string) $valor), "</td>";
}
echo "</tr>\n";
}
echo "</table>\n";
$ php tabela2.php <table border="1"> <tr><th>nome</th><th>idade</th><th>cidade</th><th>nota</th></tr> <tr><td>Ana</td><td>29</td><td>Recife</td><td>9.5</td></tr> <tr><td>Bruno</td><td>34</td><td>Olinda</td><td>7</td></tr> </table>
O (string) converte números em texto antes do htmlspecialchars, que só aceita strings (aula Conversão de Tipos).
Qual usar?
- HTML com
<?= ?>eforeach: … endforeach: quando a página tem um formato definido (cabeçalhos em português, notas formatadas). O HTML fica legível e fácil de mudar. É a forma mais usada em sites. - Tudo com
echo: para uma tabela "genérica", que mostra qualquer array sem conhecer os campos, por exemplo para depurar.
Quando dá errado
Undefined array key 5 seguido de Trying to access array offset on null
<?php
$alunos = [
["nome" => "Ana", "nota" => 9.5],
["nome" => "Bruno", "nota" => 7.0],
];
echo $alunos[5]["nome"], "\n";
echo $alunos[0]["idade"], "\n";
echo $alunos[0], "\n";
$ php erro-multi.php Warning: Undefined array key 5 in /home/ana/site/erro-multi.php on line 6 Warning: Trying to access array offset on null in /home/ana/site/erro-multi.php on line 6 Warning: Undefined array key "idade" in /home/ana/site/erro-multi.php on line 7 Warning: Array to string conversion in /home/ana/site/erro-multi.php on line 8 Array
O que aconteceu, linha a linha:
- Linha 6: não existe o aluno 5.
$alunos[5]valenull(primeiro aviso), e pedir["nome"]denullgera o segundo aviso. Resolva o primeiro e o segundo some. - Linha 7: o aluno 0 existe, mas não tem o campo
"idade". O aviso cita a chave que falta, então você sabe em que nível olhar. - Linha 8: faltou o segundo colchete.
$alunos[0]é uma ficha inteira (um array), e oechonão sabe escrevê-la.
Como resolver: faça print_r do array e confira cada nível do endereço. Se o dado pode faltar, $alunos[5]["nome"] ?? "?" resolve os dois níveis de uma vez, sem aviso.
TypeError: Cannot access offset of type string on string
<?php
$aluno = ["nome" => "Ana", "nota" => 9.5];
foreach ($aluno as $a) {
echo $a["nome"], "\n";
}
$ php erro-string2.php
Fatal error: Uncaught TypeError: Cannot access offset of type string on string in /home/ana/site/erro-string2.php:4
Stack trace:
#0 {main}
thrown in /home/ana/site/erro-string2.php on line 4
O que aconteceu: o foreach foi feito sobre uma ficha, não sobre a lista. Então $a recebeu "Ana" (uma string), e o código tentou ler ["nome"] de dentro de uma string. "Não dá para acessar uma posição do tipo string numa string." O mesmo erro aparece com um colchete a mais, como $aluno["nome"]["nota"].
Como resolver: confira em que nível você está. O foreach que dá fichas é o que percorre a lista ($alunos); o que percorre uma ficha dá os valores soltos. Um var_dump($a) dentro do laço mostra na hora o que a variável recebeu.
Referência rápida
| Você quer… | Código |
|---|---|
| Criar uma lista de fichas | $alunos = [["nome" => "Ana", "nota" => 9.5], ["nome" => "Bruno", "nota" => 7.0]]; |
| Ler um campo | $alunos[0]["nome"] |
| Ler uma ficha inteira | $alunos[0] |
| Mudar um campo | $alunos[1]["nota"] = 8.0; |
| Acrescentar uma ficha | $alunos[] = ["nome" => "Carla", "nota" => 8.5]; |
| Quantas fichas / quantos campos | count($alunos) / count($alunos[0]) |
| Percorrer as fichas | foreach ($alunos as $aluno) { … $aluno["nome"] … } |
| Percorrer tudo | foreach ($alunos as $aluno) { foreach ($aluno as $campo => $valor) { … } } |
| Grade com índices | for ($l…) { for ($c…) { $grade[$l][$c] } } |
| Ler com valor padrão | $alunos[9]["nome"] ?? "?" |
Missão final
Missão: a página mostra a tabela da turma, mas só com o nome. Complete cada linha com mais duas células: a nota (com number_format, uma casa e vírgula) e a situação: Aprovado se a nota for 7 ou mais, Reprovado se for menor. Dica: o operador ternário cabe dentro de um <?= ?>. Veja o resultado na aba Resultado.
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "cidade" => "Caruaru", "nota" => 8.5],
["nome" => "Diego", "idade" => 41, "cidade" => "Petrolina", "nota" => 6.0],
];
?>
<h1>Notas da turma</h1>
<table border="1">
<tr><th>Nome</th><th>Nota</th><th>Situação</th></tr>
<?php foreach ($alunos as $aluno): ?>
<tr>
<td><?= htmlspecialchars($aluno["nome"]) ?></td>
<!-- complete aqui: a nota e a situação -->
</tr>
<?php endforeach; ?>
</table>
<?php
$alunos = [
["nome" => "Ana", "idade" => 29, "cidade" => "Recife", "nota" => 9.5],
["nome" => "Bruno", "idade" => 34, "cidade" => "Olinda", "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "cidade" => "Caruaru", "nota" => 8.5],
["nome" => "Diego", "idade" => 41, "cidade" => "Petrolina", "nota" => 6.0],
];
?>
<h1>Notas da turma</h1>
<table border="1">
<tr><th>Nome</th><th>Nota</th><th>Situação</th></tr>
<?php foreach ($alunos as $aluno): ?>
<tr>
<td><?= htmlspecialchars($aluno["nome"]) ?></td>
<td><?= number_format($aluno["nota"], 1, ",", ".") ?></td>
<td><?= $aluno["nota"] >= 7 ? "Aprovado" : "Reprovado" ?></td>
</tr>
<?php endforeach; ?>
</table>
No laboratório, a solução gera este HTML:
$ php notas.php
<h1>Notas da turma</h1>
<table border="1">
<tr><th>Nome</th><th>Nota</th><th>Situação</th></tr>
<tr>
<td>Ana</td>
<td>9,5</td>
<td>Aprovado</td>
</tr>
<tr>
<td>Bruno</td>
<td>7,0</td>
<td>Aprovado</td>
</tr>
<tr>
<td>Carla</td>
<td>8,5</td>
<td>Aprovado</td>
</tr>
<tr>
<td>Diego</td>
<td>6,0</td>
<td>Reprovado</td>
</tr>
</table>
Exercícios
1. O endereço do dado
Complete para mostrar a cidade do segundo aluno da lista:
2. Uma ficha nova
Complete para acrescentar uma ficha no fim da lista:
3. O laço de dentro
Complete o segundo foreach para percorrer os campos de cada ficha:
foreach ( as $campo => $valor) { … }
}
4. Com $m = [[1, 2, 3], [4, 5, 6]];, quanto vale $m[1][0]?
5. E count($m), com o mesmo array?
6. O código echo $alunos[0]; mostra Array e um aviso. Por quê?
Resumo
- Um array multidimensional é um array cujos itens são arrays. O formato mais comum é a lista de fichas: indexado por fora, associativo por dentro.
- Cada par de colchetes desce um nível:
$alunos[0]["nome"]= ficha 0, campo nome. - Mudar e acrescentar usam a mesma escrita:
$alunos[1]["nota"] = 8;e$alunos[] = [ … ];. countconta só o nível em que você está.- Um
foreachdá uma ficha por volta; doisforeachaninhados passam por todos os campos. Para grades com índices, doisfor. - Para gerar HTML:
foreach: … endforeachcom<?= htmlspecialchars(…) ?>. - Erros de nível:
Array to string conversion(faltou colchete),Cannot access offset of type string on string(colchete a mais ou laço no nível errado).