Capítulo 19
Falhas controladas e proteção do formulário
6 min de leitura 1.122 palavras 1 gravura
Neste capítulo você vai conferir um token de formulário e tratar falhas sem prometer uma gravação inexistente.
Seu ponto de partida
Estude depois do capítulo 18. O exemplo deste capítulo é 19-falhas-protecao, no pacote que acompanha o volume. Copie a pasta inicio para um diretório de prática e preserve o original. A raiz do exemplo é a referência concluída desta etapa. Sua cópia permite experimentar sem perder a referência.
Execute o PHP na raiz da sua cópia com php -S 127.0.0.1:8000 -t public. Abra http://127.0.0.1:8000/. Não abra PHP por duplo clique nem use o servidor de Python. Encerre com Ctrl+C antes de trocar de etapa.
Como praticar
Leia a explicação e digite ou confira o código indicado. Mude uma coisa. Antes de conferir, anote sua previsão. Salve, recarregue e compare. Se não acontecer o esperado, investigue o erro antes de avançar. Cada experiência parte dos valores da referência. Depois de conferir, desfaça somente a mudança desse experimento antes de começar a próxima lição. Você pode parar depois de uma lição e retomar mais tarde.
Confira se o envio veio do formulário desta sessão
Um token é um valor imprevisível gerado pelo programa. O exemplo usa random_bytes para criar esse valor e o guarda na sessão. O formulário o envia em um campo oculto.
No POST, o servidor confere o token com hash_equals. Um valor ausente ou diferente é recusado antes da gravação. Essa conferência ajuda a impedir que outra página envie um formulário usando a sessão deste navegador, situação conhecida como CSRF.
O token não é senha, login ou permissão para publicar o livro de visitas. Ele acrescenta uma proteção específica a este formulário. Toda a implementação está fornecida; acompanhe a criação, o campo e a conferência como três lugares que precisam combinar.
Faça no seu exemplo
Confira o campo oculto no formulário e a condição no início do tratamento POST.
<input type="hidden" name="token" value="<?= escapar($_SESSION["token"]) ?>">
Mude uma coisa e preveja
Numa cópia, retire só o campo oculto, mantendo os demais campos fictícios válidos.
Anote o que você acha que vai acontecer antes de salvar e recarregar.
Recarregue e confira
Envie. O programa deve informar que o formulário precisa ser recarregado e não acrescentar registro. Restaure o campo e refaça a abertura antes de testar sucesso.
Se não acontecer
Se gravar sem token, confira a condição antes da escrita. Não aceite simplesmente qualquer texto no campo token.
Recuse falhas e arquivos quebrados
Uma operação no disco pode falhar. A pasta pode não existir, o arquivo pode estar inacessível ou um registro JSON pode ter sido alterado incorretamente. Nesses casos, a resposta precisa explicar que não concluiu a tarefa.
O código usa try e catch para tratar falhas previstas, chamadas exceções. Ele mostra uma orientação curta na página e deixa o arquivo original disponível para você conferir no editor. Não apaga registros inválidos para parecer que a lista está limpa.
Antes de qualquer experiência que altere dados, use uma cópia separada e encerre o servidor. Guarde a referência original. Não provoque falhas mexendo nas permissões gerais do computador.
Faça no seu exemplo
Numa cópia de teste com o servidor encerrado, faça uma cópia de entradas.jsonl. Acrescente uma linha com o texto isto não é JSON.
Mude uma coisa e preveja
Abra novamente a cópia de teste e preveja se a página deve fingir que a lista está normal.
Anote o que você acha que vai acontecer antes de salvar e recarregar.
Recarregue e confira
A página deve avisar que não conseguiu ler os registros e orientar conferir dados. Um envio não deve acrescentar dados a esse arquivo quebrado. Restaure a cópia do arquivo e confira a leitura.
Se não acontecer
Se a linha for ignorada silenciosamente, confira a função ler_visitas da referência. Se perder o arquivo, recupere a cópia em vez de criar visitas substitutas.
Mantenha o recorte de um exercício local
A etapa limita o arquivo de visitas a 512 KiB antes de aceitar outra escrita. Esse limite ajuda a evitar crescimento sem fim durante a prática. Ele não substitui limites de pedido HTTP, antispam ou um armazenamento próprio para muitos acessos simultâneos.
O servidor embutido é ferramenta de desenvolvimento. Não foi projetado para servir um mural público. O projeto também não inclui contas, moderação ou cópia automática dos dados.
Você conclui com um livro de visitas fictício funcional no computador. Se quiser transformar a ideia em serviço público, isso exige outro projeto com ambiente, política de dados e controles adequados. O próximo volume confere a entrega local que realmente construímos.
Faça no seu exemplo
Confira a constante ou condição de tamanho do arquivo antes da escrita. O valor completo está no index.php.
if (is_file($arquivo) && filesize($arquivo) > 512 * 1024) {
throw new RuntimeException("O arquivo de prática atingiu o limite.");
}
Mude uma coisa e preveja
Numa cópia, reduza o limite para 10 bytes e tente outra visita após existir um registro.
Anote o que você acha que vai acontecer antes de salvar e recarregar.
Recarregue e confira
O programa deve recusar outra escrita e explicar o limite. Restaure 512 * 1024 depois do teste. A visita anterior permanece no arquivo.
Se não acontecer
Se confirmar sem gravar, confira a mensagem de sucesso somente após a escrita e o tratamento da exceção. Não apague o arquivo para fingir que o teste passou.

