Médio PHP
Transação com rollback
Enunciado
Crie um script que gerencie uma conta bancária simples. Crie uma tabela contas com id e saldo. Insira uma conta com saldo 100. Tente transferir 150 para outra conta (que não existe), mas dentro de uma transação que deve ser revertida (rollback) se o saldo for insuficiente. Exiba o saldo final da conta original.
Requisitos
- Usar
beginTransaction,commiterollBack. - Verificar saldo antes de debitar; se insuficiente, lançar exceção e fazer rollback.
- Exibir o saldo final da conta após a tentativa.
- Usar prepared statements para consultas e atualizações.
Código inicial
<?php
// Seu código aqui
Saída esperada
Saldo final: 100
Ver dica
Dentro da transação, consulte o saldo atual. Se for menor que o valor a transferir, lance new Exception('Saldo insuficiente') e capture para dar rollback.
Mostrar solução
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Cria tabela e insere conta inicial
$pdo->exec('CREATE TABLE contas (id INTEGER PRIMARY KEY, saldo REAL)');
$pdo->exec('INSERT INTO contas (id, saldo) VALUES (1, 100)');
// Tenta transferir 150 da conta 1 para conta 2 (inexistente)
try {
$pdo->beginTransaction();
// Consulta saldo atual
$stmt = $pdo->prepare('SELECT saldo FROM contas WHERE id = :id');
$stmt->execute([':id' => 1]);
$saldo = $stmt->fetchColumn();
if ($saldo < 150) {
throw new Exception('Saldo insuficiente');
}
// Debita da conta 1
$stmt = $pdo->prepare('UPDATE contas SET saldo = saldo - :valor WHERE id = :id');
$stmt->execute([':valor' => 150, ':id' => 1]);
// Credita na conta 2 (não existe, então o UPDATE não afeta linhas)
$stmt = $pdo->prepare('UPDATE contas SET saldo = saldo + :valor WHERE id = :id');
$stmt->execute([':valor' => 150, ':id' => 2]);
$pdo->commit();
echo "Transferência realizada.\n";
} catch (Exception $e) {
$pdo->rollBack();
echo "Transferência falhou: " . $e->getMessage() . "\n";
}
// Exibe saldo final
$stmt = $pdo->query('SELECT saldo FROM contas WHERE id = 1');
$saldoFinal = $stmt->fetchColumn();
echo "Saldo final: $saldoFinal\n";
Passo a passo
- Conectamos ao SQLite em memória e configuramos exceções.
- Criamos a tabela
contase inserimos uma conta com saldo 100. - Iniciamos uma transação com
beginTransaction(). - Consultamos o saldo atual da conta 1 com prepared statement e
fetchColumn(). - Comparamos: se saldo < 150, lançamos uma exceção para forçar o rollback.
- Caso contrário, debitaríamos e creditaríamos, mas como o saldo é insuficiente, a exceção é lançada antes.
- No
catch, chamamosrollBack()e exibimos a mensagem de falha. - Por fim, consultamos o saldo final e mostramos que permaneceu 100.
Por que funciona
A transação garante que todas as operações sejam atômicas: ou todas são efetivadas, ou nenhuma. O rollback desfaz qualquer alteração feita desde o beginTransaction. O prepared statement evita injeção e o fetchColumn retorna um único valor.
Erros comuns
- Esquecer de chamar
rollBack()nocatch: a transação fica aberta e pode causar bloqueios. - Não verificar o saldo antes de debitar: poderia deixar saldo negativo.
- Usar
query()em vez deprepare()para valores dinâmicos: risco de SQL injection.
Outra forma de resolver
Poderia usar $pdo->exec('BEGIN') e $pdo->exec('ROLLBACK'), mas a API orientada a objetos é mais clara e portável.
Saída esperada
Transferência falhou: Saldo insuficiente
Saldo final: 100