PHP Boas Práticas
Um programa pode funcionar hoje e ainda assim ser um problema amanhã: difícil de ler, fácil de quebrar, aberto a ataques. As boas práticas são hábitos que os programadores PHP foram juntando ao longo dos anos para evitar esses problemas. Esta aula é um checklist comentado: cada item diz o que fazer, por que, e onde ele foi ensinado no guia. No fim, você vai ver um código da lojinha da Ana sendo refatorado (reescrito sem mudar o resultado), uma mudança por vez.
- Os 12 hábitos que separam um script "que funciona" de um código profissional.
- Como
declare(strict_types=1)e os tipos fazem o erro aparecer cedo, na linha certa. - Por que comparar com
===, escapar a saída e usar prepared statements sempre, sem exceção. - Como esconder os erros do visitante em produção sem deixar de vê-los.
- O que são funções pequenas, nomes claros e o estilo PSR-12.
- Como refatorar um código antigo passo a passo, conferindo que a saída continua a mesma.
Esta é a última aula do Nível 3. Quase tudo aqui já apareceu em alguma aula anterior; a novidade é ver tudo junto, como uma lista que você pode conferir antes de entregar qualquer código.
A ideia: a cozinha organizada
Pense numa cozinha de restaurante. Dá para cozinhar com a pia cheia, as facas misturadas e os temperos sem rótulo, e às vezes o prato até sai bom. Mas basta um dia corrido para alguém usar sal no lugar de açúcar. Por isso as cozinhas profissionais têm regras simples: cada coisa no seu lugar, tudo com etiqueta, lavar as mãos antes de começar, provar antes de servir.
Com código é igual. As regras desta aula não deixam o programa "mais rápido" nem "mais bonito" por si só. Elas fazem três coisas:
- O erro aparece cedo (no seu computador, e não no do cliente).
- O ataque não passa (XSS, SQL injection, senhas vazadas).
- Outra pessoa entende o código, e essa outra pessoa costuma ser você mesmo, daqui a seis meses.
O checklist
Use esta tabela como resumo. Cada linha é explicada logo abaixo, com exemplo e saída real.
| # | Hábito | Evita | Aula |
|---|---|---|---|
| 1 | declare(strict_types=1); no topo de todo arquivo | Conversões silenciosas ("21" virando 21) | Tipos e Retorno |
| 2 | Tipos em parâmetros, retornos e propriedades | Valor errado viajando pelo programa | Tipos Avançados |
| 3 | Comparar com === e !== | "1" == "01" ser verdadeiro | Comparação |
| 4 | Escapar toda saída com htmlspecialchars | XSS (script de outra pessoa na sua página) | Escapar a Saída |
| 5 | Prepared statements no banco | SQL injection | Prepared Statements |
| 6 | password_hash / password_verify | Senhas legíveis num vazamento | Senhas |
| 7 | Não mostrar erros em produção (mas registrá-los) | Mostrar caminhos e detalhes internos ao visitante | Tipos de Erro |
| 8 | Composer + autoload PSR-4 | Pilhas de require e bibliotecas copiadas à mão | Composer, Autoload |
| 9 | Funções pequenas, cada uma com uma tarefa | Funções de 200 linhas impossíveis de testar | Funções |
| 10 | Nomes claros, sem "números mágicos" | $t, $f, 0.1 sem explicação | Constantes |
| 11 | Estilo PSR-12 | Cada arquivo num formato diferente | Padrões PSR |
| 12 | Testes automatizados | "Arrumei uma coisa e quebrei outra" | PHPUnit |
1. declare(strict_types=1): tipos levados a sério
Por padrão, o PHP é "bonzinho": se uma função pede um int e recebe o texto "21", ele converte sozinho e segue em frente. Parece ajuda, mas esconde erros. A linha declare(strict_types=1); (em português, "declarar tipos estritos = ligado") desliga essa conversão no arquivo onde ela aparece: tipo errado vira erro na hora.
<?php
declare(strict_types=1);
function dobro(int $n): int
{
return $n * 2;
}
echo dobro(21), "\n";
echo dobro("21"), "\n";
- declare(strict_types=1);
- Precisa ser a primeira instrução do arquivo (logo depois do
<?php). Vale só para as chamadas feitas dentro deste arquivo. - int $n
- O parâmetro aceita só números inteiros.
- dobro("21")
- Um texto, mesmo que pareça número. Com tipos estritos, não é aceito.
$ php strict.php
42
Fatal error: Uncaught TypeError: dobro(): Argument #1 ($n) must be of type int, string given, called in /home/ana/loja/strict.php on line 10 and defined in /home/ana/loja/strict.php:4
Stack trace:
#0 /home/ana/loja/strict.php(10): dobro('21')
#1 {main}
thrown in /home/ana/loja/strict.php on line 4
A primeira chamada funcionou. A segunda parou o programa com um TypeError ("erro de tipo"): "o argumento nº 1 ($n) precisa ser do tipo int, foi passada uma string, chamada na linha 10". Sem a linha do declare, o mesmo arquivo mostra:
$ php sem-strict.php 42 42
Nenhum aviso. Neste exemplo pequeno, tanto faz. Num sistema de verdade, aquele "21" pode ter vindo de um formulário com "21 unidades" ou "vinte e um", e você só descobriria lá na frente, com um total errado. Regra: todo arquivo novo começa com <?php, uma linha em branco e declare(strict_types=1);.
$_GET e $_POST é texto. Com tipos estritos você converte de propósito, num lugar só, depois de validar: $qtd = filter_var($_POST['qtd'], FILTER_VALIDATE_INT); (veja Validar Dados). Assim a conversão fica visível, e não escondida.2. Tipos em tudo: parâmetros, retornos e propriedades
Escreva o tipo de cada parâmetro, de cada retorno e de cada propriedade. Os tipos são uma documentação que o PHP confere: quem lê function frete(float $valor): float já sabe o que entra e o que sai, sem precisar ler o corpo.
| Em vez de… | Escreva… | Por quê |
|---|---|---|
function total($itens) | function total(array $itens): float | Diz o que entra e o que sai. |
public $preco; | public float $preco; | A propriedade nunca recebe um texto por engano. |
function buscar($id) que às vezes devolve false | function buscar(int $id): ?Produto | ?Produto = "um Produto ou null". Deixa claro que pode não achar. |
| Uma função que só imprime | function mostrar(): void | void = "não devolve nada". |
3. Comparar com ===
O == ("igual") converte os dois lados antes de comparar, e as regras de conversão surpreendem. O === ("idêntico") exige mesmo valor e mesmo tipo. Rode e veja:
Experimente: cada linha mostra bool(true) ou bool(false). Antes de clicar em Rodar, tente adivinhar. Depois troque == por === nas quatro primeiras e rode de novo.
<?php
var_dump("1" == "01");
var_dump("10" == "1e1");
var_dump(100 == "1e2");
var_dump(null == false);
var_dump(0 == "");
var_dump("abc" == 0);
var_dump("1" === "01");
var_dump(null === false);
$ php compara.php bool(true) bool(true) bool(true) bool(true) bool(false) bool(false) bool(false) bool(false)
As quatro primeiras dão true porque, com ==, dois textos que parecem números são comparados como números ("01" vira 1, "1e1" é 1 × 10¹ = 10) e null vira false. Imagine um cupom de desconto "10" aceitando o código "010". As duas últimas, com ===, fazem o que qualquer pessoa espera. Regra: use === e !== sempre; o == só quando você quiser a conversão e souber explicar por quê. O mesmo vale para in_array($x, $lista, true) e array_search($x, $lista, true): o true no fim liga a comparação estrita.
4. Escapar a saída
Tudo o que veio de fora (formulário, banco, URL, arquivo) e vai para o HTML passa por htmlspecialchars, que troca os caracteres especiais do HTML por códigos inofensivos:
<?php
$comentario = '<script>alert("oi")</script>';
echo htmlspecialchars($comentario), "\n";
$ php esc.php <script>alert("oi")</script>
O navegador mostra o texto <script>… na tela, em vez de executar o script. A regra é escapar na hora de mostrar (não na hora de guardar), em todo echo de dado externo. Muita gente cria um atalho, function e(string $s): string { return htmlspecialchars($s); }, para não ter preguiça de escrever. Detalhes em Escapar a Saída (XSS).
5. Prepared statements, sempre
Nunca monte SQL juntando texto com dados do usuário. Use prepare e passe os valores separados:
// ERRADO: o nome entra no meio do SQL (SQL injection)
$sql = "SELECT * FROM clientes WHERE nome = '$nome'";
$linhas = $pdo->query($sql)->fetchAll();
// CERTO: o SQL tem um "buraco" (?) e o valor vai separado
$consulta = $pdo->prepare('SELECT * FROM clientes WHERE nome = ?');
$consulta->execute([$nome]);
$linhas = $consulta->fetchAll();
Com o jeito errado, um "nome" como x' OR '1'='1 devolve todos os clientes. Com o certo, ele é só um nome estranho que não existe. Não há exceção para "esse dado é confiável": o hábito de sempre usar prepare é o que protege. Veja a demonstração completa em Prepared Statements e SQL Injection.
6. Senhas com password_hash
Senha nunca é guardada como texto, nem com md5 ou sha1. Use password_hash($senha, PASSWORD_DEFAULT) para guardar e password_verify($senha, $hash) para conferir. A aula Senhas mostra o hash real e explica por que md5 não serve.
7. Erros: mostrar no seu computador, esconder em produção
Enquanto você programa, quer ver todo aviso. No site no ar (produção), as mensagens de erro entregam detalhes internos ao visitante. Veja esta página com um erro bobo, servida pelo servidor embutido:
<?php echo "<p>Olá, $nome!</p>\n"; echo "<p>Bem-vinda à lojinha.</p>\n";
Com o servidor ligado em outro terminal (php -S localhost:8000, dentro da pasta loja), a Ana pede a página com o curl, que mostra o HTML exatamente como o navegador recebe:
$ curl -s localhost:8000/ <br /> <b>Warning</b>: Undefined variable $nome in <b>/home/ana/loja/index.php</b> on line <b>2</b><br /> <p>Olá, !</p> <p>Bem-vinda à lojinha.</p>
Qualquer visitante agora sabe que o site fica em /home/ana/loja e que o arquivo se chama index.php. Em erros de banco, a mensagem pode mostrar até o nome do banco e do usuário. A configuração de produção é: não mostrar (display_errors=0) e registrar num arquivo (log_errors=1 + error_log). Desligue o servidor (Ctrl+C) e ligue de novo com essas opções (o -d muda uma configuração só nesta execução):
$ php -S localhost:8000 -d display_errors=0 -d log_errors=1 -d error_log=/home/ana/loja/erros.log [Fri Oct 16 21:01:44 2026] PHP 8.5.8 Development Server (http://localhost:8000) started
E no outro terminal:
$ curl -s localhost:8000/ <p>Olá, !</p> <p>Bem-vinda à lojinha.</p> $ cat erros.log [17-Oct-2026 00:01:45 UTC] PHP Warning: Undefined variable $nome in /home/ana/loja/index.php on line 2
O visitante não vê nada de interno; você encontra o aviso completo, com data e hora, no erros.log. No servidor de verdade, essas linhas ficam no php.ini de produção:
display_errors = Off log_errors = On error_log = /var/log/php/erros.log error_reporting = E_ALL
Repare no error_reporting = E_ALL: em produção você continua registrando todos os erros; só deixa de mostrá-los. E nunca "resolva" um aviso escondendo-o com @ (como em @$x): conserte a causa. Mais em Tipos de Erro e Depurar.
8. Composer e autoload PSR-4
Em vez de uma pilha de require 'classes/Produto.php'; no topo de cada arquivo, deixe o Composer carregar as classes sozinho: uma classe por arquivo, o namespace espelhando as pastas (Loja\Carrinho em src/Carrinho.php) e um único require __DIR__ . '/vendor/autoload.php';. Bibliotecas de terceiros também vêm pelo Composer (composer require …), nunca copiadas à mão: assim dá para atualizar com um comando e o arquivo composer.lock garante a mesma versão em todos os computadores. Veja Composer e Autoload e PSR-4.
9. Funções pequenas, cada uma com uma tarefa
Uma função boa faz uma coisa e dá para descrever o que ela faz numa frase curta, sem "e". "Calcula o frete" é uma boa função. "Calcula o total, aplica o cupom, decide o frete e imprime" são quatro funções escondidas numa. Funções pequenas são fáceis de ler, de reaproveitar e, principalmente, de testar: dá para conferir o frete sem montar um pedido inteiro.
Um sinal de alerta: uma função que calcula e imprime ao mesmo tempo. Separe: uma função calcula e return; quem chama decide se imprime, salva ou envia como JSON.
10. Nomes claros, sem números mágicos
O nome deve dizer o que a coisa é. $t não diz nada; $total diz. calc() não diz nada; calcularFrete() diz. Funções e métodos costumam começar com um verbo (calcular, buscar, formatar); variáveis e classes são substantivos ($carrinho, Produto). Escolha um idioma e mantenha (no guia, português).
Número mágico é um número solto no código cujo significado ninguém sabe: por que 200? por que 15? Dê um nome a ele com uma constante: const FRETE_GRATIS_ACIMA_DE = 200.0;. Além de explicar, quando o valor mudar você altera em um lugar só.
11. Estilo PSR-12
A PSR-12 é o guia de estilo que a comunidade PHP combinou (veja Padrões PSR). As regras que mais aparecem:
- Indentação com 4 espaços (não tab).
- A
{de classes e funções vai na linha de baixo; a deif,foreach,whilefica na mesma linha. ifsempre com chaves, mesmo com uma linha só (if ($x) return;não).- Um espaço depois de
if,foreache em volta dos operadores:if ($total > 200) {. - Arquivo só com PHP: sem o
?>no fim (evita espaços em branco extras enviados ao navegador). - Classes em
PascalCase(CarrinhoDeCompras), métodos e variáveis emcamelCase(calcularFrete), constantes emMAIUSCULAS_COM_SUBLINHADO.
Você não precisa decorar: ferramentas como o PHP-CS-Fixer e o PHP_CodeSniffer (instaladas com o Composer) conferem e até corrigem o estilo sozinhas. O importante é que o projeto inteiro siga o mesmo padrão.
12. Testes automatizados
Um teste é um pequeno programa que confere se o seu código faz o que deveria: "um carrinho novo tem 0 itens", "com o cupom PROMO10, R$ 90,00 vira R$ 81,00". Você roda todos com um comando antes de publicar qualquer mudança. Se algo quebrou, o teste avisa antes do cliente. A ferramenta padrão do PHP para isso é o PHPUnit, ensinado em Testes com PHPUnit.
Antes × depois: refatorando o pedido da lojinha
Refatorar é reescrever o código para ficar melhor sem mudar o que ele faz. A Ana escreveu, no começo do curso, este script que calcula o total de um pedido:
<?php
function calc($p, $c) {
$t = 0;
foreach ($p as $i) {
$t = $t + $i[1] * $i[2];
}
if ($c == "PROMO10") {
$t = $t - $t * 0.1;
}
if ($t > 200) {
$f = 0;
} else {
$f = 15;
}
echo "Total: R$ " . number_format($t + $f, 2, ",", ".") . "\n";
}
calc([["Caderno", 25.5, 2], ["Caneta", "3.9", 10]], "PROMO10");
$ php pedido-antes.php Total: R$ 96,00
Funciona: 2 cadernos (R$ 51,00) + 10 canetas (R$ 39,00) = R$ 90,00; com 10% de desconto, R$ 81,00; mais R$ 15,00 de frete, R$ 96,00. Mas passe pelo checklist e conte os problemas:
| Linha | Problema | Item |
|---|---|---|
| 1 | Sem declare(strict_types=1). | 1 |
| 2 | calc, $p, $c: nomes que não dizem nada. Nenhum tipo. | 2, 10 |
| 5 | $i[1] e $i[2]: quem lembra que 1 é o preço e 2 a quantidade? | 10 |
| 7 | == em vez de ===. | 3 |
| 8, 10, 13 | Números mágicos: 0.1, 200, 15. | 10 |
| 2–16 | Uma função faz quatro coisas (soma, desconto, frete, imprime). Não dá para testar o frete sozinho. | 9, 12 |
| 2 | A { da função na mesma linha (PSR-12 pede na de baixo). | 11 |
| 18 | O preço da caneta é o texto "3.9", e ninguém percebe. | 1, 2 |
O último problema parece inofensivo, porque "3.9" ainda é convertido certinho. Mas veja o que acontece se o preço vier de um formulário brasileiro, com vírgula ("3,9"):
$ php pedido-antes.php Warning: A non-numeric value encountered in /home/ana/loja/pedido-antes.php on line 5 Total: R$ 87,90
Um aviso que ninguém lê em produção (item 7) e um total errado: o PHP usou só o 3 antes da vírgula. A lojinha cobraria R$ 87,90 em vez de R$ 96,00, e o programa "funcionou".
O mesmo pedido, refatorado
<?php
declare(strict_types=1);
const DESCONTO_PROMO10 = 0.10;
const FRETE_PADRAO = 15.0;
const FRETE_GRATIS_ACIMA_DE = 200.0;
final readonly class Item
{
public function __construct(
public string $nome,
public float $preco,
public int $quantidade,
) {
}
}
/** @param list<Item> $itens */
function subtotal(array $itens): float
{
$soma = 0.0;
foreach ($itens as $item) {
$soma += $item->preco * $item->quantidade;
}
return $soma;
}
function aplicarCupom(float $valor, ?string $cupom): float
{
if ($cupom === 'PROMO10') {
return $valor * (1 - DESCONTO_PROMO10);
}
return $valor;
}
function calcularFrete(float $valor): float
{
return $valor > FRETE_GRATIS_ACIMA_DE ? 0.0 : FRETE_PADRAO;
}
function formatarReais(float $valor): string
{
return 'R$ ' . number_format($valor, 2, ',', '.');
}
$itens = [
new Item('Caderno', 25.5, 2),
new Item('Caneta', 3.9, 10),
];
$comDesconto = aplicarCupom(subtotal($itens), 'PROMO10');
$total = $comDesconto + calcularFrete($comDesconto);
echo 'Total: ' . formatarReais($total) . "\n";
$ php pedido.php Total: R$ 96,00
O mesmo resultado, que é a primeira regra de uma refatoração. O código ficou maior, e isso é normal: cada linha extra é uma explicação que antes só existia na cabeça da Ana. Mudança por mudança:
- declare(strict_types=1);
- Item 1. Tipos estritos no arquivo inteiro.
- const DESCONTO_PROMO10 = 0.10; …
- Item 10. Os números mágicos ganharam nome. Se o frete subir para R$ 18,00, muda uma linha.
- final readonly class Item
- Itens 2 e 10. No lugar de
["Caneta", "3.9", 10](posições sem nome), um objeto comnome,precoequantidade, cada um com seu tipo.readonly(PHP 8.2+) impede que o item mude depois de criado; veja readonly. O construtor usa a promoção de propriedades de Construtores. - /** @param list<Item> $itens */
- Um comentário de documentação: o PHP só sabe que é um
array; o comentário diz a quem lê (e ao editor) que é uma lista deItem. - subtotal(), aplicarCupom(), calcularFrete(), formatarReais()
- Item 9. Uma função por tarefa, cada uma com nome de verbo (item 10) e tipos (item 2). Nenhuma imprime nada: todas devolvem um valor.
- ?string $cupom
- O cupom pode ser um texto ou
null(sem cupom). - $cupom === 'PROMO10'
- Item 3. Comparação estrita.
- { na linha de baixo, 4 espaços, aspas simples
- Item 11. Estilo PSR-12. Aspas simples onde não há variável dentro do texto é um costume comum (não é regra da PSR-12).
- echo só no fim
- Item 9. Calcular e mostrar ficaram separados. Agora dá para testar
calcularFrete(201.0)sozinho (item 12) ou mostrar o total numa página HTML ou numa API JSON sem mudar nenhuma função.
E o preço como texto? Troque 3.9 por "3.9" na linha 49 e rode:
$ php pedido.php
Fatal error: Uncaught TypeError: Item::__construct(): Argument #2 ($preco) must be of type float, string given, called in /home/ana/loja/pedido.php on line 49 and defined in /home/ana/loja/pedido.php:13
Stack trace:
#0 /home/ana/loja/pedido.php(49): Item->__construct('Caneta', '3.9', 10)
#1 {main}
thrown in /home/ana/loja/pedido.php on line 13
Agora o erro aparece na hora, apontando a linha 49 (onde o item foi criado) e dizendo qual argumento estava errado: o 2º, $preco. É isto que "o erro aparece cedo" quer dizer: em vez de um total errado no fim do mês, uma mensagem clara no primeiro teste.
Missão: este é o pedido refatorado. Rode uma vez e confira o Total: R$ 96,00. Depois provoque o erro: troque o preço da caneta, 3.9, pelo texto "3.9" (entre aspas) e rode de novo. Para comparar, apague também a linha do declare e veja o erro sumir.
<?php
declare(strict_types=1);
const DESCONTO_PROMO10 = 0.10;
const FRETE_PADRAO = 15.0;
const FRETE_GRATIS_ACIMA_DE = 200.0;
final readonly class Item
{
public function __construct(
public string $nome,
public float $preco,
public int $quantidade,
) {
}
}
/** @param list<Item> $itens */
function subtotal(array $itens): float
{
$soma = 0.0;
foreach ($itens as $item) {
$soma += $item->preco * $item->quantidade;
}
return $soma;
}
function aplicarCupom(float $valor, ?string $cupom): float
{
if ($cupom === 'PROMO10') {
return $valor * (1 - DESCONTO_PROMO10);
}
return $valor;
}
function calcularFrete(float $valor): float
{
return $valor > FRETE_GRATIS_ACIMA_DE ? 0.0 : FRETE_PADRAO;
}
function formatarReais(float $valor): string
{
return 'R$ ' . number_format($valor, 2, ',', '.');
}
$itens = [
new Item('Caderno', 25.5, 2),
new Item('Caneta', 3.9, 10),
];
$comDesconto = aplicarCupom(subtotal($itens), 'PROMO10');
$total = $comDesconto + calcularFrete($comDesconto);
echo 'Total: ' . formatarReais($total) . "\n";
<?php
declare(strict_types=1);
const DESCONTO_PROMO10 = 0.10;
const FRETE_PADRAO = 15.0;
const FRETE_GRATIS_ACIMA_DE = 200.0;
final readonly class Item
{
public function __construct(
public string $nome,
public float $preco,
public int $quantidade,
) {
}
}
/** @param list<Item> $itens */
function subtotal(array $itens): float
{
$soma = 0.0;
foreach ($itens as $item) {
$soma += $item->preco * $item->quantidade;
}
return $soma;
}
function aplicarCupom(float $valor, ?string $cupom): float
{
if ($cupom === 'PROMO10') {
return $valor * (1 - DESCONTO_PROMO10);
}
return $valor;
}
function calcularFrete(float $valor): float
{
return $valor > FRETE_GRATIS_ACIMA_DE ? 0.0 : FRETE_PADRAO;
}
function formatarReais(float $valor): string
{
return 'R$ ' . number_format($valor, 2, ',', '.');
}
$itens = [
new Item('Caderno', 25.5, 2),
new Item('Caneta', "3.9", 10),
];
$comDesconto = aplicarCupom(subtotal($itens), 'PROMO10');
$total = $comDesconto + calcularFrete($comDesconto);
echo 'Total: ' . formatarReais($total) . "\n";
Como refatorar sem quebrar
- Anote a saída atual (aqui,
Total: R$ 96,00). Melhor ainda: escreva um teste que confira essa saída. - Faça uma mudança pequena (por exemplo, só renomear
$tpara$total). - Rode de novo e compare. Mudou? Desfaça e tente menor.
- Repita até o checklist ficar verde.
Não misture refatorar com mudar o comportamento. Se além de organizar você quiser mudar a regra do frete, faça isso depois, numa mudança separada. Assim, se o total mudar, você sabe exatamente por quê.
Quando dá errado
<?php echo "Início\n"; declare(strict_types=1);
$ php erro.php
Fatal error: strict_types declaration must be the very first statement in the script in /home/ana/loja/erro.php on line 3
Stack trace:
#0 {main}
O que aconteceu: o declare veio depois de outra instrução. Ele precisa ser a primeira coisa do arquivo (comentários e linhas em branco antes dele podem). Repare que nem o echo "Início" rodou: o PHP confere o arquivo inteiro antes de executar a primeira linha.
Como consertar: mova o declare(strict_types=1); para logo depois do <?php.
strict_types e apareceram vários TypeError
O que aconteceu: o código passava tipos errados o tempo todo e o PHP convertia em silêncio. O declare não criou os problemas: só os mostrou.
Como consertar: leia cada mensagem (ela diz a função, o número do argumento, o tipo esperado e o recebido) e converta o valor de propósito no lugar onde ele entra no sistema (por exemplo, (int) ou filter_var logo depois de ler o formulário). Num projeto grande, ligue arquivo por arquivo.
O que aconteceu: com display_errors=0, um erro fatal deixa a página vazia (ou com o código 500) e nada aparece na tela. Isso é o esperado em produção.
Como consertar: olhe o arquivo configurado em error_log (como o erros.log acima). No seu computador, deixe display_errors=1. Se não sabe onde está o log, php -i | grep error_log mostra a configuração.
O que aconteceu: no caminho, alguma regra mudou sem querer. Exemplo comum: trocar if ($t > 200) por if ($t >= 200), ou aplicar o frete antes do desconto.
Como consertar: volte para a última versão que dava o resultado certo e refaça em passos menores, rodando a cada passo. É para isso que os testes servem: eles conferem dezenas de casos em um segundo.
Referência rápida
| Prática | Como fica no código |
|---|---|
| Tipos estritos | declare(strict_types=1); na 1ª linha depois de <?php |
| Tipos | function f(int $a, ?string $b = null): float, public float $preco; |
| Comparação | ===, !==, in_array($x, $lista, true) |
| Saída no HTML | echo htmlspecialchars($texto); |
| Banco | $st = $pdo->prepare('… WHERE id = ?'); $st->execute([$id]); |
| Senhas | password_hash($senha, PASSWORD_DEFAULT), password_verify($senha, $hash) |
| Erros em produção | display_errors=Off, log_errors=On, error_log=…, error_reporting=E_ALL |
| Dependências | composer require pacote; require __DIR__ . '/vendor/autoload.php'; |
| Nomes | Classes PascalCase, funções e variáveis camelCase, constantes MAIUSCULAS |
| Estilo | PSR-12: 4 espaços, { de classe/função na linha de baixo, sem ?> no fim |
| Testes | vendor/bin/phpunit antes de publicar |
| Conferir a sintaxe sem rodar | php -l arquivo.php → No syntax errors detected in arquivo.php |
Exercícios
1. A primeira linha
Complete a linha que liga os tipos estritos no arquivo:
2. Comparação estrita
Complete com o operador que compara valor e tipo:
3. Escondendo os erros do visitante
Complete a configuração de produção que não mostra os erros na página:
4. O que var_dump("1" == "01"); mostra no PHP 8.5?
5. Qual destas funções segue melhor a ideia de "funções pequenas, uma tarefa"?
6. Por que display_errors deve ficar desligado no site em produção?
7. Você refatorou um código e o resultado mudou. O que isso significa?
Missão final: refatore este código pelo checklist, sem mudar a saída (Olá, Ana! e Olá, visitante!): (1) ligue os tipos estritos; (2) renomeie a função para saudacao e o parâmetro para $nome; (3) dê os tipos string ao parâmetro e ao retorno; (4) troque == por ===; (5) coloque chaves no if. Lembre de mudar também as chamadas lá embaixo.
<?php
function f($n) {
if ($n == "") return "Olá, visitante!";
return "Olá, " . $n . "!";
}
echo f("Ana") . "\n";
echo f("") . "\n";
<?php
declare(strict_types=1);
function saudacao(string $nome): string
{
if ($nome === '') {
return 'Olá, visitante!';
}
return "Olá, $nome!";
}
echo saudacao('Ana') . "\n";
echo saudacao('') . "\n";
Resumo
- Boas práticas fazem o erro aparecer cedo, barram ataques e deixam o código legível para os outros (e para você no futuro).
declare(strict_types=1)+ tipos em tudo: tipo errado viraTypeErrorna linha certa, em vez de um resultado errado lá na frente.===sempre; o==converte e surpreende ("1" == "01"étrue).- Segurança não tem exceção:
htmlspecialcharsna saída, prepared statements no banco,password_hashnas senhas. - Em produção:
display_errors=Offelog_errors=On. O visitante não vê; você lê no log. - Composer + PSR-4 para organizar; PSR-12 para o estilo; funções pequenas com nomes de verbo; constantes no lugar de números mágicos.
- Refatorar = melhorar sem mudar o resultado, um passo pequeno por vez, conferindo a saída (de preferência com testes).