Laboratório Prático em PHP

Construtor Interativo de Tabela (DrawDB + PHP PDO)

Escolha o nome da tabela e marque os campos que você precisa. O PHP gerará automaticamente o script SQL DDL (pronto para colar em Import > SQL no DrawDB) e o código PHP 8 + PDO com os tipos de bind corretos!

Selecione as Colunas e Funcionalidades da Tabela:
1️⃣ SQL Gerado (No DrawDB: File > Import > From SQL)
CREATE TABLE clientes_loja (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid CHAR(36) NOT NULL UNIQUE,
    nome VARCHAR(120) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    cpf CHAR(11) NOT NULL UNIQUE,
    preco DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
    ativo BOOLEAN NOT NULL DEFAULT 1,
    criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
2️⃣ Código PHP 8 + PDO Gerado para esta Tabela
<?php
declare(strict_types=1);

// Inserindo na tabela `clientes_loja` com Prepared Statement PDO
$stmt = $pdo->prepare('
    INSERT INTO clientes_loja (uuid, nome, email, cpf, preco, ativo)
    VALUES (:uuid, :nome, :email, :cpf, :preco, :ativo)
');

$stmt->bindValue(':uuid', '550e8400-e29b-41d4-a716-446655440000', PDO::PARAM_STR);
$stmt->bindValue(':nome', 'Marcos Silva', PDO::PARAM_STR);
$stmt->bindValue(':email', 'contato@exemplo.com.br', PDO::PARAM_STR);
$stmt->bindValue(':cpf', '01234567890', PDO::PARAM_STR);
$stmt->bindValue(':preco', '199.90', PDO::PARAM_STR);
$stmt->bindValue(':ativo', 1, PDO::PARAM_INT);
$stmt->execute();

$novoId = (int) $pdo->lastInsertId();

🧭 Guia Rápido: "O que você quer guardar no banco?"

Consulte os casos reais mais comuns em sistemas brasileiros (CPF, CNPJ, CEP, Dinheiro, Senhas, Fotos) e evite erros graves de modelagem.

Documentos & Identidade Coluna: cpf

CPF (Cadastro de Pessoa Física)

✅ Tipo Recomendado: CHAR(11) Alternativa: VARCHAR(14) (se insistir em salvar com pontos/traço, mas CHAR(11) limpo é superior)
📐 No DrawDB: Type: CHAR | Size: 11 | Flags: NN (se obrigatório), UQ (Unique)
🚫 Nunca Use: INT ou BIGINT

Números inteiros apagam o zero à esquerda! O CPF "01234567890" viraria "1234567890", corrompendo o documento.

🐘 Snippet PHP 8 + PDO
// Remove tudo que não for dígito antes de salvar no CHAR(11)
$cpfLimpo = preg_replace('/\D/', '', $_POST['cpf'] ?? '');
if (strlen($cpfLimpo) !== 11) {
    throw new InvalidArgumentException('CPF deve conter 11 dígitos.');
}
$stmt->bindValue(':cpf', $cpfLimpo, PDO::PARAM_STR);
Documentos & Identidade Coluna: cnpj

CNPJ (Pessoa Jurídica)

✅ Tipo Recomendado: CHAR(14) Alternativa: CHAR(14) alfanumérico (compatível com o novo padrão CNPJ alfanumérico da Receita Federal)
📐 No DrawDB: Type: CHAR | Size: 14 | Flags: NN, UQ (Unique)
🚫 Nunca Use: BIGINT

Além de perder zeros à esquerda, o CNPJ agora suporta letras e números (CNPJ Alfanumérico), exigindo `CHAR(14)`.

🐘 Snippet PHP 8 + PDO
// Remove pontuação mantendo letras maiúsculas e números (14 posições)
$cnpjLimpo = preg_replace('/[^A-Z0-9]/', '', strtoupper($_POST['cnpj'] ?? ''));
if (strlen($cnpjLimpo) !== 14) {
    throw new InvalidArgumentException('CNPJ deve conter 14 caracteres.');
}
$stmt->bindValue(':cnpj', $cnpjLimpo, PDO::PARAM_STR);
Segurança & Autenticação Coluna: senha_hash

Senha de Usuário (Password Hash)

✅ Tipo Recomendado: VARCHAR(255) Alternativa: CHAR(60) (apenas se fixar Bcrypt, mas VARCHAR(255) é o padrão oficial do PHP)
📐 No DrawDB: Type: VARCHAR | Size: 255 | Flags: NN
🚫 Nunca Use: VARCHAR(20) ou MD5 CHAR(32)

Jamais salve senhas em texto puro ou MD5! A função nativa `password_hash()` do PHP gera hashes de 60 caracteres (Bcrypt) a ~97 caracteres (Argon2id) e recomenda oficialmente `VARCHAR(255)` para suportar algoritmos futuros.

🐘 Snippet PHP 8 + PDO
// Ao cadastrar:
$hash = password_hash($senhaPura, PASSWORD_DEFAULT);
$stmt->bindValue(':senha_hash', $hash, PDO::PARAM_STR);

// Ao fazer login:
if (password_verify($senhaDigitada, $usuarioDoBanco['senha_hash'])) {
    // Login aprovado!
}
Financeiro & Comercial Coluna: preco_unitario / valor_total

Preços, Salários e Valores em Dinheiro (R$)

✅ Tipo Recomendado: DECIMAL(10, 2) Alternativa: DECIMAL(12, 2) para grandes movimentações ou BIGINT (em centavos)
📐 No DrawDB: Type: DECIMAL | Size: 10,2 | Flags: NN | Default: 0.00
🚫 Nunca Use: FLOAT ou DOUBLE

FLOAT usa ponto flutuante binário aproximado (`0.1 + 0.2 = 0.30000000000000004`), gerando diferenças de centavos em somas de pedidos e auditorias fiscais.

🐘 Snippet PHP 8 + PDO
// Sempre trafegue DECIMAL como string no PDO para evitar imprecisão de float:
$preco = '1499.90';
$stmt->bindValue(':preco', $preco, PDO::PARAM_STR);
Contato & Endereço Coluna: email

Endereço de E-mail

✅ Tipo Recomendado: VARCHAR(150) Alternativa: VARCHAR(254) (limite máximo oficial da RFC 5321)
📐 No DrawDB: Type: VARCHAR | Size: 150 | Flags: NN, UQ (Unique)
🚫 Nunca Use: TEXT ou CHAR(150)

CHAR(150) desperdiçaria espaço fixo em todos os registros, e TEXT dificulta a criação direta de índice UNIQUE sem prefixo no MySQL.

🐘 Snippet PHP 8 + PDO
$email = mb_strtolower(trim($_POST['email'] ?? ''), 'UTF-8');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    throw new InvalidArgumentException('E-mail inválido.');
}
$stmt->bindValue(':email', $email, PDO::PARAM_STR);
Contato & Endereço Coluna: telefone

Telefone / Celular / WhatsApp

✅ Tipo Recomendado: VARCHAR(20) Alternativa: CHAR(11) (se aceitar estritamente celulares do Brasil sem DDI +55)
📐 No DrawDB: Type: VARCHAR | Size: 20 | Flags: NN (ou NULL se opcional)
🚫 Nunca Use: INT

Um celular com DDD (ex: 11999998888) passa de 11 bilhões e ESTOURA o limite máximo do tipo INT (2,14 bilhões)! Além disso, telefones não sofrem cálculos matemáticos.

🐘 Snippet PHP 8 + PDO
// Mantém apenas dígitos (e opcionalmente o sinal + do DDI padrão E.164)
$telefone = preg_replace('/[^\d+]/', '', $_POST['telefone'] ?? '');
$stmt->bindValue(':telefone', $telefone, PDO::PARAM_STR);
Contato & Endereço Coluna: cep / uf

CEP e Sigla do Estado (UF)

✅ Tipo Recomendado: CHAR(8) para CEP e CHAR(2) para UF Alternativa: VARCHAR(10) se precisar aceitar Postal Codes internacionais
📐 No DrawDB: cep -> Type: CHAR | Size: 8 | uf -> Type: CHAR | Size: 2
🚫 Nunca Use: INT para CEP / VARCHAR(255) para UF

CEPs de São Paulo e Grande SP começam com zero (`01310-100`). Se salvar em INT, o zero some e vira `1310100`.

🐘 Snippet PHP 8 + PDO
$cep = preg_replace('/\D/', '', $_POST['cep'] ?? ''); // "01310100"
$uf  = strtoupper(trim($_POST['uf'] ?? ''));          // "SP"
$stmt->bindValue(':cep', $cep, PDO::PARAM_STR);
$stmt->bindValue(':uf', $uf, PDO::PARAM_STR);
Mídia & Conteúdo Coluna: foto_url / caminho_arquivo

Foto de Perfil, Imagens de Produtos e PDFs

✅ Tipo Recomendado: VARCHAR(255) Alternativa: VARCHAR(500) (para URLs longas de CDN/S3)
📐 No DrawDB: Type: VARCHAR | Size: 255 | Flags: NULL (se foto for opcional)
🚫 Nunca Use: LONGBLOB (guardando a imagem inteira dentro da tabela)

Salvar imagens e PDFs dentro do banco deixa as consultas lentas, multiplica o tamanho do backup em gigabytes e consome toda a RAM do servidor SQL. Salve o arquivo em uma pasta `/uploads` ou no S3 e grave apenas o caminho/URL no banco!

🐘 Snippet PHP 8 + PDO
// Salva o arquivo no disco/storage com nome único e grava apenas o caminho no banco:
$nomeArquivo = bin2hex(random_bytes(16)) . '.webp';
$caminhoRelativo = 'uploads/produtos/' . $nomeArquivo;
// move_uploaded_file($_FILES['foto']['tmp_name'], __DIR__ . '/public/' . $caminhoRelativo);

$stmt->bindValue(':foto_url', $caminhoRelativo, PDO::PARAM_STR);
Fluxo & Controle Coluna: status

Status de Pedido / Situação de Cadastro

✅ Tipo Recomendado: ENUM('pendente', 'pago', 'enviado', 'cancelado') ou VARCHAR(30) Alternativa: TINYINT UNSIGNED (mapeado com Enum no PHP)
📐 No DrawDB: Type: ENUM | Values: 'pendente','pago','enviado','cancelado' | Flags: NN | Default: 'pendente'
🚫 Nunca Use: TEXT livre sem validação

Sem restrição de domínio (ENUM ou validação por BackedEnum no PHP), erros de digitação como `"Pago"`, `"pago "` ou `"aprovado"` quebram os relatórios financeiros.

🐘 Snippet PHP 8 + PDO
enum StatusPedido: string {
    case Pendente  = 'pendente';
    case Pago      = 'pago';
    case Enviado   = 'enviado';
    case Cancelado = 'cancelado';
}

$stmt->bindValue(':status', StatusPedido::Pago->value, PDO::PARAM_STR);
Fluxo & Controle Coluna: criado_em, atualizado_em, deletado_em

Datas de Criação, Atualização e Exclusão Lógica (Soft Delete)

✅ Tipo Recomendado: TIMESTAMP (ou DATETIME) Alternativa: TIMESTAMPTZ no PostgreSQL
📐 No DrawDB: criado_em -> Type: TIMESTAMP | NN | Default: CURRENT_TIMESTAMP | deletado_em -> Type: TIMESTAMP | NULL
🚫 Nunca Use: VARCHAR(20)

Campos `TIMESTAMP` com `DEFAULT CURRENT_TIMESTAMP` são preenchidos sozinhos pelo próprio banco de dados e permitem filtrar vendas por mês, dia e hora instantaneamente.

🐘 Snippet PHP 8 + PDO
// Para Soft Delete (desativar sem apagar do banco com DELETE):
$stmt = $pdo->prepare('UPDATE produtos SET deletado_em = CURRENT_TIMESTAMP WHERE id = :id');
$stmt->bindValue(':id', $produtoId, PDO::PARAM_INT);
$stmt->execute();