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.

Você vai aprender
  • 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ábitoEvitaAula
1declare(strict_types=1); no topo de todo arquivoConversões silenciosas ("21" virando 21)Tipos e Retorno
2Tipos em parâmetros, retornos e propriedadesValor errado viajando pelo programaTipos Avançados
3Comparar com === e !=="1" == "01" ser verdadeiroComparação
4Escapar toda saída com htmlspecialcharsXSS (script de outra pessoa na sua página)Escapar a Saída
5Prepared statements no bancoSQL injectionPrepared Statements
6password_hash / password_verifySenhas legíveis num vazamentoSenhas
7Não mostrar erros em produção (mas registrá-los)Mostrar caminhos e detalhes internos ao visitanteTipos de Erro
8Composer + autoload PSR-4Pilhas de require e bibliotecas copiadas à mãoComposer, Autoload
9Funções pequenas, cada uma com uma tarefaFunções de 200 linhas impossíveis de testarFunções
10Nomes claros, sem "números mágicos"$t, $f, 0.1 sem explicaçãoConstantes
11Estilo PSR-12Cada arquivo num formato diferentePadrões PSR
12Testes 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);.

E os dados do formulário? Tudo o que chega em $_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): floatDiz 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 falsefunction buscar(int $id): ?Produto?Produto = "um Produto ou null". Deixa claro que pode não achar.
Uma função que só imprimefunction mostrar(): voidvoid = "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
&lt;script&gt;alert(&quot;oi&quot;)&lt;/script&gt;

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 de if, foreach, while fica na mesma linha.
  • if sempre com chaves, mesmo com uma linha só (if ($x) return; não).
  • Um espaço depois de if, foreach e 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 em camelCase (calcularFrete), constantes em MAIUSCULAS_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:

LinhaProblemaItem
1Sem declare(strict_types=1).1
2calc, $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, 13Números mágicos: 0.1, 200, 15.10
2–16Uma função faz quatro coisas (soma, desconto, frete, imprime). Não dá para testar o frete sozinho.9, 12
2A { da função na mesma linha (PSR-12 pede na de baixo).11
18O 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 com nome, preco e quantidade, 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 de Item.
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

  1. Anote a saída atual (aqui, Total: R$ 96,00). Melhor ainda: escreva um teste que confira essa saída.
  2. Faça uma mudança pequena (por exemplo, só renomear $t para $total).
  3. Rode de novo e compare. Mudou? Desfaça e tente menor.
  4. 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

"Fatal error: strict_types declaration must be the very first statement in the script"
<?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.

Liguei o 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.

Escondi os erros e agora a página fica em branco

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.

A refatoração mudou o resultado

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áticaComo fica no código
Tipos estritosdeclare(strict_types=1); na 1ª linha depois de <?php
Tiposfunction f(int $a, ?string $b = null): float, public float $preco;
Comparação===, !==, in_array($x, $lista, true)
Saída no HTMLecho htmlspecialchars($texto);
Banco$st = $pdo->prepare('… WHERE id = ?'); $st->execute([$id]);
Senhaspassword_hash($senha, PASSWORD_DEFAULT), password_verify($senha, $hash)
Erros em produçãodisplay_errors=Off, log_errors=On, error_log=…, error_reporting=E_ALL
Dependênciascomposer require pacote; require __DIR__ . '/vendor/autoload.php';
NomesClasses PascalCase, funções e variáveis camelCase, constantes MAIUSCULAS
EstiloPSR-12: 4 espaços, { de classe/função na linha de baixo, sem ?> no fim
Testesvendor/bin/phpunit antes de publicar
Conferir a sintaxe sem rodarphp -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:

declare();

2. Comparação estrita

Complete com o operador que compara valor e tipo:

if ($cupom 'PROMO10') {

3. Escondendo os erros do visitante

Complete a configuração de produção que não mostra os erros na página:

display_errors =

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 vira TypeError na 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: htmlspecialchars na saída, prepared statements no banco, password_hash nas senhas.
  • Em produção: display_errors=Off e log_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).