A captura mostra um trecho da referência concluída. Abra e percorra o exemplo para conferir o restante. Experimentos temporários podem produzir outra aparência; restaure os valores indicados antes de comparar.
Atividades para fazer antes das respostas
Use uma nova cópia do exemplo. Tente aplicar o que aprendeu antes de consultar os comentários.
-
Retire o token numa cópia e confira a recusa.
-
Provoque uma linha JSON inválida somente numa cópia.
-
Teste um limite pequeno de arquivo e restaure o original.
Respostas comentadas
Compare sua explicação e o que realmente aconteceu. Outra solução pode funcionar se cumprir o objetivo e preservar o acesso ao conteúdo.
-
O POST sem token deve ser recusado antes de gravar. Depois restaure e recarregue o formulário.
-
O programa informa a falha e conserva o arquivo para investigação. Recupere a cópia boa.
-
A recusa não apaga a visita anterior. A mensagem de sucesso não aparece se não houve escrita.
Quando seguir
Você vê sucesso somente quando a tarefa foi concluída e consegue recuperar uma cópia após os testes. Se ainda precisa de ajuda, repita a experiência pequena antes de avançar.
Consulta opcional
Não é preciso ler os links externos para executar os arquivos fornecidos. Eles servem para consultar detalhes quando você quiser.
O arquivo completo
Referência do exemplo 19-falhas-protecao, reproduzida aqui para que você possa conferir sem sair da página.
public/estilos.css
* { box-sizing: border-box; }
body { margin: 0; background: #f4f7fb; color: #203246;
font: 1.05rem/1.65 Arial, sans-serif; }
header, main, footer { max-width: 48rem; margin-inline: auto; padding: 1.5rem; }
header { padding-top: 2.5rem; }
h1 { font-size: clamp(1.8rem, 5vw, 2.7rem); line-height: 1.2; }
main { background: #fff; border-radius: .8rem; }
article { border-top: 1px solid #cad4df; padding-block: 1rem; }
a { color: #124e77; text-underline-offset: .2em; }
nav { display: flex; flex-wrap: wrap; gap: 1rem; }
label { display: block; font-weight: bold; }
input:not([type=hidden]), textarea { width: 100%; max-width: 34rem;
border: 2px solid #718095; border-radius: .35rem; padding: .7rem;
font: inherit; color: inherit; background: #fff; }
button { font: inherit; background: #124e77; color: white;
border: 0; border-radius: .35rem; padding: .75rem 1.2rem; cursor: pointer; }
button:hover { background: #103b59; }
.mensagem { white-space: pre-wrap; overflow-wrap: anywhere; }
.erros { color: #8b142c; }
.ok { color: #175c30; font-weight: bold; }
:focus-visible { outline: 3px solid #b04d00; outline-offset: 3px; }
.pular { position: absolute; left: -10000px; }
.pular:focus { position: static; display: block; padding: 1rem; background: white; }
@media (max-width: 35rem) { header,main,footer { padding: 1rem; } }