PHP do Zero ao Mercado
Capítulo 00 · Introdução

Bem-vindo: PHP e o mercado de trabalho

Você nunca programou? Perfeito — esta apostila foi escrita exatamente para você. Ao final dela, você terá construído código real, entendido banco de dados, APIs e Laravel, e saberá o que empresas brasileiras esperam de um dev PHP júnior.

Por que PHP em 2026?

PHP roda em mais de 70% dos sites com backend conhecido na web — incluindo WordPress, que sozinho movimenta uma economia gigante de agências e freelancers no Brasil. Além disso, o ecossistema moderno (PHP 8+, Composer, Laravel) é competitivo, rápido e muito demandado.

WordPress + LaravelAs duas maiores portas de entrada para vagas PHP no Brasil
PHP 8.xVersão moderna: rápida, tipada e nada parecida com o PHP "antigo"
Júnior → PlenoEsta apostila cobre o que se pede em vagas júnior e prepara o caminho
Radar do mercado Ao longo da apostila você verá caixas como esta. Elas conectam o que você está aprendendo com o que aparece em vagas e entrevistas reais: perguntas frequentes, termos que recrutadores usam e o que colocar no portfólio.

Como usar esta apostila

Instalando o PHP na sua máquina

Você precisa de duas coisas: o PHP (o interpretador da linguagem) e um editor de código. Recomendação: VS Code como editor.

Opção 1 — XAMPP (mais fácil para iniciantes)

  1. Baixe o XAMPP em apachefriends.org e instale.
  2. Ele traz PHP + Apache (servidor web) + MySQL (banco de dados) em um pacote só.
  3. Seus arquivos PHP vão na pasta htdocs e são acessados em http://localhost.

Opção 2 — PHP puro + servidor embutido (mais "profissional")

Instale o PHP pelo site oficial (php.net) ou por um gerenciador de pacotes, e use o servidor de desenvolvimento que já vem com ele:

terminal
$ php -v          # confere a versão instalada (use 8.2 ou superior)
$ php -S localhost:8000   # inicia um servidor na pasta atual

Seu primeiro programa

Crie um arquivo chamado index.php com o conteúdo abaixo, rode php -S localhost:8000 na mesma pasta e abra http://localhost:8000 no navegador:

index.php
<?php

echo "Olá, mundo! Meu primeiro código PHP.";
Entendendo Todo código PHP vive dentro da tag <?php. O comando echo imprime algo na tela. O ponto e vírgula ; encerra cada instrução — esquecê-lo é o erro nº 1 de iniciantes.

Exercícios

Faça antes de olhar a resposta. Errar aqui é aprender de graça.

0.1Instale o PHP (XAMPP ou puro) e rode php -v no terminal. Qual versão apareceu?

Qualquer versão 8.x está ótima (ex.: PHP 8.3.7). Se apareceu 7.x ou "comando não encontrado", reinstale — versões antigas não servem para o mercado atual.

0.2Altere o index.php para imprimir seu nome e sua meta (ex.: "Sou a Ana e serei dev PHP em 2026").
<?php

echo "Sou a Ana e serei dev PHP em 2026!";
Capítulo 01 · Fundamentos

Sintaxe, variáveis e tipos de dados

Aqui você aprende o alfabeto da linguagem: como guardar informações, que tipos de dados existem e como o PHP lida com eles.

Variáveis

Uma variável é uma caixinha com nome onde você guarda um valor. Em PHP, toda variável começa com $:

<?php

$nome = "Maria";
$idade = 28;
$salarioDesejado = 4500.50;
$empregada = false;

echo "Nome: $nome, idade: $idade";
Interpolação Dentro de aspas duplas, o PHP substitui $nome pelo valor. Dentro de aspas simples, não: 'Nome: $nome' imprime literalmente Nome: $nome.

Tipos de dados essenciais

TipoExemploPara que serve
string"Olá"Textos
int42Números inteiros
float3.14Números com decimais
booltrue / falseVerdadeiro ou falso
array[1, 2, 3]Listas e coleções (cap. 3)
nullnullAusência de valor

Use var_dump($variavel) para inspecionar o tipo e o valor de qualquer coisa — é a ferramenta de depuração mais usada do dia a dia.

Constantes

<?php

const SALARIO_MINIMO = 1518.00; // não pode ser alterada depois
define('EMPRESA', 'TechBR');    // forma alternativa

echo SALARIO_MINIMO;

Operadores

<?php

// Aritméticos
$soma = 10 + 5;      // 15
$resto = 10 % 3;     // 1  (resto da divisão — cai em entrevista!)
$potencia = 2 ** 10; // 1024

// Concatenação de strings usa PONTO
$nome = "Ana";
$frase = "Olá, " . $nome . "!";

// Atribuição composta
$pontos = 10;
$pontos += 5;  // agora vale 15
$pontos .= "" ; // também existe para strings

// Comparação
var_dump(5 == "5");   // true  — compara só o VALOR
var_dump(5 === "5");  // false — compara valor E tipo
Radar do mercado A diferença entre == e === é uma pergunta clássica de entrevista PHP. Resposta curta: == compara valores após conversão de tipo (5 e "5" são iguais); === exige valor e tipo idênticos. No código profissional, use sempre === para evitar bugs de conversão implícita.

Comentários

<?php

// Comentário de uma linha

/*
 * Comentário de
 * várias linhas
 */

$x = 1; # também funciona, mas é raro

Exercícios

Crie um arquivo cap1.php e rode com php cap1.php no terminal.

1.1Crie variáveis para nome, idade e cidade, e imprima a frase: "Ana tem 28 anos e mora em Recife" usando interpolação.
<?php

$nome = "Ana";
$idade = 28;
$cidade = "Recife";

echo "$nome tem $idade anos e mora em $cidade";
1.2Sem rodar, responda: o que imprime var_dump(0 == "a"); no PHP 8? E var_dump("1" == "01");?

0 == "a" é false no PHP 8 (no PHP 7 era true — mudança importante!). "1" == "01" é true, pois strings numéricas são comparadas como números. Mais um motivo para usar ===.

1.3Calcule quanto sobra de 100 ingressos distribuídos igualmente entre 7 escolas, usando o operador certo.
<?php

echo 100 % 7; // 2 ingressos sobram
Capítulo 02 · Fundamentos

Controle de fluxo: decisões e repetições

Programas úteis tomam decisões ("se o usuário é admin, mostre o painel") e repetem tarefas ("para cada produto, calcule o frete"). É isso que você domina agora.

if / elseif / else

<?php

$nota = 7.5;

if ($nota >= 7) {
    echo "Aprovado!";
} elseif ($nota >= 5) {
    echo "Recuperação";
} else {
    echo "Reprovado";
}

Operador ternário e null coalescing

<?php

$idade = 20;
$status = ($idade >= 18) ? "maior" : "menor";

// Null coalescing (??): usa o valor da direita se o da esquerda for null
$apelido = $usuario['apelido'] ?? "visitante";
Radar do mercado O operador ?? aparece o tempo todo em código de produção para lidar com dados que podem não existir (ex.: campos opcionais de formulário). Saber usá-lo com naturalidade sinaliza familiaridade com PHP moderno.

switch e match

<?php

$perfil = "editor";

// switch: o clássico
switch ($perfil) {
    case "admin":
        echo "Acesso total";
        break;
    case "editor":
        echo "Pode editar conteúdo";
        break;
    default:
        echo "Somente leitura";
}

