定制 omegaalfa/query-builder 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

omegaalfa/query-builder

最新稳定版本:v1.0.0

Composer 安装命令:

composer require omegaalfa/query-builder

包简介

Um Query Builder moderno e minimalista em PHP 8+, totalmente tipado e baseado em PDO, com suporte a cache, paginação, transações e geração fluente de SQL.

README 文档

README

🧩 Omegaalfa Query Builder

PHP Version License Status PDO Tests

Query Builder moderno, seguro e tipado para PHP 8.4+

📦 Instalação🚀 Início Rápido📖 Documentação🧪 Testes🔒 Segurança

🚀 Sobre o Projeto

Omegaalfa Query Builder é uma biblioteca moderna, leve e fortemente tipada em PHP 8.4+ para construção fluente de queries SQL com PDO.

🎯 Principais Características

  • 🔒 100% Prepared Statements - Segurança contra SQL Injection por design
  • 🎨 Fluent API - Interface intuitiva e encadeável
  • 📦 Zero Dependências - Apenas PHP 8.4+ e PDO
  • 🧪 Testado - 73 testes, 145 assertions
  • Performance - Cache inteligente e streaming de resultados
  • 🔄 Transações - Suporte completo com savepoints
  • 🌐 Multi-driver - MySQL, PostgreSQL, SQLite, SQL Server
  • 📝 Type-Safe - Enums tipadas e strict_types
  • 📚 Documentado - PHPDoc completo em todos os métodos

Inspirado em: Eloquent, Doctrine DBAL, Laravel Query Builder

📦 Instalação

composer require omegaalfa/query-builder

Requisitos

  • PHP >= 8.4 com strict_types habilitado
  • Extensão PDO habilitada
  • Driver PDO correspondente ao seu banco de dados
  • Bancos suportados: MySQL 5.7+, MariaDB 10.2+, PostgreSQL 9.6+, SQLite 3.x+, SQL Server, Oracle

🚀 Início Rápido

Configuração Básica

<?php
declare(strict_types=1);

use Omegaalfa\QueryBuilder\connection\PDOConnection;
use Omegaalfa\QueryBuilder\connection\DatabaseSettings;
use Omegaalfa\QueryBuilder\QueryBuilder;
use Omegaalfa\QueryBuilder\Paginator;

// 1. Configurar conexão
$config = new DatabaseSettings(
    driver: 'mysql',
    host: 'localhost',
    database: 'meu_banco',
    username: 'root',
    password: '',
    port: 3306,
    charset: 'utf8mb4'
);

// 2. Criar instâncias
$connection = new PDOConnection($config);
$paginator = new Paginator();
$qb = new QueryBuilder($connection, $paginator);

// 3. Usar!
$result = $qb
    ->select('usuarios', ['id', 'nome', 'email'])
    ->where('ativo', SqlOperator::EQUALS, true)
    ->orderBy('nome', OrderDirection::ASC)
    ->limit(10)
    ->execute();

foreach ($result->data as $usuario) {
    echo "{$usuario['nome']} - {$usuario['email']}\n";
}

⚡ Performance & Benchmarks

Streaming de Grandes Volumes

Benchmark comparativo entre Omegaalfa Query Builder (execute(false)) e Laravel Eloquent (cursor()) para processamento de grandes datasets com unbuffered queries (streaming real).

📊 Ver Resultados Completos

Tabela Comparativa

Registros Omega Time (s) Omega Mem (MB) Laravel Time (s) Laravel Mem (MB) Verificação
10 0.145 0.000 0.024 4.000 ✅ 10/10
100 0.001 0.000 0.002 0.000 ✅ 100/100
1,000 0.003 0.000 0.008 0.000 ✅ 1K/1K
10,000 0.097 0.000 0.067 0.000 ✅ 10K/10K
100,000 0.189 2.000 0.653 0.000 ✅ 100K/100K
1,000,000 1.441 32.016 6.630 0.000 1M/1M

Gráfico de Performance (Tempo de Execução)

Processamento de 1 Milhão de Registros

Omegaalfa Query Builder:  ████                1.44s  (4.6x mais rápido)
Laravel Eloquent Cursor:  ████████████████████ 6.63s

Gráfico de Consumo de Memória

Memória Peak para 1 Milhão de Registros

Omegaalfa (streaming):    ████████████████████ 32.02 MB
Laravel (cursor):                              ~0.00 MB*

* Valores medidos pelo script de benchmark

