O instante da ligação importa
Um importador fictício recebe dois montantes, 10 e 20, e prepara statements para executar mais tarde. bindParam conserva uma referência à variável; o valor usado depende do que ela contém quando execute é chamado. Se os dois statements apontarem para a mesma variável reutilizada no ciclo, ambos podem receber 20. bindValue captura o valor entregue na chamada de ligação. Desenha a sequência preparação, ligação, alteração e execução antes de escolher o método. A transação agrupa efeitos na base de dados, mas não corrige uma referência PHP mal utilizada. O tipo PDO::PARAM_INT declara o tipo do parâmetro e também não transforma referência em cópia.
Cada execução tem entradas completas
Num worker que reutiliza um statement, trata cada tarefa como uma operação nova. Fornece os parâmetros exigidos por essa execução, em vez de depender de valores deixados por uma tarefa anterior. Quando o tipo de ligação é relevante, escolhe uma estratégia explícita e testa-a no driver utilizado. O array de execute não é equivalente a declarar cada parâmetro como inteiro com bindValue. Um teste SQLite com typeof permite observar essa diferença localmente; não prova todas as conversões de outro motor. Parâmetros representam valores, não nomes de tabelas ou colunas. Para ordenação configurável, mapeia opções permitidas para fragmentos SQL definidos pela aplicação.
Ler uma linha é avançar o cursor
Uma query devolve duas colunas por tarefa. Chamar fetchColumn para a primeira coluna e depois para a segunda não lê dois campos da mesma linha: cada chamada obtém a coluna de uma nova linha. Lê uma linha com fetch e extrai os campos dessa estrutura. Se a query junta duas tabelas com coluna id, atribui aliases como task_id e owner_id. FETCH_ASSOC não fornece duas entradas distintas com a mesma chave. FETCH_BOTH acrescenta posições numéricas e não substitui um contrato de API. O teste deve verificar os nomes e a relação entre valores, não apenas a quantidade de resultados recebidos.
Zero, vazio e falha são resultados diferentes
Um COUNT pode devolver zero numa linha válida; uma leitura seguinte pode devolver false porque o cursor terminou. Usa comparações que respeitem esses contratos. fetchAll sem linhas devolve um array vazio e, depois de uma leitura anterior, recolhe apenas as linhas restantes. Um UPDATE pode executar com sucesso e afetar zero linhas porque o predicado não corresponde a nenhum registo. PDO::exec também pode devolver zero sem representar erro. Não uses rowCount de SELECT como contagem portátil: o comportamento depende do driver. Se precisas apenas de saber se existe um registo, lê uma linha; se precisas da contagem, define a query e o momento dessa observação.
Laboratório: conservar dois valores
O código cria uma base SQLite em memória, prepara dois INSERT e adia a execução até terminar o ciclo. Prevê a saída antes de executar: as linhas devem conservar os montantes 10 e 20. Depois troca apenas bindValue por bindParam, mantendo a variável do ciclo, e observa a alteração. Volta à versão correta antes de continuar. A leitura usa um alias explícito para o identificador e FETCH_ASSOC para obter uma estrutura pequena. Acrescenta ao teste uma leitura depois do fim do cursor e confirma false com comparação estrita. Este laboratório é local e não usa dados de clientes nem uma ligação a uma base de produção.
Resumo para diagnóstico de integrações
Quando um lote termina sem exceção mas contém valores errados, separa três perguntas: quais foram os parâmetros no momento da execução, que linhas a query devolveu e que efeitos a escrita realmente produziu? Regista uma amostra sintética com valores e ordem das chamadas. Um contador de sucesso não prova que o contrato foi cumprido. Confirma aliases, posição do cursor e tratamento de resultados vazios antes de alterar a infraestrutura. Relaciona esta aula com os contratos JSON: uma estrutura PDO ambígua pode tornar-se uma resposta JSON válida e semanticamente errada. A aula seguinte acrescenta propriedade da transação e decisões perante concorrência, que exigem garantias além da leitura correta.
<?php
declare(strict_types=1);
$db = new PDO('sqlite::memory:', null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$db->exec('CREATE TABLE entries(id INTEGER PRIMARY KEY, amount INTEGER NOT NULL)');
$queue = [];
foreach ([10, 20] as $amount) {
$statement = $db->prepare('INSERT INTO entries(amount) VALUES(:amount)');
$statement->bindValue(':amount', $amount, PDO::PARAM_INT);
$queue[] = $statement;
}
foreach ($queue as $statement) {
$statement->execute();
}
$rows = $db->query('SELECT id AS entry_id, amount FROM entries ORDER BY id')->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($rows, JSON_THROW_ON_ERROR);
Dois lançamentos preparados com a mesma variável conservam os valores originais quando bindValue captura cada montante.
Armadilhas comuns
Não confundir referências com cópias, campos da mesma linha com leituras sucessivas, ou zero linhas afetadas com falha SQL.
Tópicos relacionados: Transações e falhas parciais em PDO · JSON, contratos e falhas explícitas · Streams, CSV e importações controladas
Define os parâmetros, a forma do resultado e a interpretação de zero e false como partes explícitas do contrato.
Referência: PHP manual: pdostatement.fetch · PHP 8.5 reference; DR PHP 2026.3; new fixtures executed on PHP 8.4.4 / SQLite 3.51.2