// match (PHP 8+): mais moderno, retorna valor e compara com ===
$permissao = match ($perfil) {
    "admin"  => "Acesso total",
    "editor" => "Pode editar conteúdo",
    default  => "Somente leitura",
};
echo $permissao;

Laços de repetição

<?php

// while: repete ENQUANTO a condição for verdadeira
$i = 1;
while ($i <= 5) {
    echo "Linha $i\n";
    $i++;
}

// for: quando você sabe quantas vezes repetir
for ($i = 0; $i < 10; $i++) {
    echo $i . " ";
}

// foreach: o mais usado no dia a dia — percorre arrays
$frutas = ["maçã", "banana", "uva"];
foreach ($frutas as $fruta) {
    echo "Fruta: $fruta\n";
}

// break interrompe o laço; continue pula para a próxima volta
foreach ([1, 2, 3, 4, 5] as $n) {
    if ($n === 3) continue; // pula o 3
    if ($n === 5) break;    // para antes do 5
    echo $n; // imprime 1 2 4
}
Regra prática foreach para arrays (90% dos casos reais), for para contagens, while quando não se sabe quantas repetições haverá (ex.: ler linhas de um arquivo).

Exercícios

Clássicos que treinam raciocínio — e que já caíram em testes técnicos.

2.1FizzBuzz: imprima de 1 a 30. Múltiplos de 3 viram "Fizz", múltiplos de 5 viram "Buzz", múltiplos de ambos viram "FizzBuzz". Este é o teste técnico mais famoso do mundo.
<?php

for ($i = 1; $i <= 30; $i++) {
    if ($i % 15 === 0) {
        echo "FizzBuzz\n";
    } elseif ($i % 3 === 0) {
        echo "Fizz\n";
    } elseif ($i % 5 === 0) {
        echo "Buzz\n";
    } else {
        echo "$i\n";
    }
}
2.2Usando match, converta um número de 1 a 7 no dia da semana correspondente ("domingo" a "sábado").
<?php

$numero = 3;

$dia = match ($numero) {
    1 => "domingo",
    2 => "segunda",
    3 => "terça",
    4 => "quarta",
    5 => "quinta",
    6 => "sexta",
    7 => "sábado",
    default => "número inválido",
};

echo $dia; // terça
2.3Some todos os números pares de 1 a 100 usando um laço.
<?php

$soma = 0;
for ($i = 2; $i <= 100; $i += 2) {
    $soma += $i;
}
echo $soma; // 2550
Capítulo 03 · Fundamentos

Arrays e strings: as ferramentas do dia a dia

Se PHP fosse uma caixa de ferramentas, arrays seriam o martelo e strings a chave de fenda. Você vai usá-los em literalmente todo projeto.

Arrays indexados

<?php

$linguagens = ["PHP", "JavaScript", "Python"];

echo $linguagens[0];        // PHP (índices começam em 0!)
$linguagens[] = "Go";       // adiciona no final
echo count($linguagens);    // 4

Arrays associativos (chave => valor)

Em vez de índices numéricos, você usa nomes. É a estrutura mais importante do PHP — dados de formulários, resultados de banco e respostas de API chegam nesse formato:

<?php

$vaga = [
    "titulo"   => "Dev PHP Júnior",
    "salario"  => 3800,
    "remoto"   => true,
    "stack"    => ["PHP 8", "Laravel", "MySQL"],
];

echo $vaga["titulo"];      // Dev PHP Júnior
echo $vaga["stack"][1];    // Laravel (array dentro de array!)

foreach ($vaga as $campo => $valor) {
    // percorre chave e valor ao mesmo tempo
}

Funções de array que você precisa conhecer

FunçãoO que faz
count($arr)Quantidade de itens
in_array($v, $arr)Verifica se o valor existe
array_keys($arr)Retorna só as chaves
array_merge($a, $b)Junta dois arrays
sort($arr) / rsort($arr)Ordena crescente / decrescente
array_map($fn, $arr)Transforma cada item
array_filter($arr, $fn)Filtra itens por condição
array_sum($arr)Soma os valores
implode(", ", $arr)Array → string
explode(",", $str)String → array
<?php

$precos = [100, 250, 80, 400];

// array_map: aplica uma função a cada item
$comDesconto = array_map(fn($p) => $p * 0.9, $precos);

// array_filter: mantém só o que passa no teste
$caros = array_filter($precos, fn($p) => $p > 100);

echo array_sum($precos);            // 830
echo implode(" | ", $precos);       // 100 | 250 | 80 | 400
Radar do mercado array_map, array_filter e array_reduce são queridinhas em testes técnicos: mostram que você pensa de forma funcional em vez de escrever só laços gigantes. As arrow functions (fn($x) => ..., PHP 7.4+) deixam o código enxuto — use-as.

Strings: funções essenciais

<?php

$email = "  ANA.silva@Empresa.COM  ";

$limpo = trim($email);                    // remove espaços das pontas
$minusculo = strtolower($limpo);          // ana.silva@empresa.com
echo strlen($minusculo);                  // tamanho da string
echo str_contains($minusculo, "@");      // true (PHP 8+)
echo str_replace("empresa", "tech", $minusculo);
echo ucfirst("olá");                      // Olá
echo mb_strtoupper("ação");              // AÇÃO (mb_* respeita acentos!)

// sprintf: formatação profissional
$preco = 1234.5;
echo sprintf("Total: R$ %.2f", $preco);  // Total: R$ 1234.50
echo number_format($preco, 2, ",", "."); // 1.234,50 (formato brasileiro)
Atenção com acentos Funções como strtoupper podem falhar com "ç" e acentos. Para textos em português, prefira as versões mb_* (multibyte): mb_strtoupper, mb_strlen, mb_substr.

Exercícios

Arrays associativos + funções de array = o pão com manteiga das provas técnicas.

3.1Dado $notas = [8.5, 6.0, 9.2, 4.5, 7.8], calcule a média e conte quantas notas são maiores ou iguais a 7.
<?php

$notas = [8.5, 6.0, 9.2, 4.5, 7.8];

$media = array_sum($notas) / count($notas);
$aprovadas = count(array_filter($notas, fn($n) => $n >= 7));

echo "Média: $media | Notas >= 7: $aprovadas"; // Média: 7.2 | Notas >= 7: 3
3.2Crie um array associativo de um produto (nome, preco, estoque) e imprima: "Notebook — R$ 3.499,90 (12 em estoque)" com number_format.
<?php

$produto = ["nome" => "Notebook", "preco" => 3499.90, "estoque" => 12];

echo sprintf(
    "%s — R$ %s (%d em estoque)",
    $produto["nome"],
    number_format($produto["preco"], 2, ",", "."),
    $produto["estoque"]
);
3.3Transforme a string "php,laravel,mysql,docker" em uma lista com cada tecnologia em maiúsculas, separada por " | ".
<?php

$str = "php,laravel,mysql,docker";

$resultado = implode(" | ", array_map("mb_strtoupper", explode(",", $str)));

echo $resultado; // PHP | LARAVEL | MYSQL | DOCKER
Capítulo 04 · Fundamentos

Funções: organizando e reutilizando código

Funções transformam blocos de código em peças nomeadas e reutilizáveis. É aqui que seu código começa a parecer profissional.

Declarando e chamando

<?php

function calcularFrete(float $peso, string $uf): float
{
    $base = 15.00;
    $porKg = ($uf === "SP") ? 2.50 : 4.00;
    return $base + ($peso * $porKg);
}

echo calcularFrete(3.5, "SP"); // 23.75
Radar do mercado Repare nos type hints: float $peso, string $uf e o retorno : float. Código tipado é padrão em qualquer empresa séria hoje. Entregar teste técnico sem tipos em 2026 é ponto negativo.