🏆 Conclusões

  • Até 4.6x mais rápido que Laravel Eloquent para volumes acima de 1M de registros
  • Consumo de memória previsível - ~32MB constantes para streaming de 1M de registros
  • Escalabilidade linear - performance proporcional ao volume de dados
  • Ideal para: ETL, relatórios, exports, processamento batch, big data

📌 Condições do Teste

  • PHP: 8.4.1
  • Banco: MySQL 8.0 com big.csv (1M registros)
  • Ambiente: WSL2 Ubuntu
  • Método Omega: execute(false) - unbuffered query com Generator
  • Método Laravel: DB::cursor() - lazy collection streaming
  • Hardware: Ambiente de desenvolvimento padrão

⚠️ Nota: Resultados podem variar conforme hardware, configuração do PHP/MySQL e características dos dados. Benchmark disponível em teste.php para validação independente.

📖 Documentação Completa

📋 Índice de Métodos

Operações Básicas:

Cláusulas WHERE:

JOINs:

  • join() - Junção entre tabelas

Agrupamento e Ordenação:

Funções de Agregação:

Avançado:

🔨 Referência de Métodos

SELECT

select()

Inicia uma consulta SELECT.

public function select(string $table, array $fields = ['*']): self

Parâmetros:

  • $table - Nome da tabela
  • $fields - Array de colunas (default: ['*'])

Exemplo:

// SELECT básico
$qb->select('usuarios');

// Campos específicos
$qb->select('produtos', ['id', 'nome', 'preco']);

// Com funções SQL
$qb->select('pedidos', ['id', 'COUNT(*) as total', 'SUM(valor) as total_valor']);

// Com aliases de tabela
$qb->select('usuarios', ['u.id', 'u.nome', 'u.email'])->alias('u');

⚠️ Segurança: Para campos dinâmicos vindos de usuário, sempre use whitelist:

$allowedFields = ['id', 'nome', 'email'];
$userFields = array_intersect($_GET['fields'], $allowedFields);
$qb->select('usuarios', $userFields);

INSERT

insert()

Insere um único registro.

public function insert(string $table, array $data): self

Parâmetros:

  • $table - Nome da tabela
  • $data - Array associativo [coluna => valor]

Exemplo:

$qb->insert('usuarios', [
    'nome' => 'João Silva',
    'email' => 'joao@example.com',
    'ativo' => true,
    'criado_em' => new DateTime()
])->execute();

// Obter ID inserido
$novoId = $qb->getInsertId();
echo "Registro criado com ID: {$novoId}";

insertBatch()

Insere múltiplos registros de uma vez.

public function insertBatch(string $table, array $data): self

Parâmetros:

  • $table - Nome da tabela
  • $data - Array de arrays associativos

Exemplo:

$usuarios = [
    ['nome' => 'João', 'email' => 'joao@example.com'],
    ['nome' => 'Maria', 'email' => 'maria@example.com'],
    ['nome' => 'Pedro', 'email' => 'pedro@example.com']
];

$qb->insertBatch('usuarios', $usuarios)->execute();

⚠️ Importante: Todos os registros devem ter as mesmas colunas.

UPDATE

update()

Atualiza registros existentes.

public function update(string $table, array $data): self

Parâmetros:

  • $table - Nome da tabela
  • $data - Array associativo [coluna => novo valor]

Exemplo:

// Atualizar com WHERE
$qb->update('usuarios', [
    'nome' => 'João Santos',
    'atualizado_em' => new DateTime()
])
->where('id', SqlOperator::EQUALS, 123)
->execute();

// Atualizar múltiplos registros
$qb->update('produtos', ['ativo' => false])
   ->where('estoque', SqlOperator::EQUALS, 0)
   ->execute();

⚠️ Sempre use WHERE para evitar atualizar toda a tabela!

DELETE

delete()

Remove registros.

public function delete(string $table): self

Parâmetros:

  • $table - Nome da tabela

Exemplo:

// Deletar com WHERE
$qb->delete('usuarios')
   ->where('ativo', SqlOperator::EQUALS, false)
   ->where('ultimo_acesso', SqlOperator::LESS_THAN, '2020-01-01')
   ->execute();

// Deletar por ID
$qb->delete('produtos')
   ->where('id', SqlOperator::EQUALS, 456)
   ->execute();

⚠️ Sempre use WHERE para evitar deletar toda a tabela!

WHERE

where()

Adiciona condição WHERE (AND).

public function where(string $column, SqlOperator|string $operator, mixed $value): self

Parâmetros:

  • $column - Nome da coluna
  • $operator - Operador SQL (use enum SqlOperator)
  • $value - Valor para comparação

