Banco de dados
De SQL puro ao CRUD completo em PHP. A regra inegociável: sempre PDO com prepared statements — variável de usuário nunca entra concatenada na query.
Introdução
SQL é a linguagem para conversar com o banco de dados (MySQL/MariaDB no XAMPP). O PHP não guarda dados sozinho: ele conecta ao MySQL, envia comandos SQL e recebe os resultados de volta como arrays.
Tipos de dados SQL
Cada coluna tem um tipo fixo. Escolher certo economiza espaço e evita erro.
| Tipo | Guarda | Exemplo |
|---|---|---|
INT | Número inteiro | idade, id |
VARCHAR(n) | Texto curto até n caracteres | nome, email |
TEXT | Texto longo (sem limite prático) | comentário, post |
DATE | Data (AAAA-MM-DD) | 2026-06-01 |
DATETIME | Data + hora | 2026-06-01 09:30:00 |
DECIMAL(m,d) | Número exato (dinheiro!) | DECIMAL(10,2) → 19.90 |
BOOLEAN | Verdadeiro/falso (na prática um TINYINT 0/1) | ativo |
DECIMAL, nunca FLOAT. Float tem erro de arredondamento e some centavos.
DDL: criar e alterar tabelas
DDL (Data Definition Language) define a estrutura: criar banco, criar/alterar/remover tabelas.
-- Criar o banco (charset utf8mb4 = suporte total a acentos e emoji)
CREATE DATABASE agenda CHARACTER SET utf8mb4;
-- Criar a tabela
CREATE TABLE contatos (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE,
criado_em DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Adicionar uma coluna
ALTER TABLE contatos ADD telefone VARCHAR(20);
-- Modificar uma coluna existente
ALTER TABLE contatos MODIFY telefone VARCHAR(30);
-- Remover a tabela inteira (cuidado!)
DROP TABLE contatos;
Constraints
Regras que o banco garante por você. Definidas na criação da coluna.
| Constraint | O que faz |
|---|---|
PRIMARY KEY | Identificador único da linha (não repete, não nulo) |
AUTO_INCREMENT | O banco gera o próximo número sozinho |
FOREIGN KEY | Liga uma coluna ao id de outra tabela (relacionamento) |
NOT NULL | Campo obrigatório |
UNIQUE | Valor não pode se repetir (ex.: email) |
DEFAULT | Valor automático se nada for informado |
-- Cada pedido pertence a um contato (FOREIGN KEY)
CREATE TABLE pedidos (
id INT AUTO_INCREMENT PRIMARY KEY,
contato_id INT NOT NULL,
valor DECIMAL(10,2) NOT NULL,
FOREIGN KEY (contato_id) REFERENCES contatos(id)
);
DML: manipular dados
DML (Data Manipulation Language) mexe nos dados: inserir, ler, atualizar, apagar. Estes exemplos são SQL "puro" (no phpMyAdmin); já já fazemos o mesmo via PHP.
-- INSERT: inserir uma linha
INSERT INTO contatos (nome, email) VALUES ('Ana', 'ana@site.com');
-- SELECT: ler com filtro, ordenação e limite
SELECT nome, email FROM contatos
WHERE nome LIKE 'A%' -- começa com A
ORDER BY nome ASC -- A→Z (DESC = Z→A)
LIMIT 10; -- no máximo 10 linhas
-- UPDATE: sempre com WHERE! (sem ele, altera TODAS as linhas)
UPDATE contatos SET email = 'nova@site.com' WHERE id = 1;
-- DELETE: idem, sempre com WHERE
DELETE FROM contatos WHERE id = 1;
WHERE aplica a operação em todas as linhas. Um DELETE FROM contatos; apaga a tabela inteira.
Conexão PDO
O PDO conecta via uma DSN (string que diz driver, host, banco e charset) + usuário + senha. Envolva em try/catch e ative o modo de exceção para os erros não passarem despercebidos.
<?php
// DSN: driver:host;banco;charset
$dsn = "mysql:host=localhost;dbname=agenda;charset=utf8mb4";
$usuario = "root"; // no XAMPP, root sem senha (em produção: usuário restrito!)
$senha = "";
try {
$pdo = new PDO($dsn, $usuario, $senha);
// Erros viram exceções (essencial para o try/catch funcionar).
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// fetch() devolve array associativo por padrão.
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
// Mensagem amigável. NUNCA exiba $e->getMessage() em produção.
exit("Não foi possível conectar ao banco. Tente mais tarde.");
}
CRUD com prepared statements
O coração da segurança: a query vai com placeholders (? ou :nome) e os valores são passados separados, no execute(). O banco trata os valores como dados, nunca como código — isso elimina SQL injection.
<?php
// CREATE — inserir (placeholders posicionais ?)
$stmt = $pdo->prepare("INSERT INTO contatos (nome, email) VALUES (?, ?)");
$stmt->execute(["Ana", "ana@site.com"]);
echo $pdo->lastInsertId(); // id gerado, ex.: 1
// READ (vários) — fetchAll devolve um array de linhas
$contatos = $pdo->query("SELECT id, nome FROM contatos ORDER BY nome")->fetchAll();
// READ (um) — placeholder nomeado :id
$stmt = $pdo->prepare("SELECT * FROM contatos WHERE id = :id");
$stmt->execute([":id" => 1]);
$contato = $stmt->fetch(); // uma linha (ou false se não achar)
// UPDATE
$stmt = $pdo->prepare("UPDATE contatos SET email = ? WHERE id = ?");
$stmt->execute(["novo@site.com", 1]);
// DELETE
$stmt = $pdo->prepare("DELETE FROM contatos WHERE id = ?");
$stmt->execute([1]);
"... WHERE id = $id" (concatenar variável na query). É a porta da SQL injection. Sempre placeholder + execute().
MySQLi vs PDO
Existem duas APIs no PHP: MySQLi (só MySQL) e PDO (funciona com MySQL, PostgreSQL, SQLite… — é portável). Prefira PDO: mesma sintaxe para qualquer banco e prepared statements mais limpos. O exemplo abaixo é só para comparação.
<?php
// MySQLi — apenas para comparação. Na prática, use PDO.
$mysqli = new mysqli("localhost", "root", "", "agenda");
$resultado = $mysqli->query("SELECT nome FROM contatos");
while ($linha = $resultado->fetch_assoc()) {
echo $linha["nome"];
}
Mini-projeto: agenda de contatos
CRUD completo num arquivo só, usando o conexao.php da seção 06. O mesmo formulário serve para criar e editar; a listagem traz links de editar/excluir. Tudo com prepared statements e saída escapada com h().
<?php
require "conexao.php"; // traz o $pdo já configurado
// Helper: escapa toda saída (anti-XSS).
function h(string $s): string {
return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
}
// ---- CRIAR ou ATUALIZAR (POST) ----
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$id = (int) ($_POST["id"] ?? 0);
$nome = trim($_POST["nome"] ?? "");
$email = trim($_POST["email"] ?? "");
if ($nome !== "") {
if ($id > 0) {
// Editar existente.
$stmt = $pdo->prepare("UPDATE contatos SET nome = ?, email = ? WHERE id = ?");
$stmt->execute([$nome, $email, $id]);
} else {
// Inserir novo.
$stmt = $pdo->prepare("INSERT INTO contatos (nome, email) VALUES (?, ?)");
$stmt->execute([$nome, $email]);
}
}
header("Location: agenda.php"); // POST-Redirect-GET
exit;
}
// ---- EXCLUIR (GET ?excluir=ID) ----
if (isset($_GET["excluir"])) {
$stmt = $pdo->prepare("DELETE FROM contatos WHERE id = ?");
$stmt->execute([(int) $_GET["excluir"]]);
header("Location: agenda.php");
exit;
}
// ---- EDITAR: carregar o contato no formulário (GET ?editar=ID) ----
$edicao = null;
if (isset($_GET["editar"])) {
$stmt = $pdo->prepare("SELECT * FROM contatos WHERE id = ?");
$stmt->execute([(int) $_GET["editar"]]);
$edicao = $stmt->fetch();
}
// ---- LISTAR ----
$contatos = $pdo->query("SELECT id, nome, email FROM contatos ORDER BY nome")->fetchAll();
?>
<!DOCTYPE html>
<html lang="pt-BR">
<body>
<h1>Agenda de contatos</h1>
<!-- O mesmo form cria e edita (o id escondido decide) -->
<form method="post">
<input type="hidden" name="id" value="<?= h($edicao["id"] ?? "") ?>">
<input type="text" name="nome" value="<?= h($edicao["nome"] ?? "") ?>" placeholder="Nome">
<input type="email" name="email" value="<?= h($edicao["email"] ?? "") ?>" placeholder="E-mail">
<button type="submit"><?= $edicao ? "Salvar" : "Adicionar" ?></button>
</form>
<table>
<?php foreach ($contatos as $c): ?>
<tr>
<td><?= h($c["nome"]) ?></td>
<td><?= h($c["email"]) ?></td>
<td>
<a href="?editar=<?= (int) $c["id"] ?>">editar</a>
<a href="?excluir=<?= (int) $c["id"] ?>"
onclick="return confirm('Excluir?')">excluir</a>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
schema.sql (seção 03) no phpMyAdmin para criar o banco e a tabela, salve conexao.php e agenda.php em htdocs/, e acesse http://localhost/agenda.php pelo Apache.