PHP e páginas dinâmicas com base de dados

Tipos, funções, pedidos, PDO, transações, templates, sessões e autenticação no servidor.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Ao receber quantidade=2, o servidor precisa de distinguir uma string válida de um parâmetro ausente, de 2x ou de um array. O PHP faz essa verificação antes de alterar o stock. Corre a cada pedido, e o cliente recebe apenas a saída. Variáveis locais não sobrevivem ao pedido; a base de dados e a sessão conservam o estado necessário.

Valores, strings e arrays

Uma variável começa por $; o valor tem um tipo em execução. === compara valor e tipo, enquanto == admite conversões. Se uma função devolve uma nota de 0 a 20 ou null quando o aluno não existe, verifica !== null. O teste if ($nota) também rejeitaria a nota válida 0.

<?php
$nota = 0;
var_dump($nota !== null); // bool(true)
var_dump((bool) $nota);   // bool(false)
echo 'Nota: ' . $nota;    // Nota: 0

A concatenação usa ., não +. Strings com aspas duplas podem interpolar variáveis; aspas simples tratam o texto mais literalmente, com exceções para a barra e a própria aspa. strlen mede bytes, por isso um caráter UTF-8 pode ocupar mais de uma unidade. Não o confundas com a quantidade de letras visíveis.

Um array PHP é um mapa ordenado de chaves para valores. Pode funcionar como lista ou como registo:

<?php
$livro = ['titulo' => 'Redes', 'preco' => 18];
$nomes = ['Redes', 'SQL'];
$nomes[] = 'Algoritmos';
foreach ($nomes as $indice => $nome) {
    echo $indice, ': ', $nome, "\n";
}

A saída tem as linhas 0: Redes, 1: SQL, 2: Algoritmos. count conta entradas. array_map transforma valores e array_filter escolhe-os; alguns métodos conservam chaves, por isso uma lista filtrada pode não ter índices consecutivos.

Três testes respondem a perguntas diferentes. isset($x) é falso se $x não existe ou vale null. array_key_exists distingue uma chave existente com valor null de uma chave ausente. empty também considera vazios valores como 0 e a string '0', por isso não serve para aceitar um zero válido.

strpos('Redes', 'Red') devolve 0; strpos('Redes', 'SQL') devolve false. A procura deve usar !== false, porque a posição zero é uma correspondência válida. explode(',', 'Redes,SQL') divide numa lista; implode(' / ', $nomes) junta os valores com esse separador.

sort ordena valores e renumera chaves; asort ordena valores conservando as chaves; ksort ordena pelas chaves. As três alteram o array recebido.

Depois de array_filter, usa array_values se precisas de uma lista com índices consecutivos, por exemplo para emitir um array JSON. Sem essa chamada, um filtro que remove a posição 0 conserva a chave 1, e json_encode emite um objeto como {"1":"SQL"}. Com array_values, as chaves voltam a 0 e o resultado é ["SQL"].

Funções, tipos e erros

As funções têm o seu âmbito. Passar um array por valor permite alterá-lo localmente sem alterar o do chamador; um parâmetro com & passa por referência. Objetos têm outra semântica: duas variáveis podem referir o mesmo objeto.

<?php
declare(strict_types=1);
function total(int $preco, int $quantidade = 1): int {
    return $preco * $quantidade;
}
echo total(1250, 2); // 2500

strict_types altera a aceitação de argumentos escalares nas chamadas feitas por esse ficheiro. Não valida parâmetros HTTP nem altera todos os operadores de conversão. Em modo estrito, chamar esta função com '2' em vez de 2 gera TypeError.

Por isso valida e converte a entrada na fronteira antes de chamar funções do domínio. ?int admite inteiro ou null.

Uma classe junta estado e operações. private limita o acesso à classe que declara o membro; protected permite também o acesso nas subclasses; public expõe-o. $this identifica o objeto atual; uma operação static pertence à classe e não tem $this. extends herda de uma única classe e implements declara um contrato de interface. Não precisas de uma classe para cada página: usa-a quando representa um conceito e reúne operações relacionadas.

<?php
class Reserva {
    private int $quantidade;
    public function __construct(int $quantidade) {
        if ($quantidade < 1) throw new InvalidArgumentException('Quantidade inválida');
        $this->quantidade = $quantidade;
    }
    public function unidades(): int { return $this->quantidade; }
}
echo (new Reserva(2))->unidades(); // 2