Operadores disponíveis:

  • SqlOperator::EQUALS - =
  • SqlOperator::NOT_EQUALS - <>
  • SqlOperator::GREATER_THAN - >
  • SqlOperator::LESS_THAN - <
  • SqlOperator::GREATER_THAN_OR_EQUALS - >=
  • SqlOperator::LESS_THAN_OR_EQUALS - <=
  • SqlOperator::LIKE - LIKE
  • SqlOperator::NOT_LIKE - NOT LIKE

Exemplo:

// Comparação simples
$qb->select('usuarios')
   ->where('ativo', SqlOperator::EQUALS, true)
   ->where('idade', SqlOperator::GREATER_THAN, 18);

// LIKE
$qb->select('produtos')
   ->where('nome', SqlOperator::LIKE, '%notebook%');

// Múltiplas condições (AND)
$qb->select('pedidos')
   ->where('status', SqlOperator::EQUALS, 'pendente')
   ->where('valor', SqlOperator::GREATER_THAN, 100)
   ->where('criado_em', SqlOperator::GREATER_THAN, '2024-01-01');

orWhere()

Adiciona condição WHERE (OR).

public function orWhere(string $column, SqlOperator|string $operator, mixed $value): self

Exemplo:

// WHERE status = 'pendente' OR status = 'processando'
$qb->select('pedidos')
   ->where('status', SqlOperator::EQUALS, 'pendente')
   ->orWhere('status', SqlOperator::EQUALS, 'processando');

// WHERE (categoria = 'A' OR categoria = 'B') AND ativo = true
$qb->select('produtos')
   ->where('categoria', SqlOperator::EQUALS, 'A')
   ->orWhere('categoria', SqlOperator::EQUALS, 'B')
   ->where('ativo', SqlOperator::EQUALS, true);

whereIn()

Verifica se valor está na lista.

public function whereIn(string $column, array $values): self

Exemplo:

// WHERE id IN (1, 2, 3, 4, 5)
$qb->select('usuarios')
   ->whereIn('id', [1, 2, 3, 4, 5]);

// WHERE status IN ('pendente', 'aprovado', 'processando')
$qb->select('pedidos')
   ->whereIn('status', ['pendente', 'aprovado', 'processando']);

whereNotIn()

Verifica se valor NÃO está na lista.

public function whereNotIn(string $column, array $values): self

Exemplo:

// WHERE status NOT IN ('cancelado', 'rejeitado')
$qb->select('pedidos')
   ->whereNotIn('status', ['cancelado', 'rejeitado']);

whereBetween()

Verifica se valor está entre dois valores.

public function whereBetween(string $column, array $range): self

Parâmetros:

  • $range - Array com 2 elementos: [min, max]

Exemplo:

// WHERE preco BETWEEN 100 AND 500
$qb->select('produtos')
   ->whereBetween('preco', [100, 500]);

// WHERE data BETWEEN '2024-01-01' AND '2024-12-31'
$qb->select('pedidos')
   ->whereBetween('criado_em', ['2024-01-01', '2024-12-31']);

whereNotBetween()

Verifica se valor NÃO está entre dois valores.

public function whereNotBetween(string $column, array $range): self

whereNull()

Verifica se coluna é NULL.

public function whereNull(string $column): self

Exemplo:

// WHERE deletado_em IS NULL
$qb->select('usuarios')
   ->whereNull('deletado_em');

whereNotNull()

Verifica se coluna NÃO é NULL.

public function whereNotNull(string $column): self

Exemplo:

// WHERE email IS NOT NULL
$qb->select('usuarios')
   ->whereNotNull('email');

JOIN

join()

Cria junção (JOIN) entre tabelas.

public function join(
    string $table, 
    string $key, 
    string $operator, 
    string $refer, 
    JoinType $type = JoinType::INNER
): self

Parâmetros:

  • $table - Tabela a ser unida
  • $key - Coluna da primeira tabela
  • $operator - Operador de comparação (=, <>, >, <, >=, <=)
  • $refer - Coluna da segunda tabela
  • $type - Tipo de JOIN (enum JoinType)

Tipos de JOIN:

  • JoinType::INNER - INNER JOIN (padrão)
  • JoinType::LEFT - LEFT JOIN
  • JoinType::RIGHT - RIGHT JOIN
  • JoinType::FULL - FULL JOIN (não suportado no MySQL)

Exemplo:

// INNER JOIN
$qb->select('usuarios', ['usuarios.id', 'usuarios.nome', 'pedidos.total'])
   ->join('pedidos', 'usuarios.id', '=', 'pedidos.usuario_id')
   ->where('pedidos.status', SqlOperator::EQUALS, 'concluido');

// LEFT JOIN
$qb->select('categorias', ['categorias.nome', 'COUNT(produtos.id) as total_produtos'])
   ->join('produtos', 'categorias.id', '=', 'produtos.categoria_id', JoinType::LEFT)
   ->groupBy('categorias.id');

// Múltiplos JOINs
$qb->select('pedidos', ['*'])
   ->join('usuarios', 'pedidos.usuario_id', '=', 'usuarios.id')
   ->join('enderecos', 'usuarios.id', '=', 'enderecos.usuario_id', JoinType::LEFT)
   ->where('pedidos.status', SqlOperator::EQUALS, 'pendente');

AGRUPAMENTO E ORDENAÇÃO

groupBy()

Agrupa resultados por coluna.

public function groupBy(string $column): self

Exemplo:

// Contar pedidos por status
$qb->select('pedidos', ['status', 'COUNT(*) as total'])
   ->groupBy('status');

// Múltiplos agrupamentos
$qb->select('vendas', ['ano', 'mes', 'SUM(valor) as total'])
   ->groupBy('ano')
   ->groupBy('mes');

having()

Filtra grupos (após GROUP BY).

public function having(string $column, SqlOperator $operator, mixed $value): self

Exemplo:

// Categorias com mais de 10 produtos
$qb->select('produtos', ['categoria_id', 'COUNT(*) as total'])
   ->groupBy('categoria_id')
   ->having('total', SqlOperator::GREATER_THAN, 10);

// Usuários com mais de 5 pedidos
$qb->select('pedidos', ['usuario_id', 'COUNT(*) as total_pedidos'])
   ->groupBy('usuario_id')
   ->having('total_pedidos', SqlOperator::GREATER_THAN, 5);

havingRaw()

HAVING com SQL customizado (use com cuidado).

public function havingRaw(string $condition): self

Exemplo:

$qb->select('pedidos', ['usuario_id', 'SUM(valor) as total_gasto'])
   ->groupBy('usuario_id')
   ->havingRaw('SUM(valor) > AVG(valor) * 2');

⚠️ ADVANCED API: Use apenas com SQL confiável, nunca com input de usuário!

orderBy()

Ordena resultados.

public function orderBy(string $column, OrderDirection $direction = OrderDirection::ASC): self

Parâmetros:

  • $column - Coluna para ordenar
  • $direction - Direção: OrderDirection::ASC ou OrderDirection::DESC

Exemplo:

// Ordenação simples
$qb->select('produtos')
   ->orderBy('nome', OrderDirection::ASC);

// Múltiplas ordenações
$qb->select('usuarios')
   ->orderBy('ativo', OrderDirection::DESC)
   ->orderBy('nome', OrderDirection::ASC);

// Ordenar por função
$qb->select('pedidos', ['*', 'DATE(criado_em) as data'])
   ->orderBy('data', OrderDirection::DESC);

limit()

Limita número de resultados (paginação).

public function limit(int $limit, int $offset = 0): self

Parâmetros:

  • $limit - Quantidade de registros (1-10000)
  • $offset - Posição inicial (0-1000000)

Exemplo:

// Primeiros 10 registros
$qb->select('produtos')->limit(10);

// Paginação: página 2, 20 por página
$page = 2;
$perPage = 20;
$offset = ($page - 1) * $perPage;
$qb->select('produtos')->limit($perPage, $offset);

// Com ordenação
$qb->select('usuarios')
   ->orderBy('criado_em', OrderDirection::DESC)
   ->limit(50);

FUNÇÕES DE AGREGAÇÃO

count()

Conta registros.

public function count(string $column = '*'): int

Exemplo:

// Total de usuários
$total = $qb->select('usuarios')->count();

// Total de usuários ativos
$ativos = $qb->select('usuarios')
             ->where('ativo', SqlOperator::EQUALS, true)
             ->count();

// Contar valores únicos
$categorias = $qb->select('produtos')->count('DISTINCT categoria_id');

sum()

Soma valores de uma coluna.

public function sum(string $column): float

Exemplo:

// Total de vendas
$total = $qb->select('pedidos')->sum('valor');

// Total de vendas aprovadas
$totalAprovado = $qb->select('pedidos')
                    ->where('status', SqlOperator::EQUALS, 'aprovado')
                    ->sum('valor');

avg()