Parâmetros: padrão, nomeados e variádicos

<?php

// Valor padrão
function saudar(string $nome, string $saudacao = "Olá"): string
{
    return "$saudacao, $nome!";
}
echo saudar("Ana");                  // Olá, Ana!
echo saudar("Ana", "Bem-vinda");     // Bem-vinda, Ana!

// Argumentos nomeados (PHP 8+)
echo saudar(saudacao: "E aí", nome: "João");

// Variádicos: aceita qualquer quantidade de argumentos
function somar(int ...$numeros): int
{
    return array_sum($numeros);
}
echo somar(1, 2, 3, 4); // 10

Funções anônimas e arrow functions

<?php

// Função anônima guardada em variável
$dobrar = function (int $n): int {
    return $n * 2;
};

// Arrow function: sintaxe curta, captura variáveis de fora automaticamente
$taxa = 1.1;
$comTaxa = fn(float $preco) => $preco * $taxa;

echo $dobrar(21);      // 42
echo $comTaxa(100);    // 110

Escopo e tipos que aceitam null

<?php

$mensagem = "externa";

function testar(): void
{
    // echo $mensagem; // ERRO! Variáveis de fora não entram sozinhas
}

// ?string = "string OU null"
function buscarApelido(int $id): ?string
{
    return ($id === 1) ? "aninha" : null;
}
Boas práticas desde já Uma função deve fazer uma coisa só e ter nome de verbo (calcularFrete, validarEmail). Se ela passa de ~20 linhas ou você usa "e" para descrevê-la ("valida e salva e envia e-mail"), divida em funções menores.

Exercícios

Funções bem escritas são o que diferencia código de júnior promissor de código de curioso.

4.1Escreva validarSenha(string $senha): bool que retorna true apenas se a senha tiver 8+ caracteres e ao menos um número.
<?php

function validarSenha(string $senha): bool
{
    return strlen($senha) >= 8 && preg_match('/\d/', $senha) === 1;
}

var_dump(validarSenha("abc123"));    // false (curta)
var_dump(validarSenha("segredo42")); // true
4.2Crie aplicarDesconto que recebe um preço e um percentual com valor padrão de 10%, retornando o preço final tipado.
<?php

function aplicarDesconto(float $preco, float $percentual = 10): float
{
    return $preco * (1 - $percentual / 100);
}

echo aplicarDesconto(200);      // 180
echo aplicarDesconto(200, 25);  // 150
4.3Use array_filter com uma arrow function para manter apenas e-mails válidos em ["a@b.com", "invalido", "x@y.org"]. Dica: filter_var($e, FILTER_VALIDATE_EMAIL).
<?php

$emails = ["a@b.com", "invalido", "x@y.org"];

$validos = array_filter(
    $emails,
    fn($e) => filter_var($e, FILTER_VALIDATE_EMAIL) !== false
);

print_r($validos); // a@b.com e x@y.org
Capítulo 05 · Web

Formulários, superglobais e sessões

PHP nasceu para a web. Aqui você aprende a receber dados do usuário — a base de qualquer sistema de login, cadastro ou busca.

Como a web funciona (em 30 segundos)

O navegador envia uma requisição (request) ao servidor; o PHP processa e devolve uma resposta (response), geralmente HTML. Os dois métodos principais:

Recebendo dados com $_GET e $_POST

form.php
<?php
// Acessível em: form.php?nome=Ana
$nome = $_GET['nome'] ?? 'visitante';
?>
<form method="POST" action="processa.php">
  <input type="text" name="email" placeholder="Seu e-mail">
  <input type="password" name="senha" placeholder="Senha">
  <button type="submit">Entrar</button>
</form>
processa.php
<?php

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = trim($_POST['email'] ?? '');
    $senha = $_POST['senha'] ?? '';

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        exit('E-mail inválido.');
    }

    echo "Recebido: " . htmlspecialchars($email);
}
Nunca confie no usuário Todo dado externo é suspeito. Sempre: (1) use ?? para campos ausentes, (2) valide com filter_var, (3) escape a saída com htmlspecialchars() antes de exibir. Esse hábito evita a falha XSS, tema do capítulo de segurança.

As superglobais principais

VariávelContém
$_GETParâmetros da URL
$_POSTDados de formulários POST
$_SERVERInfo da requisição/servidor (método, IP, URL)
$_SESSIONDados que persistem entre páginas para o mesmo usuário
$_COOKIECookies salvos no navegador
$_FILESArquivos enviados por upload

Sessões: mantendo o usuário logado

HTTP "não tem memória": cada requisição é independente. Sessões resolvem isso guardando dados no servidor, ligados ao visitante por um cookie:

login.php
<?php
session_start(); // SEMPRE antes de qualquer saída

// ...após validar e-mail e senha no banco:
$_SESSION['usuario_id'] = 42;
$_SESSION['nome'] = 'Ana';

header('Location: painel.php'); // redireciona
exit;
painel.php
<?php
session_start();

if (!isset($_SESSION['usuario_id'])) {
    header('Location: login.php');
    exit;
}

echo "Bem-vinda, " . htmlspecialchars($_SESSION['nome']);

// Para sair (logout.php):
// session_destroy();
Radar do mercado "Explique como funciona uma sessão em PHP" é pergunta frequente. Resposta de ouro: o servidor cria um arquivo/registro identificado por um ID; esse ID vai para o navegador num cookie (PHPSESSID); a cada requisição o cookie volta e o PHP recarrega os dados em $_SESSION. Mencione também que logins modernos usam password_hash (cap. 10).

Exercícios

Monte um mini-projeto: são só 2 arquivos.

5.1Crie um formulário de contato (nome, e-mail, mensagem) que valida os três campos via POST e reexibe os erros. Escape toda saída.
<?php
$erros = [];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nome     = trim($_POST['nome'] ?? '');
    $email    = trim($_POST['email'] ?? '');
    $mensagem = trim($_POST['mensagem'] ?? '');

    if ($nome === '')  $erros[] = 'Nome é obrigatório.';
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $erros[] = 'E-mail inválido.';
    if (mb_strlen($mensagem) < 10) $erros[] = 'Mensagem muito curta.';

    if (empty($erros)) {
        echo 'Obrigado, ' . htmlspecialchars($nome) . '!';
        exit;
    }
}
?>
<?php foreach ($erros as $erro): ?>
  <p style="color:red"><?= htmlspecialchars($erro) ?></p>
<?php endforeach; ?>
<form method="POST">
  <input name="nome" placeholder="Nome">
  <input name="email" placeholder="E-mail">
  <textarea name="mensagem" placeholder="Mensagem"></textarea>
  <button>Enviar</button>
</form>
5.2Crie um contador de visitas por sessão: cada recarregamento da página soma 1 em $_SESSION['visitas'].
<?php
session_start();

$_SESSION['visitas'] = ($_SESSION['visitas'] ?? 0) + 1;

echo "Você visitou esta página {$_SESSION['visitas']} vez(es).";
Capítulo 06 · Avançado

Programação Orientada a Objetos (POO)

POO é o divisor de águas entre "sei PHP" e "sou empregável em PHP". Laravel, WordPress moderno e todo código corporativo são orientados a objetos.

Classes e objetos

Uma classe é um molde; um objeto é algo construído a partir dele. A classe define propriedades (dados) e métodos (comportamentos):

<?php

class Produto
{
    // Promoção de propriedades no construtor (PHP 8+): declara e atribui de uma vez
    public function __construct(
        private string $nome,
        private float $preco,
        private int $estoque = 0,
    ) {}