Para chamar um método estático, usa Classe::metodo(); uma propriedade estática usa Classe::$propriedade. Dentro de métodos, self:: resolve a classe onde o código foi definido, parent:: a classe pai e static:: a classe usada na chamada, respeitando a herança. Uma classe abstract não pode ser instanciada e pode exigir métodos sem implementação; uma interface declara métodos que a classe deve cumprir. final impede estender a classe ou substituir o método marcado.

throw interrompe o percurso normal. Um catch apropriado trata a exceção; finally corre no fim, incluindo quando houve exceção. Distingue um erro de entrada que podes explicar ao utilizador de um erro interno que deves registar sem revelar caminhos, SQL ou segredos ao cliente.

Receber um pedido

$_GET contém parâmetros da query string. $_POST contém parâmetros de corpos codificados como formulário, não qualquer corpo de um POST. Para JSON, lê php://input e usa json_decode. $_SERVER['REQUEST_METHOD'] indica o método.

Um parâmetro enviado pelo cliente pode faltar, ser uma string inesperada ou ser um array, por exemplo quantidade[]=2. No exemplo executável, a entrada é o corpo HTTP; parse_str faz explicitamente o trabalho de descodificar o formulário:

Validar a quantidade recebida
<?php
$entrada = file_get_contents('php://input');
parse_str($entrada, $dados);
$bruto = $dados['quantidade'] ?? null;
$quantidade = is_string($bruto)
    ? filter_var($bruto, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 3]])
    : false;
if ($quantidade === false) {
    echo "Quantidade inválida.
";
    exit;
}
$precoCentimos = 1250;
echo 'Total: ', $quantidade * $precoCentimos, " cêntimos
";
Resultado

Executa o código para ver a saída e os gráficos.

Com quantidade=2 obténs Total: 2500 cêntimos; com quantidade=0, quantidade=2x, corpo vazio ou quantidade[]=2, obténs Quantidade inválida.. Primeiro confirmamos que o valor é string; depois verificamos inteiro entre 1 e 3, porque testar apenas intval('2x') aceitaria 2 e perderia o lixo da entrada.

Numa ação real de formulário, usa $bruto = $_POST['quantidade'] ?? null, verifica o método e devolve um estado de erro adequado. Usa inteiros em cêntimos quando queres contas exatas de dinheiro; números de vírgula flutuante podem introduzir arredondamento.

SQLite e consultas preparadas

O PDO dá uma interface para a base de dados. A conexão escolhe o driver; uma consulta preparada mantém os valores separados do texto SQL:

A consulta pode ser estudada primeiro sem PHP. O ficheiro de apoio cria os mesmos três livros; o filtro conserva os preços inferiores a 20 e a ordenação põe SQL antes de Redes.

Ficheiros do exemplo
Filtrar e ordenar livros em SQLite
SELECT titulo, preco FROM livro WHERE preco < 20 ORDER BY preco;
CREATE TABLE livro (
    id INTEGER PRIMARY KEY,
    titulo TEXT NOT NULL,
    preco INTEGER NOT NULL
);
INSERT INTO livro (titulo, preco) VALUES
    ('Redes', 18), ('Algoritmos', 25), ('SQL', 12);
Resultado

Executa a consulta para ver as tabelas.

No programa PHP, a consulta tem um parâmetro :limite. O driver recebe o valor 20 separadamente do texto SQL:

Consultar preços com PDO
<?php
$db = new PDO('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->exec('CREATE TABLE livro (id INTEGER PRIMARY KEY, titulo TEXT NOT NULL, preco INTEGER NOT NULL)');
$inserir = $db->prepare('INSERT INTO livro (titulo, preco) VALUES (?, ?)');
foreach ([['Redes', 18], ['Algoritmos', 25], ['SQL', 12]] as $livro) {
    $inserir->execute($livro);
}
$consulta = $db->prepare('SELECT titulo, preco FROM livro WHERE preco < :limite ORDER BY preco');
$consulta->execute(['limite' => 20]);
foreach ($consulta->fetchAll() as $livro) {
    echo $livro['titulo'], ': ', $livro['preco'], "
";
}
Resultado

Executa o código para ver a saída e os gráficos.

O programa cria uma base em memória, insere três livros e consulta preços abaixo de 20. Obténs SQL: 12 e Redes: 18, por ordem de preço crescente.

prepare analisa uma instrução com marcadores; execute fornece os valores. fetch obtém uma linha ou false quando não há mais; fetchAll obtém as restantes. PDO::FETCH_ASSOC permite aceder por nomes de colunas, e o modo de exceção torna os erros SQL explícitos.

Para persistir entre pedidos, usa uma base em ficheiro e um caminho estável, por exemplo new PDO('sqlite:' . __DIR__ . '/../database/biblioteca.db'). O processo do servidor precisa de permissão para aceder à base; não a coloques numa pasta pública que permita o download.

Alterar dados de forma consistente

Uma reserva pode exigir reduzir stock e criar uma linha. Uma transação faz com que as alterações sejam confirmadas em conjunto ou anuladas. Este excerto pressupõe a conexão $db, entrada já validada e autenticação e CSRF já verificados:

$db->beginTransaction();
try {
    $stmt = $db->prepare('UPDATE livro SET stock = stock - :q WHERE id = :id AND stock >= :q');
    $stmt->execute(['q' => $quantidade, 'id' => $livroId]);
    if ($stmt->rowCount() !== 1) {
        throw new RuntimeException('Stock insuficiente');
    }
    $stmt = $db->prepare('INSERT INTO reserva (utilizador_id, livro_id, quantidade) VALUES (?, ?, ?)');
    $stmt->execute([$utilizadorId, $livroId, $quantidade]);
    $db->commit();
} catch (Throwable $erro) {
    if ($db->inTransaction()) $db->rollBack();
    throw $erro;
}

Para reservar duas unidades de um livro com stock 3, os estados dentro da transação são:

PassoStockReserva
Antes da transação3Não existe
UPDATE, com :q = 21Não existe ainda
INSERT1Duas unidades
commit1Confirmada
Se o INSERT falhar, rollback3Não criada

O commit confirma as duas alterações em conjunto. Se outro pedido tentar reservar duas unidades quando o stock já é 1, a condição stock >= :q falha: o UPDATE não altera linhas e a exceção impede o INSERT. Ler o stock antes e atualizar depois sem condição deixaria uma janela entre os passos. As restrições da base, como chaves estrangeiras, completam a validação; em SQLite, ativa PRAGMA foreign_keys = ON em cada conexão quando dependes delas.

Páginas, ações e templates

Uma página consulta e apresenta. Uma ação recebe entrada, valida, altera dados e redireciona. O template produz HTML a partir de dados já calculados. Coloca a conexão e as consultas numa camada própria, para não repetir SQL nos templates.

Os delimitadores permitem alternar entre PHP e HTML. O texto fora deles é enviado diretamente; <?= ... ?> escreve o valor da expressão. Este exemplo completo recebe os dados numa lista antes de começar a saída:

<?php
$livros = [['titulo' => 'Redes & Web'], ['titulo' => '<SQL>']];
?>
<ul>
<?php foreach ($livros as $livro) { ?>
  <li><?= htmlspecialchars($livro['titulo'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></li>
<?php } ?>
</ul>

O HTML enviado contém Redes &amp; Web e &lt;SQL&gt;. O navegador apresenta os títulos originais como texto. O ciclo abre num bloco PHP e fecha noutro, envolvendo um li por livro. Guardar o título como texto e escapar ao apresentar permite reutilizá-lo em contextos diferentes.

<?php
require_once __DIR__ . '/../database/livros.php';
require_once __DIR__ . '/../templates/livros.php';
$livros = procurarLivros($db, $termo);
mostrarLivros($livros);

Este é um excerto de organização, com funções definidas nesses ficheiros. require falha quando falta um ficheiro necessário; include pode continuar com aviso. A variante _once evita inclusões repetidas. __DIR__ dá a pasta do ficheiro atual, evitando depender da pasta de execução.

Ao apresentar texto em HTML, usa htmlspecialchars($texto, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'). Uma string válida na base de dados ainda precisa do escape apropriado ao contexto de saída.

header('Location: /reservas/', true, 303);
exit;

header deve correr antes de saída que envie os cabeçalhos. O redirecionamento instrui o cliente; não pára o PHP, por isso usamos exit. Após um POST, 303 leva o navegador a fazer um GET ao destino. Isso evita repetir o POST ao atualizar a página de confirmação.

Sessões e autenticação

HTTP não guarda automaticamente a identidade entre pedidos. session_start() recupera ou cria estado no servidor; um cookie contém habitualmente o identificador da sessão. Não guarda a palavra-passe nem todo o conteúdo de $_SESSION no navegador.

No registo, guarda password_hash($palavraPasse, PASSWORD_DEFAULT). No login, consulta o utilizador pelo identificador e verifica o resumo com password_verify. Este excerto pressupõe $utilizador obtido por consulta preparada:

session_start();
if ($utilizador && password_verify($palavraPasse, $utilizador['password_hash'])) {
    session_regenerate_id(true);
    $_SESSION['utilizador_id'] = $utilizador['id'];
    header('Location: /reservas/', true, 303);
    exit;
}

Segue dois pedidos. No login, o servidor verifica a palavra-passe, regenera o identificador para combater fixação de sessão e guarda utilizador_id no estado do servidor. No pedido seguinte, o navegador envia apenas o identificador no cookie, e o servidor recupera o utilizador a partir dele.

Regenerar o identificador após autenticar é obrigatório nesse percurso. Configura cookies com HttpOnly, Secure em HTTPS e uma política SameSite adequada. Usa um destino fixo ou validado; não confies cegamente no Referer recebido.

Autenticar identifica o utilizador. Autorizar verifica o que ele pode fazer. Uma ação de edição deve confirmar a sessão e a propriedade do registo, mesmo que o botão só apareça para o dono. No logout, limpa $_SESSION, destrói a sessão e remove o cookie quando aplicável. A segurança web explica as defesas que completam estas ações.

Âmbito global e captura de uma variável

Uma função nomeada não recebe automaticamente as variáveis globais. Em precoLocal, a $taxa definida no corpo é local e vale 1; a global começa em 2. global $taxa em mudarTaxa liga o nome local à variável global, permitindo alterá-la.

Uma closure explicita o que conserva do ambiente. use ($taxa) captura o valor quando a função é criada. use (&$taxa) conserva uma referência à variável. Depois de mudar a global para 5, essas duas funções já não usam a mesma taxa:

Local, global e duas capturas
<?php
$taxa = 2;
function precoLocal(int $base): int {
    $taxa = 1;
    return $base + $taxa;
}
function mudarTaxa(int $nova): void {
    global $taxa;
    $taxa = $nova;
}
$fixa = function (int $base) use ($taxa): int {
    return $base + $taxa;
};
$atual = function (int $base) use (&$taxa): int {
    return $base + $taxa;
};
mudarTaxa(5);
echo precoLocal(10), "\n";
echo $fixa(10), "\n";
echo $atual(10), "\n";
echo $taxa, "\n";
Resultado

Executa o código para ver a saída e os gráficos.

ExpressãoResultadoTaxa usada
precoLocal(10)11Local, 1
$fixa(10)12Capturada por valor, 2
$atual(10)15Variável referida, agora 5
$taxa no ficheiro5Global alterada pela função

Os nomes iguais não juntam âmbitos diferentes. Sem a declaração local nem global, ler $taxa numa função nomeada não a vai procurar no ficheiro exterior: no PHP 8 gera um aviso de variável indefinida. Os exemplos históricos podem mostrar um aviso menos grave em versões anteriores; não uses essa leitura como passagem de argumentos.

$_POST, $_GET e $_SESSION são superglobais e estão acessíveis dentro de funções sem global. Isso não transforma as variáveis habituais em superglobais. Para uma função de cálculo, passar a taxa como argumento torna a dependência visível na chamada e evita alterar estado global.

Terminar uma referência de foreach

O & em foreach ($notas as &$nota) permite alterar cada posição do array. No fim do ciclo, $nota continua ligada à última posição visitada. O fim das chavetas não termina essa referência:

A última nota depois do ciclo
<?php
$notas = [12, 15, 18];
foreach ($notas as &$nota) {
    ++$nota;
}
$nota = 0;
echo json_encode($notas), "\n";
unset($nota);

$notas = [12, 15, 18];
foreach ($notas as &$nota) {
    ++$nota;
}
unset($nota);
$nota = 0;
echo json_encode($notas), "\n";
Resultado

Executa o código para ver a saída e os gráficos.

No primeiro percurso, incrementar dá [13,16,19], mas $nota = 0 altera a última posição e a saída passa a [13,16,0]. No segundo, unset($nota) desfaz a ligação antes dessa atribuição. O array conserva [13,16,19]; o novo $nota é uma variável independente.

unset aqui remove o nome que ficou como referência, não o último elemento do array. Para só ler notas, usa foreach ($notas as $nota) sem &. Se precisas de as transformar sem esse vínculo posterior, podes atribuir por índice ou criar outra lista com array_map. Não deixes uma referência pendente que o código seguinte possa alterar sem intenção.

Exercícios

Distinguir zero de ausência

Uma função obterNota devolve um inteiro de 0 a 20 ou null quando não existe nota. Porque falha if (obterNota($id))? Escreve uma condição correta chamando a função uma só vez.

Pista

Zero é falso numa condição booleana.

Solução

Usa $nota = obterNota($id); if ($nota !== null) { /* existe */ }. A nota zero é um resultado válido e passa esta condição. A condição original confundia um valor existente igual a zero com ausência.

Confere a tua resposta:

  • Conservei 0 como valor válido.
  • Usei uma única chamada e comparação estrita.

Erros frequentes

Não usar empty, porque também rejeita 0, nem assumir que uma nota em falta é uma string vazia.

Voltar à explicação
Validar a fronteira HTTP

Uma ação aceita quantidade inteira entre 1 e 5. Analisa as entradas ausente, quantidade=3, quantidade=3x e quantidade[]=3. Descreve a validação antes de chamar reservar(int $q).

Pista

Uma entrada HTTP não é necessariamente uma string.

Mais uma pista

Confirma is_string antes de filter_var e usa min_range/max_range.

Solução

A ausência deve falhar porque não há valor. 3 deve produzir o inteiro 3. 3x deve falhar porque contém lixo, mesmo que intval extraísse 3. quantidade[]=3 chega como array e deve falhar no teste is_string antes da validação escalar. Obtém $_POST['quantidade'] ?? null, exige string e FILTER_VALIDATE_INT entre 1 e 5. Só depois passa o inteiro à função.

Confere a tua resposta:

  • Rejeitei ausência, lixo e array.
  • Passei um inteiro validado à função.

Erros frequentes

intval('3x') devolveria 3 e perderia o lixo da entrada, por isso converter não substitui validar. strict_types na função não substitui validação HTTP.

Voltar à explicação
Escolher SQL e ordenação

Escreve uma consulta PDO para listar titulo e preco de livros com preco menor ou igual a um limite recebido. A ordenação recebida só pode ser titulo ou preco. Explica por que não usar ORDER BY :coluna.

Pista

Valores usam marcadores; nomes de colunas precisam de uma lista fixa.

Mais uma pista

Escolhe um dos dois textos conhecidos e prepara a condição de preço.

Solução

Com $db conectado e $limite já validado, a escolha de coluna pode ser feita assim:

$colunas = ['titulo' => 'titulo', 'preco' => 'preco'];
$ordem = $_GET['ordem'] ?? null;
if (!is_string($ordem) || !isset($colunas[$ordem])) {
    throw new InvalidArgumentException('Ordenação inválida');
}
$sql = 'SELECT titulo, preco FROM livro WHERE preco <= :limite ORDER BY '
    . $colunas[$ordem];
$stmt = $db->prepare($sql);
$stmt->execute(['limite' => $limite]);
$livros = $stmt->fetchAll(PDO::FETCH_ASSOC);

A concatenação usa apenas um dos dois nomes fixos. :limite representa um valor; ORDER BY :coluna não transforma o valor recebido num nome de coluna. Numa ação real, trata a exceção como erro de entrada, sem expor detalhes internos.

Confere a tua resposta:

  • Preparei o limite como valor.
  • Escolhi a coluna numa lista fechada.

Erros frequentes

Preparar não torna seguro concatenar uma coluna arbitrária enviada pelo cliente.

Voltar à explicação
Separar alteração e confirmação

A reserva reduz stock e insere uma linha. A inserção falha após o UPDATE. O que tem de acontecer à primeira alteração? Após sucesso, como evitas repetir o POST ao atualizar a confirmação?

Pista

As duas alterações pertencem à mesma transação.

Mais uma pista

O cliente deve seguir um 303 para uma página de leitura.

Solução

Se a inserção falha, faz rollback da transação, recuperando o stock. Só faz commit depois dos dois passos. No sucesso, envia Location para um destino fixo com estado 303 e termina com exit. O cliente lê a confirmação por GET, de modo que atualizar essa página não repete o POST.

Confere a tua resposta:

  • Expliquei rollback da primeira alteração.
  • Usei commit no sucesso e 303 com exit.

Erros frequentes

Location sozinho não termina o PHP. A transação não substitui validar stock no UPDATE nem confirmar a autorização.

Voltar à explicação
Conservar chaves e emitir uma lista

Prevê as duas saídas e corrige a segunda para que uma API envie uma lista JSON com apenas SQL.

<?php
$livros = ['Redes', 'SQL'];
$filtrados = array_filter($livros, fn($titulo) => $titulo === 'SQL');
var_dump(strpos('SQL avançado', 'SQL'));
echo json_encode($filtrados);

Pista

array_filter conserva as chaves da entrada.

Solução

A primeira saída é int(0), uma correspondência no início, não ausência. A segunda é {"1":"SQL"}, porque a chave 0 foi removida e a chave 1 foi conservada. Usa echo json_encode(array_values($filtrados)); para obter ["SQL"]. Se usasses o resultado de strpos como booleano, rejeitarias esta correspondência válida.

Confere a tua resposta:

  • Distingui posição 0 de false.
  • Expliquei a chave 1 preservada e usei array_values.

Erros frequentes

Não presumir que filtrar renumera a lista, nem testar a posição com if sem comparação estrita com false.

Voltar à explicação
Distinguir captura de estado global

Prevê os três valores escritos. O ficheiro é executado desde o início em cada pedido e não usa sessão, ficheiro nem base de dados. Um novo pedido começa no total 3?

<?php
$total = 0;
function somar(int $valor): void {
    global $total;
    $total += $valor;
}
$antes = function () use ($total) { return $total; };
$agora = function () use (&$total) { return $total; };
somar(3);
echo $antes(), ' ', $agora(), ' ', $total;

Refatora o cálculo para uma função que receba total e valor e devolva a soma, sem alterar uma global. Indica a atribuição que atualiza o total no chamador.

Solução

A saída é 0 3 3. somar liga o nome local à global e deixa-a em 3. A closure antes conservou 0 na criação; agora refere a variável agora igual a 3. A terceira leitura é da mesma global.

Um novo pedido volta a executar $total = 0 e não recupera o valor anterior. A global é estado do programa desse pedido, não estado persistente do utilizador.

function somado(int $total, int $valor): int {
    return $total + $valor;
}
$total = somado($total, 3);

A função só calcula. A atribuição no chamador torna explícito que valor é atualizado. Nem uma variável com o mesmo nome numa função nem uma captura por valor criam acesso automático à global.

Confere a tua resposta:

  • Obtive 0 3 3 e distingui as duas capturas.
  • Identifiquei global como acesso à variável exterior.
  • Expliquei por que um novo pedido não recupera este total sem persistência.
Voltar à explicação
Um percurso de leitura que altera a última posição

Prevê o array escrito e explica por que o segundo ciclo o altera, apesar de não ter & na sua declaração. Corrige o programa para conservar todos os valores duplicados pelo primeiro ciclo.

<?php
$valores = [2, 4, 6];
foreach ($valores as &$valor) {
    $valor *= 2;
}
foreach ($valores as $valor) {
    // Apenas pretendemos percorrer os valores.
}
echo json_encode($valores);

Pista

O segundo ciclo também atribui valores ao nome que recebe cada elemento.

Solução

Depois do primeiro ciclo, o array é [4,8,12], e $valor continua a referir a sua última posição. No segundo, atribuir 4 a esse nome deixa o último elemento em 4; atribuir 8 deixa-o em 8. A última iteração lê essa posição já alterada, que vale 8. A saída é [4,8,8].

Coloca unset($valor); imediatamente depois do primeiro ciclo. O segundo passa então a usar uma variável independente e conserva [4,8,12]. Usar outro nome no segundo ciclo evitaria esta alteração concreta, mas deixaria a referência pendente para o código posterior. Não apagues $valores[2]: o objetivo é terminar o vínculo, não perder uma nota.

Confere a tua resposta:

  • Obtive [4,8,8] e segui a referência à última posição.
  • Coloquei unset depois do ciclo por referência.
  • Conservei [4,8,12] na leitura corrigida.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.