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.
Como usar esta apostila
- Siga a ordem dos capítulos. Cada um assume o anterior.
- Digite o código. Não copie e cole — digitar cria memória muscular.
- Faça os exercícios no fim de cada capítulo antes de ver a resposta.
- Marque o capítulo como concluído para acompanhar seu progresso na barra lateral.
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)
- Baixe o XAMPP em
apachefriends.orge instale. - Ele traz PHP + Apache (servidor web) + MySQL (banco de dados) em um pacote só.
- Seus arquivos PHP vão na pasta
htdocse são acessados emhttp://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:
$ php -v # confere a versão instalada (use 8.2 ou superior)
$ php -S localhost:8000 # inicia um servidor na pasta atualSeu 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:
<?php
echo "Olá, mundo! Meu primeiro código PHP.";<?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.
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.
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!";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";$nome pelo valor. Dentro de aspas simples, não: 'Nome: $nome' imprime literalmente Nome: $nome.
Tipos de dados essenciais
| Tipo | Exemplo | Para que serve |
|---|---|---|
string | "Olá" | Textos |
int | 42 | Números inteiros |
float | 3.14 | Números com decimais |
bool | true / false | Verdadeiro ou falso |
array | [1, 2, 3] | Listas e coleções (cap. 3) |
null | null | Ausê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== 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 é raroExercícios
Crie um arquivo cap1.php e rode com php cap1.php no terminal.
<?php
$nome = "Ana";
$idade = 28;
$cidade = "Recife";
echo "$nome tem $idade anos e mora em $cidade";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 ===.
<?php
echo 100 % 7; // 2 ingressos sobramControle 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";?? 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
}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.
<?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";
}
}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<?php
$soma = 0;
for ($i = 2; $i <= 100; $i += 2) {
$soma += $i;
}
echo $soma; // 2550Arrays 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); // 4Arrays 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ção | O 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 | 400array_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)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.
$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: 3number_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"]
);"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 | DOCKERFunçõ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.75float $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); // 10Funçõ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); // 110Escopo 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;
}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.
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")); // trueaplicarDesconto 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); // 150array_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.orgFormulá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:
- GET — busca dados; parâmetros vão na URL (
?busca=php&pagina=2). - POST — envia dados "escondidos" no corpo da requisição (login, cadastro, upload).
Recebendo dados com $_GET e $_POST
<?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><?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);
}?? 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ável | Contém |
|---|---|
$_GET | Parâmetros da URL |
$_POST | Dados de formulários POST |
$_SERVER | Info da requisição/servidor (método, IP, URL) |
$_SESSION | Dados que persistem entre páginas para o mesmo usuário |
$_COOKIE | Cookies salvos no navegador |
$_FILES | Arquivos 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:
<?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;<?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();$_SESSION. Mencione também que logins modernos usam password_hash (cap. 10).
Exercícios
Monte um mini-projeto: são só 2 arquivos.
<?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>$_SESSION['visitas'].<?php
session_start();
$_SESSION['visitas'] = ($_SESSION['visitas'] ?? 0) + 1;
echo "Você visitou esta página {$_SESSION['visitas']} vez(es).";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
| Modificador | Quem acessa | Quando usar |
|---|---|---|
public | Qualquer código | A "interface" da classe: métodos que o mundo usa |
private | Só a própria classe | Padrão para propriedades — protege os dados |
protected | A classe e suas filhas | Quando herança precisa acessar |
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);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.
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(); // 120Notificavel 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!");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.
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:
$ composer init # cria o composer.json do projeto
$ composer require vlucas/phpdotenv # instala uma biblioteca
$ composer install # instala tudo listado no composer.jsonAutoload 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:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}<?php
namespace App\Model;
class Produto
{
// A classe App\Model\Produto DEVE estar em src/Model/Produto.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.
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...).";
}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:
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.
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".<?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-phpSaldoInsuficienteException 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();
}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):
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;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:
<?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
<?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();"...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
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.
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";
}<?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.
"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]).
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:
| Verbo | Uso | Exemplo |
|---|---|---|
GET | Ler dados | GET /produtos, GET /produtos/5 |
POST | Criar | POST /produtos |
PUT/PATCH | Atualizar | PUT /produtos/5 |
DELETE | Excluir | DELETE /produtos/5 |
Códigos de status que você precisa decorar
| Código | Significado |
|---|---|
200 OK | Deu certo |
201 Created | Recurso criado (resposta de POST) |
400 Bad Request | Dados inválidos enviados pelo cliente |
401 / 403 | Não autenticado / sem permissão |
404 Not Found | Recurso não existe |
500 | Erro 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:
<?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']);
})(),
};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
<?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);
}Exercícios
Uma API sua rodando + coleção do Postman no README = portfólio nota 10.
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]);
})(),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";
}(a) 201 Created · (b) 400 Bad Request (ou 422 para validação semântica) · (c) 404 Not Found.
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');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
}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
- ☐ Todo SQL usa prepared statements
- ☐ Toda saída HTML passa por
htmlspecialchars - ☐ Senhas com
password_hash/password_verify - ☐ Formulários que alteram dados têm token CSRF
- ☐ Segredos no
.env, nunca no Git (.gitignore!) - ☐ Erros detalhados só em desenvolvimento (
display_errors=Offem produção) - ☐ Uploads: valide extensão E tipo MIME, renomeie o arquivo
Exercícios
Segurança é o tema em que errar no estudo custa barato — e errar no emprego custa caro.
<?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!';
}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.
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
$ composer create-project laravel/laravel minha-loja
$ cd minha-loja
$ php artisan serve # http://localhost:8000Pastas que importam no início:
| Pasta/arquivo | O que vive lá |
|---|---|
routes/web.php | Rotas de páginas (e routes/api.php para APIs) |
app/Http/Controllers | Controllers — a lógica de cada rota |
app/Models | Models — classes que representam tabelas |
resources/views | Templates Blade (o HTML) |
database/migrations | Histórico versionado da estrutura do banco |
.env | Configuraçã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
<?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']);$ php artisan make:controller ProdutoController<?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)
$ php artisan make:model Produto -m # -m já cria a migration junto<?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
});
}$ php artisan migrate # cria a tabela no banco configurado no .envEloquent é 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->nomeViews com Blade
@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{{ }} 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!)
<?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.<?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
}
}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.
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 -mSchema::create('tarefas', function (Blueprint $table) {
$table->id();
$table->string('titulo');
$table->boolean('concluida')->default(false);
$table->timestamps();
});use App\Models\Tarefa;
Route::get('/tarefas', function () {
return view('tarefas', ['tarefas' => Tarefa::all()]);
});<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).
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();
}
}$ curl -X POST http://localhost:8000/api/tarefas \
-H "Content-Type: application/json" \
-d '{"titulo": "Terminar a apostila"}'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:
- Cadastro e login de usuários (senha com
password_hash/Breeze). - CRUD de vagas: título, empresa, salário, descrição, remoto (sim/não).
- Busca por título com filtro de faixa salarial.
- API REST pública em
/api/vagas(GET) e protegida para criar/editar. - 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
- Crie uma conta e suba 3 projetos: o CRUD do cap. 8, a API do cap. 9 e o projeto final.
- Cada repositório com README caprichado: o que faz, prints, como rodar (
composer install,.env.example,php artisan migrate), tecnologias usadas. - Commits pequenos e frequentes com mensagens claras ("Adiciona validação no cadastro de vagas") — recrutadores técnicos olham o histórico.
- Aprenda o básico de Git:
clone,add,commit,push,branch,pull request. É pré-requisito universal.
As perguntas de entrevista desta apostila
Revisão relâmpago — você já viu todas as respostas:
| Pergunta | Onde 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
- Git e GitHub fluentes — pratique com seus próprios projetos.
- Testes automatizados — PHPUnit/Pest; escreva testes para o projeto final. Vagas plenas exigem; júnior que testa se destaca muito.
- Docker básico — rodar PHP + MySQL em containers (Laravel Sail facilita).
- Front básico — HTML/CSS decentes e JavaScript suficiente para consumir sua API.
- Deploy real — coloque o projeto final no ar (Railway, Render, VPS barata). "Link do projeto rodando" no currículo é ouro.
- Inglês técnico de leitura — documentação e Stack Overflow são em inglês.
Exercício final
O único sem botão de resposta — a resposta é sua carreira.