    public function getNome(): string
    {
        return $this->nome;
    }

    public function precoFormatado(): string
    {
        return "R$ " . number_format($this->preco, 2, ",", ".");
    }

    public function vender(int $quantidade): void
    {
        if ($quantidade > $this->estoque) {
            throw new InvalidArgumentException("Estoque insuficiente.");
        }
        $this->estoque -= $quantidade;
    }
}

$notebook = new Produto("Notebook", 3499.90, 10);
echo $notebook->getNome();          // Notebook
echo $notebook->precoFormatado();   // R$ 3.499,90
$notebook->vender(2);

Visibilidade: public, private, protected

ModificadorQuem acessaQuando usar
publicQualquer códigoA "interface" da classe: métodos que o mundo usa
privateSó a própria classePadrão para propriedades — protege os dados
protectedA classe e suas filhasQuando herança precisa acessar
Encapsulamento Propriedades private + métodos públicos controlados = encapsulamento. Ninguém consegue fazer $notebook->estoque = -50 por fora; toda mudança passa por regras (como o método vender()). Esse é um dos 4 pilares da POO.

Herança

<?php

class Funcionario
{
    public function __construct(
        protected string $nome,
        protected float $salarioBase,
    ) {}

    public function calcularSalario(): float
    {
        return $this->salarioBase;
    }
}

class Gerente extends Funcionario
{
    public function calcularSalario(): float
    {
        // Sobrescreve o método do pai e o reutiliza
        return parent::calcularSalario() + 2000; // bônus de gestão
    }
}

$dev = new Funcionario("Ana", 5000);
$gerente = new Gerente("Bruno", 5000);

echo $dev->calcularSalario();     // 5000
echo $gerente->calcularSalario(); // 7000 — polimorfismo em ação!

Interfaces e classes abstratas

Uma interface é um contrato: quem a implementa promete ter aqueles métodos. Uma classe abstrata é um molde incompleto que não pode ser instanciado diretamente:

<?php

interface Pagavel
{
    public function pagar(float $valor): string;
}

class Pix implements Pagavel
{
    public function pagar(float $valor): string
    {
        return "Pagos R$ $valor via Pix (instantâneo).";
    }
}

class Boleto implements Pagavel
{
    public function pagar(float $valor): string
    {
        return "Boleto de R$ $valor gerado (compensa em 2 dias).";
    }
}

// A função aceita QUALQUER classe que cumpra o contrato:
function processarPagamento(Pagavel $metodo, float $valor): void
{
    echo $metodo->pagar($valor);
}

processarPagamento(new Pix(), 100);
processarPagamento(new Boleto(), 100);
Radar do mercado Em entrevistas, os 4 pilares da POO são presença garantida: Encapsulamento (proteger dados), Herança (reaproveitar), Polimorfismo (mesmo método, comportamentos diferentes) e Abstração (expor o essencial, esconder detalhes). O exemplo Pagavel acima demonstra os quatro — memorize-o como resposta.

Recursos que completam seu vocabulário

<?php

class Config
{
    // static: pertence à CLASSE, não ao objeto
    public static function versao(): string
    {
        return "1.0.0";
    }
}
echo Config::versao(); // acesso com :: sem precisar de new

// Enum (PHP 8.1+): conjunto fechado de valores
enum StatusPedido: string
{
    case Pendente = 'pendente';
    case Pago     = 'pago';
    case Enviado  = 'enviado';
}
$status = StatusPedido::Pago;
echo $status->value; // "pago"

// Trait: reaproveita código entre classes sem herança
trait TemTimestamps
{
    public function criadoEm(): string
    {
        return date('d/m/Y H:i');
    }
}
class Post { use TemTimestamps; }

Exercícios

POO só entra na cabeça construindo. Capriche aqui.

6.1Crie uma classe ContaBancaria com saldo privado, métodos depositar() e sacar() (que impede saldo negativo lançando exceção) e getSaldo().
<?php

class ContaBancaria
{
    public function __construct(private float $saldo = 0) {}

    public function depositar(float $valor): void
    {
        if ($valor <= 0) {
            throw new InvalidArgumentException("Valor deve ser positivo.");
        }
        $this->saldo += $valor;
    }

    public function sacar(float $valor): void
    {
        if ($valor > $this->saldo) {
            throw new RuntimeException("Saldo insuficiente.");
        }
        $this->saldo -= $valor;
    }

    public function getSaldo(): float
    {
        return $this->saldo;
    }
}

$conta = new ContaBancaria(100);
$conta->depositar(50);
$conta->sacar(30);
echo $conta->getSaldo(); // 120
6.2Crie a interface Notificavel com método enviar(string $mensagem) e implemente Email e WhatsApp. Escreva uma função que notifica por qualquer canal.
<?php

interface Notificavel
{
    public function enviar(string $mensagem): string;
}

class Email implements Notificavel
{
    public function enviar(string $mensagem): string
    {
        return "E-mail enviado: $mensagem";
    }
}

class WhatsApp implements Notificavel
{
    public function enviar(string $mensagem): string
    {
        return "Zap enviado: $mensagem";
    }
}

function notificar(Notificavel $canal, string $msg): void
{
    echo $canal->enviar($msg) . "\n";
}

notificar(new Email(), "Pedido aprovado!");
notificar(new WhatsApp(), "Pedido aprovado!");
6.3De memória: cite os 4 pilares da POO e dê um exemplo de cada em uma frase.

Encapsulamento: saldo privado só alterado por depositar/sacar. Herança: Gerente extends Funcionario reaproveita código. Polimorfismo: calcularSalario() se comporta diferente em cada classe. Abstração: quem usa Pagavel não precisa saber como o Pix funciona por dentro.

Capítulo 07 · Profissional

Boas práticas: Composer, PSR e tratamento de erros

Este capítulo é o que separa quem estudou por conta de quem está pronto para um time: gerenciar dependências, seguir padrões e lidar com erros como gente grande.

Composer: o gerenciador de pacotes do PHP

Nenhum projeto profissional reinventa a roda. O Composer instala bibliotecas prontas (validação, e-mail, PDFs...) e gerencia o autoload do seu próprio código. Instale-o em getcomposer.org e:

terminal
$ composer init                     # cria o composer.json do projeto
$ composer require vlucas/phpdotenv # instala uma biblioteca
$ composer install                  # instala tudo listado no composer.json

Autoload PSR-4: adeus, require manual

Em vez de escrever require 'classes/Produto.php' em todo arquivo, você configura o autoload uma vez e o PHP encontra as classes sozinho:

composer.json
{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}
src/Model/Produto.php
<?php

namespace App\Model;

class Produto
{
    // A classe App\Model\Produto DEVE estar em src/Model/Produto.php
}
index.php
<?php

require __DIR__ . '/vendor/autoload.php'; // único require do projeto!

use App\Model\Produto;

$p = new Produto();

Rode composer dump-autoload após criar o mapeamento. Namespaces funcionam como "sobrenomes" das classes, evitando conflitos entre bibliotecas.

Radar do mercado As PSRs (PHP Standards Recommendations) são padrões da comunidade. As mais citadas em vagas: PSR-4 (autoload, acima), PSR-12 (estilo de código: indentação, chaves, nomes) e PSR-7 (interfaces HTTP). Dizer "sigo PSR-12 e uso autoload PSR-4" em entrevista mostra maturidade. Ferramentas como PHP_CodeSniffer e php-cs-fixer verificam isso automaticamente.

Erros e exceções

<?php

