CRUD completo
Esta aula ensina a construir um sistema CRUD completo em PHP, cobrindo as operações Create, Read, Update e Delete, a estrutura de diretórios, validação de dados e um exemplo prático com MySQL e PDO.
Nesta aula, vamos construir um sistema CRUD completo em PHP. CRUD é um acrônimo para Create (criar), Read (ler), Update (atualizar) e Delete (excluir) — as quatro operações básicas de persistência em bancos de dados. Implementaremos cada uma delas usando PHP com PDO e MySQL, seguindo boas práticas de segurança e organização de código.
O objetivo é que ao final você seja capaz de criar, listar, editar e excluir registros de forma segura, com validação de dados e proteção contra SQL injection. Vamos desenvolver um gerenciador simples de usuários.
Create, Read, Update, Delete
As quatro operações CRUD são a base de qualquer aplicação que manipula dados persistentes. Em PHP, cada operação corresponde a uma instrução SQL específica e a um fluxo de requisição HTTP (GET, POST, PUT, DELETE).
- Create (INSERT): Adiciona um novo registro ao banco. Geralmente via formulário POST.
- Read (SELECT): Recupera registros. Pode ser uma listagem ou um único registro via GET.
- Update (UPDATE): Altera um registro existente. Normalmente via POST com ID.
- Delete (DELETE): Remove um registro. Pode ser via GET ou POST com confirmação.
Exemplo de código para cada operação usando PDO:
// Create
$stmt = $pdo->prepare("INSERT INTO usuarios (nome, email) VALUES (:nome, :email)");
$stmt->execute(['nome' => $nome, 'email' => $email]);
// Read (todos)
$stmt = $pdo->query("SELECT * FROM usuarios");
$usuarios = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Read (único)
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE id = :id");
$stmt->execute(['id' => $id]);
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
// Update
$stmt = $pdo->prepare("UPDATE usuarios SET nome = :nome, email = :email WHERE id = :id");
$stmt->execute(['nome' => $nome, 'email' => $email, 'id' => $id]);
// Delete
$stmt = $pdo->prepare("DELETE FROM usuarios WHERE id = :id");
$stmt->execute(['id' => $id]);
Estrutura
Organizar o código em pastas e arquivos separados facilita a manutenção. Uma estrutura comum para um CRUD simples:
/projeto
/config
database.php
/includes
header.php
footer.php
/public
index.php
create.php
edit.php
delete.php
config/database.php: Conexão PDO com MySQL.includes/header.phpefooter.php: Partes comuns do HTML.public/index.php: Listagem de usuários.public/create.php: Formulário de criação e processamento.public/edit.php: Formulário de edição e processamento.public/delete.php: Confirmação e exclusão.
Exemplo de config/database.php:
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
die("Erro na conexão: " . $e->getMessage());
}
?>
Validação
Validar os dados antes de inserir ou atualizar é essencial para segurança e integridade. Devemos verificar se os campos obrigatórios estão preenchidos, se os formatos estão corretos (email, CPF, etc.) e sanitizar as entradas.
Exemplo de validação para nome e email:
$erros = [];
if (empty($_POST['nome'])) {
$erros[] = 'O campo nome é obrigatório.';
} elseif (strlen($_POST['nome']) < 3) {
$erros[] = 'O nome deve ter pelo menos 3 caracteres.';
} else {
$nome = htmlspecialchars(trim($_POST['nome']), ENT_QUOTES, 'UTF-8');
}
if (empty($_POST['email'])) {
$erros[] = 'O campo email é obrigatório.';
} elseif (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
$erros[] = 'Email inválido.';
} else {
$email = filter_var(trim($_POST['email']), FILTER_SANITIZE_EMAIL);
}
if (empty($erros)) {
// prosseguir com INSERT ou UPDATE
} else {
// exibir erros
}
Além disso, use prepared statements para evitar SQL injection, como já demonstrado.
Exemplo prático
Vamos criar um CRUD completo de usuários. Primeiro, a tabela SQL:
CREATE TABLE usuarios (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Agora, o arquivo index.php (listagem):
query("SELECT * FROM usuarios ORDER BY criado_em DESC");
$usuarios = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
CRUD - Listar Usuários
Usuários
Novo Usuário
ID
Nome
Email
Criado em
Ações
= $usuario['id'] ?>
= htmlspecialchars($usuario['nome']) ?>
= htmlspecialchars($usuario['email']) ?>
= $usuario['criado_em'] ?>
Editar
Excluir
create.php:
prepare("INSERT INTO usuarios (nome, email) VALUES (:nome, :email)");
$stmt->execute(['nome' => $nome, 'email' => $email]);
header('Location: index.php');
exit;
}
}
?>
CRUD - Criar Usuário
Novo Usuário
- = $erro ?>
edit.php e delete.php seguem padrão similar, com prepared statements e validação.
Boas práticas
- Sempre use prepared statements ou PDO com binding para evitar SQL injection.
- Valide e sanitize todas as entradas do usuário.
- Use transações para operações que envolvem múltiplas queries.
- Nunca confie em dados vindos do cliente; verifique permissões no servidor.
- Mantenha a lógica de negócio separada da apresentação (MVC é recomendado para projetos maiores).
Referências
- PHP: PDO - Manual
- PHP: Prepared Statements
- PHP: filter_var
- PHP: htmlspecialchars
- MDN: Métodos de requisição HTTP
- MySQL: SQL Statements
Exercícios
- Crie um novo campo 'telefone' na tabela usuarios e implemente a validação (apenas números, mínimo 10 dígitos).
- Implemente uma função de busca (Read) que filtre usuários por nome ou email usando LIKE.
- Adicione proteção CSRF nos formulários de criação e edição.
- Crie uma página de confirmação antes de excluir (em vez de usar confirm() do JavaScript).
- Implemente paginação na listagem (exibir 10 registros por página).
ALTER TABLE usuarios ADD telefone VARCHAR(20);
// No create.php e edit.php:
if (empty($_POST['telefone'])) {
$erros[] = 'Telefone é obrigatório.';
} elseif (!preg_match('/^\d{10,}$/', $_POST['telefone'])) {
$erros[] = 'Telefone deve conter apenas números e no mínimo 10 dígitos.';
} else {
$telefone = preg_replace('/[^\d]/', '', $_POST['telefone']);
}
$busca = isset($_GET['busca']) ? '%'.$_GET['busca'].'%' : '%';
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE nome LIKE :busca OR email LIKE :busca");
$stmt->execute(['busca' => $busca]);
$usuarios = $stmt->fetchAll(PDO::FETCH_ASSOC);
// No início da sessão:
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
// No formulário:
// No processamento:
if (!hash_equals($_SESSION['token'], $_POST['token'])) {
die('Token inválido');
}
// delete.php:
prepare("DELETE FROM usuarios WHERE id = :id");
$stmt->execute(['id' => $id]);
header('Location: index.php');
exit;
}
$stmt = $pdo->prepare("SELECT * FROM usuarios WHERE id = :id");
$stmt->execute(['id' => $id]);
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
?>
$pagina = isset($_GET['pagina']) ? (int)$_GET['pagina'] : 1;
$limite = 10;
$offset = ($pagina - 1) * $limite;
$total = $pdo->query("SELECT COUNT(*) FROM usuarios")->fetchColumn();
$paginas = ceil($total / $limite);
$stmt = $pdo->prepare("SELECT * FROM usuarios ORDER BY id DESC LIMIT :limite OFFSET :offset");
$stmt->bindValue(':limite', $limite, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$usuarios = $stmt->fetchAll(PDO::FETCH_ASSOC);
// No HTML, exibir links de paginação.