Calcula média.

public function avg(string $column): float

Exemplo:

// Preço médio
$precoMedio = $qb->select('produtos')->avg('preco');

// Nota média de avaliações
$notaMedia = $qb->select('avaliacoes')
                ->where('produto_id', SqlOperator::EQUALS, 123)
                ->avg('nota');

min()

Retorna valor mínimo.

public function min(string $column): mixed

Exemplo:

// Menor preço
$menorPreco = $qb->select('produtos')
                 ->where('ativo', SqlOperator::EQUALS, true)
                 ->min('preco');

max()

Retorna valor máximo.

public function max(string $column): mixed

Exemplo:

// Maior preço
$maiorPreco = $qb->select('produtos')->max('preco');

// Data mais recente
$ultimaCompra = $qb->select('pedidos')
                   ->where('usuario_id', SqlOperator::EQUALS, 123)
                   ->max('criado_em');

exists()

Verifica se existe algum registro.

public function exists(): bool

Exemplo:

// Verificar se usuário existe
$existe = $qb->select('usuarios')
             ->where('email', SqlOperator::EQUALS, 'teste@example.com')
             ->exists();

if ($existe) {
    echo "Email já cadastrado!";
}

MÉTODOS AVANÇADOS

raw()

Executa query SQL customizada.

public function raw(string $query, array $params = []): self

Exemplo:

// Query complexa com CTE
$qb->raw('
    WITH vendas_por_mes AS (
        SELECT DATE_FORMAT(data, "%Y-%m") as mes, SUM(valor) as total
        FROM vendas
        WHERE ano = ?
        GROUP BY mes
    )
    SELECT * FROM vendas_por_mes WHERE total > ?
', [2024, 10000])->execute();

// Window functions
$qb->raw('
    SELECT 
        nome,
        valor,
        ROW_NUMBER() OVER (PARTITION BY categoria ORDER BY valor DESC) as ranking
    FROM produtos
')->execute();

⚠️ ADVANCED API: Use apenas com SQL estático confiável!

alias()

Define alias para a tabela principal.

public function alias(string $alias): self

Exemplo:

$qb->select('usuarios', ['u.id', 'u.nome'])
   ->alias('u')
   ->join('pedidos', 'u.id', '=', 'pedidos.usuario_id')
   ->where('u.ativo', SqlOperator::EQUALS, true);

transactional()

Executa código dentro de transação com rollback automático.

public function transactional(callable $callback): mixed

Exemplo:

try {
    $pedidoId = $qb->transactional(function($qb, $pdo) {
        // 1. Criar pedido
        $qb->insert('pedidos', [
            'usuario_id' => 123,
            'total' => 99.90,
            'status' => 'pendente'
        ])->execute();
        
        $pedidoId = $qb->getInsertId();
        
        // 2. Adicionar itens
        $qb->insertBatch('itens_pedido', [
            ['pedido_id' => $pedidoId, 'produto_id' => 1, 'qtd' => 2],
            ['pedido_id' => $pedidoId, 'produto_id' => 5, 'qtd' => 1]
        ])->execute();
        
        // 3. Atualizar estoque
        $qb->update('produtos', ['estoque' => 'estoque - 2'])
           ->where('id', SqlOperator::EQUALS, 1)
           ->execute();
        
        return $pedidoId;
    });
    
    echo "Pedido criado: {$pedidoId}";
    
} catch (Exception $e) {
    // Rollback automático já foi executado
    echo "Erro: {$e->getMessage()}";
}

cache()

Habilita cache de query.

public function cache(int $ttl = 3600): self

Parâmetros:

  • $ttl - Tempo de vida em segundos (default: 1 hora)

Exemplo:

// Cache por 1 hora
$result = $qb->select('configuracoes')
             ->cache(3600)
             ->execute();

// Cache por 5 minutos
$result = $qb->select('produtos')
             ->where('destaque', SqlOperator::EQUALS, true)
             ->cache(300)
             ->execute();

// Cache por 24 horas
$result = $qb->select('categorias')
             ->cache(86400)
             ->execute();

⚠️ Importante: O cache não isola por usuário. Para dados sensíveis, sempre filtre por usuario_id:

$result = $qb->select('pedidos')
             ->where('usuario_id', SqlOperator::EQUALS, $currentUserId)
             ->cache(3600)
             ->execute();

execute()

Executa a query e retorna resultados.

public function execute(bool $bufferedQuery = true): QueryResultDTO

Retorno:

class QueryResultDTO {
    public iterable $data;      // Resultados (array ou Generator)
    public int $count;          // Número de linhas afetadas
    public ?PaginationDTO $pagination;  // Dados de paginação (se limit usado)
}

Exemplo:

$result = $qb->select('usuarios')->execute();

// Iterar resultados
foreach ($result->data as $usuario) {
    echo "{$usuario['nome']}\n";
}

// Informações
echo "Total: {$result->count} registros\n";

// Paginação (se usou limit)
if ($result->pagination) {
    echo "Página {$result->pagination->currentPage} de {$result->pagination->totalPages}\n";
    echo "Total de itens: {$result->pagination->totalItems}\n";
}

getQuerySql()

Retorna SQL gerado (sem substituir parâmetros).

public function getQuerySql(): string

Exemplo:

$sql = $qb->select('usuarios')
          ->where('ativo', SqlOperator::EQUALS, true)
          ->getQuerySql();

echo $sql;
// SELECT * FROM `usuarios` WHERE `ativo` = :param0

toSql()

SQL formatado para debug (com valores substituídos).

public function toSql(bool $withParams = false): string

Exemplo:

$qb->select('usuarios')
   ->where('id', SqlOperator::EQUALS, 123)
   ->where('nome', SqlOperator::LIKE, '%João%');

// SQL com placeholders
echo $qb->toSql();
// SELECT * FROM `usuarios` WHERE `ativo` = :param0 AND `nome` LIKE :param1

// SQL com valores (apenas para debug!)
echo $qb->toSql(true);
// SELECT * FROM `usuarios` WHERE `ativo` = 123 AND `nome` LIKE '%João%'

⚠️ Apenas para debug! Nunca use toSql(true) em produção.

explain()

Analisa performance da query (EXPLAIN).

public function explain(): array

Exemplo:

$analysis = $qb->select('usuarios')
               ->join('pedidos', 'usuarios.id', '=', 'pedidos.usuario_id')
               ->where('usuarios.ativo', SqlOperator::EQUALS, true)
               ->explain();

print_r($analysis);
// [
//   ['id' => 1, 'select_type' => 'SIMPLE', 'table' => 'usuarios', 'type' => 'ALL', ...],
//   ['id' => 1, 'select_type' => 'SIMPLE', 'table' => 'pedidos', 'type' => 'ref', ...]
// ]

📝 Exemplos Completos

Exemplo 1: CRUD Completo

use Omegaalfa\QueryBuilder\enums\SqlOperator;
use Omegaalfa\QueryBuilder\enums\OrderDirection;

// CREATE
$qb->insert('produtos', [
    'nome' => 'Notebook Dell',
    'preco' => 3500.00,
    'estoque' => 10,
    'categoria_id' => 1,
    'ativo' => true
])->execute();

$produtoId = $qb->getInsertId();

// READ
$produto = $qb->select('produtos')
              ->where('id', SqlOperator::EQUALS, $produtoId)
              ->execute();

// UPDATE
$qb->update('produtos', ['preco' => 3200.00, 'estoque' => 8])
   ->where('id', SqlOperator::EQUALS, $produtoId)
   ->execute();

// DELETE
$qb->delete('produtos')
   ->where('id', SqlOperator::EQUALS, $produtoId)
   ->execute();

Exemplo 2: Relatório com JOINs e Agregações

// Relatório de vendas por categoria
$result = $qb->select('categorias', [
    'categorias.nome as categoria',
    'COUNT(vendas.id) as total_vendas',
    'SUM(vendas.valor) as valor_total',
    'AVG(vendas.valor) as ticket_medio'
])
->join('produtos', 'categorias.id', '=', 'produtos.categoria_id')
->join('vendas', 'produtos.id', '=', 'vendas.produto_id')
->whereBetween('vendas.data', ['2024-01-01', '2024-12-31'])
->groupBy('categorias.id')
->having('total_vendas', SqlOperator::GREATER_THAN, 100)
->orderBy('valor_total', OrderDirection::DESC)
->execute();

foreach ($result->data as $row) {
    echo "{$row['categoria']}: ";
    echo "R$ " . number_format($row['valor_total'], 2) . " ";
    echo "({$row['total_vendas']} vendas)\n";
}

Exemplo 3: Paginação Completa

$page = $_GET['page'] ?? 1;
$perPage = 20;
$offset = ($page - 1) * $perPage;

$result = $qb->select('usuarios', ['id', 'nome', 'email', 'criado_em'])
             ->where('ativo', SqlOperator::EQUALS, true)
             ->orderBy('criado_em', OrderDirection::DESC)
             ->limit($perPage, $offset)
             ->execute();

// Exibir resultados
foreach ($result->data as $usuario) {
    echo "{$usuario['nome']} ({$usuario['email']})\n";
}

// Informações de paginação
$pg = $result->pagination;
echo "\nPágina {$pg->currentPage} de {$pg->totalPages}\n";
echo "Mostrando {$result->count} de {$pg->totalItems} usuários\n";

// Links de navegação
if ($pg->currentPage > 1) {
    echo "<a href='?page=" . ($pg->currentPage - 1) . "'>← Anterior</a> ";
}
if ($pg->currentPage < $pg->totalPages) {
    echo "<a href='?page=" . ($pg->currentPage + 1) . "'>Próxima →</a>";
}

Exemplo 4: Transação Complexa

try {
    $resultado = $qb->transactional(function($qb, $pdo) use ($usuarioId, $carrinho) {
        // 1. Criar pedido
        $qb->insert('pedidos', [
            'usuario_id' => $usuarioId,
            'status' => 'pendente',
            'total' => array_sum(array_column($carrinho, 'subtotal')),
            'criado_em' => new DateTime()
        ])->execute();
        
        $pedidoId = $qb->getInsertId();
        
        // 2. Adicionar itens do pedido
        $itens = [];
        foreach ($carrinho as $item) {
            $itens[] = [
                'pedido_id' => $pedidoId,
                'produto_id' => $item['produto_id'],
                'quantidade' => $item['quantidade'],
                'preco_unitario' => $item['preco'],
                'subtotal' => $item['subtotal']
            ];
        }
        $qb->insertBatch('itens_pedido', $itens)->execute();
        
        // 3. Atualizar estoque
        foreach ($carrinho as $item) {
            // Verificar estoque disponível
            $produto = $qb->select('produtos', ['estoque'])
                          ->where('id', SqlOperator::EQUALS, $item['produto_id'])
                          ->execute();
            
            $estoque = $produto->data[0]['estoque'];
            if ($estoque < $item['quantidade']) {
                throw new Exception("Estoque insuficiente para produto {$item['produto_id']}");
            }
            
            // Decrementar estoque
            $pdo->exec("
                UPDATE produtos 
                SET estoque = estoque - {$item['quantidade']}
                WHERE id = {$item['produto_id']}
            ");
        }
        
        // 4. Limpar carrinho
        $qb->delete('carrinho')
           ->where('usuario_id', SqlOperator::EQUALS, $usuarioId)
           ->execute();
        
        return $pedidoId;
    });
    
    echo "Pedido #{$resultado} criado com sucesso!";
    
} catch (Exception $e) {
    echo "Erro ao processar pedido: {$e->getMessage()}";
}

Exemplo 5: Query Complexa com Subqueries

// Produtos mais vendidos do mês com informações de categoria
$result = $qb->raw('
    SELECT 
        p.id,
        p.nome,
        c.nome as categoria,
        p.preco,
        vendas.total_vendido,
        vendas.receita_total
    FROM produtos p
    INNER JOIN categorias c ON p.categoria_id = c.id
    INNER JOIN (
        SELECT 
            produto_id,
            SUM(quantidade) as total_vendido,
            SUM(subtotal) as receita_total
        FROM itens_pedido ip
        INNER JOIN pedidos ped ON ip.pedido_id = ped.id
        WHERE 
            ped.status = ? AND
            MONTH(ped.criado_em) = MONTH(CURRENT_DATE) AND
            YEAR(ped.criado_em) = YEAR(CURRENT_DATE)
        GROUP BY produto_id
    ) vendas ON p.id = vendas.produto_id
    ORDER BY vendas.receita_total DESC
    LIMIT 10
', ['concluido'])->execute();

foreach ($result->data as $produto) {
    echo "{$produto['nome']} ({$produto['categoria']})\n";
    echo "  Vendidos: {$produto['total_vendido']}\n";
    echo "  Receita: R$ " . number_format($produto['receita_total'], 2) . "\n\n";
}

🧪 Testes

O projeto possui cobertura completa de testes com PHPUnit.

Executar Testes

# Todos os testes
vendor/bin/phpunit

# Com cobertura
vendor/bin/phpunit --coverage-html coverage/

# Testes específicos
vendor/bin/phpunit tests/QueryBuilder/QueryBuilderSecurityTest.php

# Análise estática (PHPStan)
composer phpstan

Suítes de Teste

  • QueryBuilderUnitTest - Testes unitários básicos
  • QueryBuilderIntegrationTest - Testes de integração
  • QueryBuilderSecurityTest - Testes de segurança
  • QueryBuilderBindingTest - Testes de parameter binding
  • QueryBuilderCacheTest - Testes de cache
  • QueryBuilderOperationsTest - Testes de operações SQL
  • QueryBuilderExecutionTest - Testes de execução
  • QueryBuilderFullTest - Testes end-to-end

Status: 73 testes, 145 assertions, 0 failures

🔒 Segurança

Arquitetura Segura

Este Query Builder foi desenvolvido com segurança por design:

100% Prepared Statements para valores
Type-safe binding (PDO::PARAM_INT, PDO::PARAM_NULL, etc)
Strict types habilitado em todos os arquivos
Validação de operadores em JOINs
Enums tipadas para operadores SQL
Quote automático de identificadores (Identifier Injection Protection) ✅ Isolamento de Cache por contexto de conexão (Multi-tenant safe)

Documentação de Segurança

Uso Seguro

// ✅ SEGURO - Valores sempre com prepared statements
$qb->where('email', SqlOperator::EQUALS, $_POST['email']);

// ✅ SEGURO - Whitelist para campos dinâmicos
$allowedFields = ['id', 'nome', 'email'];
$fields = array_intersect($_GET['fields'], $allowedFields);
$qb->select('usuarios', $fields);

// ⚠️ CUIDADO - O método select() agora aplica quoteIdentifier automaticamente,
// mas ainda é recomendado validar input de usuário para evitar erros de lógica.
$qb->select('usuarios', $_GET['fields']); 

Reportar Vulnerabilidades

🔒 Não reporte vulnerabilidades via issues públicas!

Resposta garantida em 24-48 horas.

🗺️ Roadmap

✅ v1.0 (Atual)

  • Query Builder completo
  • Prepared statements obrigatórios
  • Enums tipadas
  • Cache integrado
  • Transações com savepoints
  • Multi-driver
  • 73 testes automatizados
  • Auditoria de segurança

🚧 v1.1 (Planejado)

  • Query Builder para migrations
  • Soft deletes
  • Events/Observers
  • Connection pooling
  • Context-aware cache

💡 v2.0 (Futuro)

  • Query caching com Redis/Memcached
  • Read replicas
  • Sharding support
  • GraphQL query builder
  • Performance profiler visual

🐛 Reportando Bugs

Encontrou um bug? Ajude-nos a melhorar!

Como Reportar

  1. Verifique se já não foi reportado nas Issues
  2. Abra uma nova issue com:
### Descrição
[Descreva o problema]

### Reprodução
1. Configure conexão com MySQL 8.0
2. Execute: $qb->select(...)->execute()
3. Observe erro: [mensagem]

### Esperado vs Atual
Esperado: [comportamento correto]
Atual: [o que está acontecendo]

### Ambiente
- PHP: 8.4.x
- Query Builder: 1.0.x
- Driver: MySQL/PostgreSQL/SQLite
- SO: Linux/Windows/macOS

🤝 Contribuindo

Contribuições são muito bem-vindas!

Como Contribuir

  1. Fork o repositório
  2. Crie uma branch: git checkout -b feature/MinhaFeature
  3. Commit: git commit -m 'Adiciona MinhaFeature'
  4. Push: git push origin feature/MinhaFeature
  5. Abra um Pull Request

Diretrizes

  • ✅ Siga PSR-12
  • ✅ Adicione testes para novas features
  • ✅ Mantenha strict_types
  • ✅ Documente com PHPDoc
  • ✅ Execute composer phpstan antes de commitar
  • ✅ Atualize README se necessário

📄 Licença

Distribuído sob a Licença MIT. Veja LICENSE para detalhes.

✅ Uso comercial
✅ Modificação
✅ Distribuição
✅ Uso privado
⚠️ Sem garantia
⚠️ Atribuição obrigatória

💬 Contato e Suporte

Autor

Omegaalfa

Comunidade

⭐ Mostre seu Apoio

Se este projeto foi útil:

  • ⭐ Dê uma estrela no GitHub
  • 🐦 Compartilhe com a comunidade
  • 🤝 Contribua com código ou documentação
  • Apoie via GitHub Sponsors

Desenvolvido com ❤️ por Omegaalfa

Stars Forks

⬆ Voltar ao topo

💬 Contato

Criado por Omegaalfa.
Para dúvidas ou sugestões: github.com/omegaalfa

统计信息

  • 总下载量: 17
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-08