function dividir(float $a, float $b): float
{
    if ($b === 0.0) {
        throw new InvalidArgumentException("Divisão por zero não permitida.");
    }
    return $a / $b;
}

try {
    echo dividir(10, 0);
} catch (InvalidArgumentException $e) {
    echo "Erro de argumento: " . $e->getMessage();
} catch (Throwable $e) {
    // Throwable pega qualquer erro/exceção — último recurso
    echo "Erro inesperado: " . $e->getMessage();
} finally {
    echo "\nSempre executa (fechar conexões, liberar recursos...).";
}
Regra de ouro Lance exceções onde o problema acontece; capture onde você sabe o que fazer com ele. Nunca engula erros com um catch vazio — em produção isso vira bug fantasma impossível de rastrear.

Exceções personalizadas

<?php

class EstoqueInsuficienteException extends RuntimeException {}

// Uso:
// throw new EstoqueInsuficienteException("Só restam 2 unidades.");
// Vantagem: o catch consegue diferenciar SEU erro de negócio
// de erros genéricos do sistema.

Configuração com .env

Senhas de banco e chaves de API nunca vão no código (nem no Git!). Elas vivem em um arquivo .env, lido por bibliotecas como vlucas/phpdotenv:

.env
DB_HOST=localhost
DB_NAME=loja
DB_USER=root
DB_PASS=segredo123
<?php
require __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$host = $_ENV['DB_HOST'];

Exercícios

Monte a estrutura de um projeto real — isso vai direto para o seu portfólio.

7.1Crie um projeto com composer init, configure autoload PSR-4 (App\ → src/) e crie App\Util\Texto com um método estático slug() que transforma "Olá Mundo PHP" em "ola-mundo-php".
src/Util/Texto.php
<?php

namespace App\Util;

class Texto
{
    public static function slug(string $texto): string
    {
        $texto = mb_strtolower(trim($texto));
        // remove acentos
        $texto = iconv('UTF-8', 'ASCII//TRANSLIT', $texto);
        // troca tudo que não é letra/número por hífen
        $texto = preg_replace('/[^a-z0-9]+/', '-', $texto);
        return trim($texto, '-');
    }
}

// index.php:
// require __DIR__ . '/vendor/autoload.php';
// echo \App\Util\Texto::slug("Olá Mundo PHP"); // ola-mundo-php
7.2Crie a exceção SaldoInsuficienteException e refatore a ContaBancaria do capítulo 6 para usá-la, com um try/catch demonstrando.
<?php

class SaldoInsuficienteException extends RuntimeException {}

class ContaBancaria
{
    public function __construct(private float $saldo = 0) {}

    public function sacar(float $valor): void
    {
        if ($valor > $this->saldo) {
            throw new SaldoInsuficienteException(
                "Tentou sacar $valor com saldo de {$this->saldo}."
            );
        }
        $this->saldo -= $valor;
    }
}

try {
    (new ContaBancaria(50))->sacar(100);
} catch (SaldoInsuficienteException $e) {
    echo "Operação negada: " . $e->getMessage();
}
Capítulo 08 · Banco de dados

Banco de dados: MySQL + PDO

Todo sistema guarda dados: usuários, produtos, pedidos. Você vai aprender SQL essencial e a conectar o PHP ao MySQL do jeito certo — com PDO e prepared statements.

SQL em 10 minutos

SQL é a linguagem dos bancos relacionais. Crie o banco e a tabela no seu MySQL (via phpMyAdmin do XAMPP ou terminal):

SQL
CREATE DATABASE loja CHARACTER SET utf8mb4;

USE loja;

CREATE TABLE produtos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(120) NOT NULL,
    preco DECIMAL(10,2) NOT NULL,
    estoque INT NOT NULL DEFAULT 0,
    criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- As 4 operações (CRUD):
INSERT INTO produtos (nome, preco, estoque) VALUES ('Teclado', 199.90, 15);
SELECT * FROM produtos WHERE preco < 500 ORDER BY nome;
UPDATE produtos SET estoque = 20 WHERE id = 1;
DELETE FROM produtos WHERE id = 1;
Radar do mercado CRUD (Create, Read, Update, Delete) é o termo mais falado em vagas júnior: "desenvolver CRUDs" = criar telas/rotas de cadastro, listagem, edição e exclusão. Saber também JOIN (juntar tabelas), GROUP BY e índices básicos coloca você na frente da fila.

Conectando com PDO

PDO é a interface moderna do PHP para bancos de dados. Ela funciona com MySQL, PostgreSQL, SQLite e outros, mudando só a string de conexão:

conexao.php
<?php

function conectar(): PDO
{
    $dsn = "mysql:host=localhost;dbname=loja;charset=utf8mb4";

    return new PDO($dsn, "root", "", [
        // Lança exceções em erros (essencial!)
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        // Resultados como array associativo
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
}

CRUD completo com prepared statements

crud.php
<?php
require 'conexao.php';

$pdo = conectar();

// CREATE — repare nos placeholders (?): NUNCA concatene valores no SQL
$stmt = $pdo->prepare("INSERT INTO produtos (nome, preco, estoque) VALUES (?, ?, ?)");
$stmt->execute(["Mouse Gamer", 149.90, 30]);
$novoId = $pdo->lastInsertId();

// READ — todos
$produtos = $pdo->query("SELECT * FROM produtos")->fetchAll();
foreach ($produtos as $p) {
    echo "{$p['nome']} — R$ {$p['preco']}\n";
}

// READ — um, com placeholder nomeado
$stmt = $pdo->prepare("SELECT * FROM produtos WHERE id = :id");
$stmt->execute(['id' => $novoId]);
$produto = $stmt->fetch();

// UPDATE
$stmt = $pdo->prepare("UPDATE produtos SET preco = :preco WHERE id = :id");
$stmt->execute(['preco' => 129.90, 'id' => $novoId]);

// DELETE
$stmt = $pdo->prepare("DELETE FROM produtos WHERE id = ?");
$stmt->execute([$novoId]);

echo "Linhas afetadas: " . $stmt->rowCount();
Por que prepared statements? Se você montar SQL concatenando entrada do usuário — "...WHERE nome = '$busca'" — um atacante pode digitar ' OR '1'='1 e ler (ou apagar) seu banco inteiro. Isso é SQL Injection, a falha nº 1 da web. Prepared statements separam o comando dos dados e eliminam o problema. Detalhes no capítulo 10.

Transações: tudo ou nada

<?php
// Ex.: transferência — debitar de um e creditar em outro deve ser atômico

try {
    $pdo->beginTransaction();

    $pdo->prepare("UPDATE contas SET saldo = saldo - 100 WHERE id = 1")->execute();
    $pdo->prepare("UPDATE contas SET saldo = saldo + 100 WHERE id = 2")->execute();

    $pdo->commit();   // confirma as duas
} catch (Throwable $e) {
    $pdo->rollBack(); // desfaz tudo se qualquer uma falhar
    throw $e;
}

Relacionamentos e JOIN

SQL
CREATE TABLE pedidos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    produto_id INT NOT NULL,
    quantidade INT NOT NULL,
    FOREIGN KEY (produto_id) REFERENCES produtos(id)
);

-- Junta as duas tabelas pelo relacionamento:
SELECT pedidos.id, produtos.nome, pedidos.quantidade
FROM pedidos
INNER JOIN produtos ON produtos.id = pedidos.produto_id;

Exercícios

Um CRUD funcional no GitHub vale mais que dez certificados.

8.1Crie a tabela usuarios (id, nome, email único, criado_em) e escreva um script PHP que insere um usuário e depois lista todos.
CREATE TABLE usuarios (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nome VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
<?php
require 'conexao.php';
$pdo = conectar();

$stmt = $pdo->prepare("INSERT INTO usuarios (nome, email) VALUES (?, ?)");
$stmt->execute(["Ana Silva", "ana@exemplo.com"]);

foreach ($pdo->query("SELECT * FROM usuarios")->fetchAll() as $u) {
    echo "#{$u['id']} {$u['nome']} <{$u['email']}>\n";
}
8.2Escreva uma busca de produtos por nome parcial (LIKE) usando prepared statement — sem risco de SQL Injection.
<?php
$busca = $_GET['q'] ?? '';

$stmt = $pdo->prepare("SELECT * FROM produtos WHERE nome LIKE :busca");
$stmt->execute(['busca' => "%{$busca}%"]);

$resultados = $stmt->fetchAll();

O curinga % entra no valor, nunca no SQL. O prepared statement cuida do resto.

8.3De memória: por que "SELECT * FROM usuarios WHERE email = '$email'" é perigoso, e qual a alternativa correta?

Porque o valor de $email entra direto no SQL: um atacante pode injetar comandos (' OR '1'='1' --) e ler/alterar dados. Correto: $pdo->prepare("SELECT * FROM usuarios WHERE email = ?") seguido de $stmt->execute([$email]).

Capítulo 09 · Backend

APIs REST: o coração do backend moderno

Aplicativos, sites em React/Vue e integrações entre empresas conversam via APIs. Saber construir e consumir APIs REST é requisito em praticamente toda vaga de backend.

O que é uma API REST?

Uma API é um "garçom digital": o cliente (app, site) faz um pedido, o servidor responde — quase sempre em JSON. REST é o estilo que organiza esses pedidos em recursos (URLs) e verbos HTTP:

VerboUsoExemplo
GETLer dadosGET /produtos, GET /produtos/5
POSTCriarPOST /produtos
PUT/PATCHAtualizarPUT /produtos/5
DELETEExcluirDELETE /produtos/5

Códigos de status que você precisa decorar

CódigoSignificado
200 OKDeu certo
201 CreatedRecurso criado (resposta de POST)
400 Bad RequestDados inválidos enviados pelo cliente
401 / 403Não autenticado / sem permissão
404 Not FoundRecurso não existe
500Erro interno do servidor (bug seu!)

JSON em PHP

<?php

$produto = ["id" => 1, "nome" => "Teclado", "preco" => 199.9];

$json = json_encode($produto);           // array → JSON
$dados = json_decode($json, true);       // JSON → array (true = assoc.)

echo $json; // {"id":1,"nome":"Teclado","preco":199.9}

Construindo uma API em PHP puro

Uma mini-API de produtos em um arquivo. Rode com php -S localhost:8000 api.php e teste com o navegador, Postman ou cURL:

api.php
<?php
require 'conexao.php';

header('Content-Type: application/json; charset=utf-8');

$pdo    = conectar();
$metodo = $_SERVER['REQUEST_METHOD'];
$uri    = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

// extrai o id de /produtos/5, se houver
preg_match('#^/produtos/?(\d+)?$#', $uri, $m);
$id = $m[1] ?? null;

if (!isset($m[0])) {
    http_response_code(404);
    echo json_encode(['erro' => 'Rota não encontrada']);
    exit;
}

match (true) {
    // GET /produtos → lista todos
    $metodo === 'GET' && $id === null => print(json_encode(
        $pdo->query("SELECT * FROM produtos")->fetchAll()
    )),

    // GET /produtos/5 → busca um
    $metodo === 'GET' => (function () use ($pdo, $id) {
        $stmt = $pdo->prepare("SELECT * FROM produtos WHERE id = ?");
        $stmt->execute([$id]);
        $produto = $stmt->fetch();

        if (!$produto) {
            http_response_code(404);
            echo json_encode(['erro' => 'Produto não encontrado']);
            return;
        }
        echo json_encode($produto);
    })(),

    // POST /produtos → cria
    $metodo === 'POST' => (function () use ($pdo) {
        $corpo = json_decode(file_get_contents('php://input'), true);

        if (empty($corpo['nome']) || !isset($corpo['preco'])) {
            http_response_code(400);
            echo json_encode(['erro' => 'nome e preco são obrigatórios']);
            return;
        }

        $stmt = $pdo->prepare("INSERT INTO produtos (nome, preco) VALUES (?, ?)");
        $stmt->execute([$corpo['nome'], $corpo['preco']]);

        http_response_code(201);
        echo json_encode(['id' => (int) $pdo->lastInsertId()]);
    })(),

    // DELETE /produtos/5
    $metodo === 'DELETE' && $id !== null => (function () use ($pdo, $id) {
        $pdo->prepare("DELETE FROM produtos WHERE id = ?")->execute([$id]);
        http_response_code(204); // sem conteúdo
    })(),

    default => (function () {
        http_response_code(405);
        echo json_encode(['erro' => 'Método não permitido']);
    })(),
};
Detalhes que impressionam Repare: Content-Type: application/json no header, corpo lido de php://input, validação com 400, criação com 201 e exclusão com 204. Usar os códigos certos é o tipo de detalhe que avaliadores de teste técnico procuram.

Consumindo APIs de terceiros

consumir.php
<?php
// Exemplo real: consultar CEP na API pública ViaCEP

$cep = "01310100";
$json = file_get_contents("https://viacep.com.br/ws/{$cep}/json/");
$dados = json_decode($json, true);

echo $dados['logradouro']; // Avenida Paulista

// Com cURL (mais controle: timeout, headers, POST...):
$ch = curl_init("https://viacep.com.br/ws/{$cep}/json/");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
]);
$resposta = curl_exec($ch);
$status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status === 200) {
    $dados = json_decode($resposta, true);
}
Radar do mercado Termos de vaga traduzidos: "integração com APIs" = consumir serviços externos (pagamento, frete, WhatsApp); "API RESTful" = construir endpoints como os acima; "Postman/Insomnia" = ferramentas para testar APIs (aprenda uma!); "autenticação JWT/Bearer token" = proteger endpoints com tokens — o Laravel facilita isso (próximo capítulo).

Exercícios

Uma API sua rodando + coleção do Postman no README = portfólio nota 10.

9.1Adicione à API o endpoint PUT /produtos/{id} que atualiza nome e preço, retornando 404 se não existir.
<?php
// dentro do match(true):
$metodo === 'PUT' && $id !== null => (function () use ($pdo, $id) {
    $corpo = json_decode(file_get_contents('php://input'), true);

    $stmt = $pdo->prepare(
        "UPDATE produtos SET nome = ?, preco = ? WHERE id = ?"
    );
    $stmt->execute([$corpo['nome'] ?? '', $corpo['preco'] ?? 0, $id]);

    if ($stmt->rowCount() === 0) {
        http_response_code(404);
        echo json_encode(['erro' => 'Produto não encontrado']);
        return;
    }
    echo json_encode(['atualizado' => true]);
})(),
9.2Escreva um script que consulta a API pública https://brasilapi.com.br/api/banks/v1 e imprime o nome dos 5 primeiros bancos.
<?php

$json = file_get_contents("https://brasilapi.com.br/api/banks/v1");
$bancos = json_decode($json, true);

foreach (array_slice($bancos, 0, 5) as $banco) {
    echo ($banco['name'] ?? 'sem nome') . "\n";
}
9.3De memória: qual status code para (a) criação bem-sucedida, (b) dados inválidos do cliente, (c) recurso inexistente?

(a) 201 Created · (b) 400 Bad Request (ou 422 para validação semântica) · (c) 404 Not Found.

Capítulo 10 · Profissional

Segurança: o que todo dev PHP precisa saber

Empresas perdem dinheiro e reputação com falhas de segurança — por isso perguntam sobre o tema em quase toda entrevista. As quatro proteções abaixo cobrem 90% do que se espera de um júnior.

1. SQL Injection → prepared statements

<?php
// VULNERÁVEL — nunca faça isso:
$sql = "SELECT * FROM usuarios WHERE email = '{$_POST['email']}'";
// Se o atacante enviar: ' OR '1'='1  → retorna TODOS os usuários

// SEGURO:
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE email = ?");
$stmt->execute([$_POST['email'] ?? '']);

2. XSS (Cross-Site Scripting) → escapar a saída

XSS acontece quando conteúdo enviado por um usuário é exibido sem tratamento e vira código no navegador de outro:

<?php
$comentario = '<script>roubarCookies()</script>';

// VULNERÁVEL:
echo $comentario; // o script EXECUTA no navegador da vítima

// SEGURO — vira texto inofensivo na tela:
echo htmlspecialchars($comentario, ENT_QUOTES, 'UTF-8');
Mantra Valide na entrada, escape na saída. filter_var ao receber; htmlspecialchars ao exibir; prepared statement ao persistir. Três hábitos, três grandes classes de ataque neutralizadas.

3. Senhas → password_hash, nunca texto puro (nem MD5!)

<?php

// No CADASTRO:
$hash = password_hash($_POST['senha'], PASSWORD_DEFAULT);
// salve $hash no banco — algo como $2y$10$N9qo8uLOickgx2ZMRZoMye...
// Impossível de reverter; cada hash é único mesmo para senhas iguais.

// No LOGIN:
$stmt = $pdo->prepare("SELECT id, senha_hash FROM usuarios WHERE email = ?");
$stmt->execute([$_POST['email'] ?? '']);
$usuario = $stmt->fetch();

if ($usuario && password_verify($_POST['senha'], $usuario['senha_hash'])) {
    session_start();
    session_regenerate_id(true); // previne fixação de sessão
    $_SESSION['usuario_id'] = $usuario['id'];
} else {
    echo "Credenciais inválidas."; // mensagem genérica de propósito
}
Radar do mercado Pergunta clássica: "Como você armazena senhas?" Resposta que aprova: password_hash() com PASSWORD_DEFAULT (bcrypt/argon2) e verificação com password_verify(). Mencionar MD5 ou SHA1 para senhas é eliminatório — são rápidos demais e quebráveis por força bruta.

4. CSRF → token nos formulários

CSRF é quando um site malicioso faz o navegador da vítima (já logada) enviar uma ação ao seu sistema. A defesa é um token secreto por sessão:

<?php
session_start();

// Ao gerar o formulário:
$_SESSION['csrf'] = $_SESSION['csrf'] ?? bin2hex(random_bytes(32));
?>
<form method="POST">
  <input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">
  <button>Excluir conta</button>
</form>

<?php
// Ao processar:
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(403);
    exit('Token CSRF inválido.');
}

Checklist de segurança do júnior

Exercícios

Segurança é o tema em que errar no estudo custa barato — e errar no emprego custa caro.

10.1Escreva o fluxo completo de cadastro: receber nome/e-mail/senha por POST, validar, gerar hash e inserir no banco com prepared statement.
<?php
require 'conexao.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nome  = trim($_POST['nome'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $senha = $_POST['senha'] ?? '';

    if ($nome === '' || !filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($senha) < 8) {
        http_response_code(400);
        exit('Dados inválidos.');
    }

    $hash = password_hash($senha, PASSWORD_DEFAULT);

    $stmt = conectar()->prepare(
        "INSERT INTO usuarios (nome, email, senha_hash) VALUES (?, ?, ?)"
    );
    $stmt->execute([$nome, $email, $hash]);

    echo 'Cadastro realizado!';
}
10.2Explique com suas palavras a diferença entre SQL Injection e XSS, e a defesa principal de cada um.

SQL Injection ataca o banco: entrada do usuário vira comando SQL. Defesa: prepared statements. XSS ataca o navegador de outros usuários: entrada vira HTML/JS executável na página. Defesa: escapar a saída com htmlspecialchars. Um mira os dados no servidor; o outro, as pessoas que veem a página.

Capítulo 11 · Framework

Laravel: o framework que emprega

Laravel é o framework PHP mais popular do mundo e o mais pedido em vagas no Brasil. Ele organiza tudo que você aprendeu — rotas, banco, segurança — em uma estrutura elegante e produtiva.

Por que um framework?

Você acabou de escrever roteamento, PDO e CSRF na mão. O Laravel entrega tudo isso pronto, testado e seguro, para você focar na regra de negócio. Entender o "na mão" primeiro (como você fez!) é o que separa quem usa Laravel de quem depende dele.

Instalação e estrutura

terminal
$ composer create-project laravel/laravel minha-loja
$ cd minha-loja
$ php artisan serve   # http://localhost:8000

Pastas que importam no início:

Pasta/arquivoO que vive lá
routes/web.phpRotas de páginas (e routes/api.php para APIs)
app/Http/ControllersControllers — a lógica de cada rota
app/ModelsModels — classes que representam tabelas
resources/viewsTemplates Blade (o HTML)
database/migrationsHistórico versionado da estrutura do banco
.envConfiguração local (banco, chaves)

MVC: o mapa mental do Laravel

O padrão MVC divide a aplicação em: Model (dados e regras), View (o que o usuário vê) e Controller (recebe a requisição, orquestra Model e View). Requisição → Rota → Controller → Model → View → Resposta.

Rotas e controllers

routes/web.php
<?php

use App\Http\Controllers\ProdutoController;
use Illuminate\Support\Facades\Route;

Route::get('/', fn() => view('welcome'));

Route::get('/produtos', [ProdutoController::class, 'index']);
Route::get('/produtos/{id}', [ProdutoController::class, 'show']);
terminal
$ php artisan make:controller ProdutoController
app/Http/Controllers/ProdutoController.php
<?php

namespace App\Http\Controllers;

use App\Models\Produto;

class ProdutoController extends Controller
{
    public function index()
    {
        $produtos = Produto::orderBy('nome')->get();

        return view('produtos.index', ['produtos' => $produtos]);
    }

    public function show(int $id)
    {
        $produto = Produto::findOrFail($id); // 404 automático!

        return view('produtos.show', compact('produto'));
    }
}

Migrations e Models (Eloquent)

terminal
$ php artisan make:model Produto -m   # -m já cria a migration junto
database/migrations/..._create_produtos_table.php
<?php

public function up(): void
{
    Schema::create('produtos', function (Blueprint $table) {
        $table->id();
        $table->string('nome', 120);
        $table->decimal('preco', 10, 2);
        $table->integer('estoque')->default(0);
        $table->timestamps(); // created_at e updated_at automáticos
    });
}
terminal
$ php artisan migrate   # cria a tabela no banco configurado no .env

Eloquent é o ORM do Laravel: cada Model é uma tabela, cada objeto é uma linha — e você quase não escreve SQL:

<?php

use App\Models\Produto;

// CREATE
$p = Produto::create(['nome' => 'Mouse', 'preco' => 89.9, 'estoque' => 10]);
// (requer: protected $fillable = ['nome', 'preco', 'estoque']; no Model)

// READ
$todos   = Produto::all();
$baratos = Produto::where('preco', '<', 100)->orderBy('preco')->get();
$um      = Produto::find(1);

// UPDATE
$um->preco = 79.9;
$um->save();

// DELETE
$um->delete();

// Relacionamentos (no Model Pedido):
// public function produto() { return $this->belongsTo(Produto::class); }
// Uso: $pedido->produto->nome

Views com Blade

resources/views/produtos/index.blade.php
@extends('layouts.app')

@section('conteudo')
  <h1>Produtos</h1>

  @forelse ($produtos as $produto)
    <p>{{ $produto->nome }} — R$ {{ number_format($produto->preco, 2, ',', '.') }}</p>
  @empty
    <p>Nenhum produto cadastrado.</p>
  @endforelse
@endsection
Segurança grátis As chaves {{ }} do Blade já aplicam htmlspecialchars automaticamente (anti-XSS), e todo formulário usa @csrf para o token. O framework aplica por padrão o que você aprendeu no capítulo 10 — agora você sabe por quê.

Validação e formulários

<?php
// No controller:
public function store(Request $request)
{
    $dados = $request->validate([
        'nome'  => 'required|string|max:120',
        'preco' => 'required|numeric|min:0',
    ]);

    Produto::create($dados);

    return redirect('/produtos')->with('sucesso', 'Produto criado!');
}

API REST em Laravel (compare com o cap. 9!)

routes/api.php
<?php

use App\Http\Controllers\Api\ProdutoApiController;

Route::apiResource('produtos', ProdutoApiController::class);
// Uma linha cria: GET /api/produtos, GET /api/produtos/{id},
// POST, PUT/PATCH e DELETE — tudo mapeado para métodos do controller.
Api/ProdutoApiController.php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Produto;
use Illuminate\Http\Request;

class ProdutoApiController extends Controller
{
    public function index()
    {
        return Produto::paginate(15); // JSON automático, com paginação!
    }

    public function store(Request $request)
    {
        $dados = $request->validate([
            'nome'  => 'required|max:120',
            'preco' => 'required|numeric',
        ]);

        return response()->json(Produto::create($dados), 201);
    }

    public function show(Produto $produto)  // route model binding: 404 automático
    {
        return $produto;
    }

    public function update(Request $request, Produto $produto)
    {
        $produto->update($request->validate([
            'nome'  => 'sometimes|max:120',
            'preco' => 'sometimes|numeric',
        ]));

        return $produto;
    }

    public function destroy(Produto $produto)
    {
        $produto->delete();

        return response()->noContent(); // 204
    }
}
Radar do mercado Vocabulário Laravel que aparece em vagas: Eloquent (ORM), Artisan (CLI: php artisan ...), Migrations/Seeders (banco versionado e dados de teste), Middleware (filtros de requisição, ex.: exigir login), Sanctum (autenticação de API por token), Queues/Jobs (tarefas em segundo plano) e PHPUnit/Pest (testes). Para júnior: domine os 4 primeiros e saiba explicar os demais.

Exercícios

O projeto destes exercícios é o embrião do seu portfólio do capítulo final.

11.1Crie um projeto Laravel, o Model Tarefa (titulo, concluida boolean) com migration, e uma rota /tarefas que lista todas em uma view Blade.
$ composer create-project laravel/laravel tarefas-app
$ cd tarefas-app
$ php artisan make:model Tarefa -m
migration
Schema::create('tarefas', function (Blueprint $table) {
    $table->id();
    $table->string('titulo');
    $table->boolean('concluida')->default(false);
    $table->timestamps();
});
routes/web.php
use App\Models\Tarefa;

Route::get('/tarefas', function () {
    return view('tarefas', ['tarefas' => Tarefa::all()]);
});
resources/views/tarefas.blade.php
<h1>Minhas tarefas</h1>
<ul>
  @forelse ($tarefas as $t)
    <li>{{ $t->titulo }} {{ $t->concluida ? '✔' : '' }}</li>
  @empty
    <li>Nada por aqui ainda.</li>
  @endforelse
</ul>

Rode php artisan migrate e php artisan serve. Use php artisan tinker para criar tarefas: Tarefa::create(['titulo' => 'Estudar PHP']) (adicione $fillable no Model).

11.2Transforme as tarefas em uma API: Route::apiResource + controller com index, store (validação) e destroy. Teste com Postman ou cURL.
<?php
// routes/api.php
use App\Http\Controllers\TarefaController;
Route::apiResource('tarefas', TarefaController::class)
    ->only(['index', 'store', 'destroy']);

// app/Http/Controllers/TarefaController.php
namespace App\Http\Controllers;

use App\Models\Tarefa;
use Illuminate\Http\Request;

class TarefaController extends Controller
{
    public function index()
    {
        return Tarefa::all();
    }

    public function store(Request $request)
    {
        $dados = $request->validate(['titulo' => 'required|max:255']);
        return response()->json(Tarefa::create($dados), 201);
    }

    public function destroy(Tarefa $tarefa)
    {
        $tarefa->delete();
        return response()->noContent();
    }
}
teste com cURL
$ curl -X POST http://localhost:8000/api/tarefas \
    -H "Content-Type: application/json" \
    -d '{"titulo": "Terminar a apostila"}'
Capítulo 12 · Carreira

Projeto final, portfólio e a primeira vaga

Conhecimento sem prova pública não convence recrutador. Este capítulo transforma o que você aprendeu em portfólio, currículo e preparação para entrevistas.

Projeto final: mini sistema de vagas

Construa (em Laravel, ou PHP puro se preferir consolidar a base) um sistema com:

  1. Cadastro e login de usuários (senha com password_hash/Breeze).
  2. CRUD de vagas: título, empresa, salário, descrição, remoto (sim/não).
  3. Busca por título com filtro de faixa salarial.
  4. API REST pública em /api/vagas (GET) e protegida para criar/editar.
  5. Candidatura: usuário logado se candidata a uma vaga (relacionamento N:N).

Esse escopo exercita tudo da apostila: POO, banco, validação, sessões, segurança e API.

GitHub: seu novo currículo

As perguntas de entrevista desta apostila

Revisão relâmpago — você já viu todas as respostas:

PerguntaOnde está a resposta
Diferença entre == e ===?Cap. 1
O que são os 4 pilares da POO?Cap. 6
O que é PSR-4 / autoload / Composer?Cap. 7
Como evitar SQL Injection?Caps. 8 e 10
Como armazenar senhas?Cap. 10
O que é REST? Códigos de status?Cap. 9
Como funciona sessão em PHP?Cap. 5
O que é MVC / Eloquent / migration?Cap. 11

Roadmap depois da apostila

Radar do mercado: onde procurar vagas LinkedIn (configure o cargo "Desenvolvedor PHP Júnior" nos alertas), Gupy, programathor.com.br, e comunidades: PHP Brasil e Laravel Brasil (Telegram/Discord) — muitas vagas circulam lá antes dos portais. Freelas em WordPress/PHP na Workana são ótimos para o primeiro dinheiro e cases reais.
Última dica Candidate-se mesmo sem cumprir 100% dos requisitos — vaga júnior pedindo "3 anos de experiência" é lista de desejos, não filtro rígido. Seu diferencial é o portfólio: código público, rodando, com README claro. Poucos candidatos júnior têm isso. Agora você tem. Boa sorte — e bem-vindo(a) à profissão. 🐘

Exercício final

O único sem botão de resposta — a resposta é sua carreira.

12.1Construa o projeto final, suba no GitHub com README completo, faça deploy e adicione o link no seu LinkedIn. Prazo sugerido: 3 semanas.