Pular para conteúdo

Query Builder e Motor de Busca

O Query Builder do MIDDAG é uma ferramenta poderosa para construção de consultas complexas de forma fluente, segura e orientada a objetos.

Ele substitui a necessidade de escrever SQL manual ($DB->get_records_sql) dentro de Services e Controllers, oferecendo uma abstração de alto nível que lida automaticamente com paginação, joins de metadados e hidratação de objetos.


Arquitetura do Componente

O sistema de busca é dividido em 4 partes para garantir a Imutabilidade e a separação de responsabilidades:

[Image of Query Builder Architecture Diagram]

  1. Factory (factory): O ponto de entrada. Cria instâncias de queries e oferece atalhos sintáticos.
  2. Query Object (query): Um objeto de valor imutável que armazena o que deve ser buscado (filtros, ordens), mas não sabe como buscar.
  3. Executor (executor): O "motorista". Pega o objeto Query, traduz para comandos do Repositório e coordena a execução.
  4. Result (result): Um container iterável que guarda os resultados, contagem total e metadados de paginação.

Construindo Consultas (Fluent Interface)

A forma recomendada de iniciar uma busca é injetando o serviço local_middag\core\service\item\item_search_service.

Exemplo Básico

<?php

use local_middag\core\middag;
use local_middag\core\service\item\item_search_service;
use local_middag\core\enum\operator;

// 1. Obter o serviço de busca
$search = middag::get(item_search_service::class);

// 2. Construir a query (Imutável!)
$query = $search->newQuery()
    ->domain(course_item::class)       // Define o tipo de retorno
    ->where('status', operator::EQ, 'published')
    ->where('visible', operator::EQ, 1)
    ->orderBy('timecreated DESC')
    ->paginate(0, 20); // Página 0, 20 itens

// 3. Executar
$result = $search->search($query);

// 4. Usar os dados
echo "Total encontrado: " . $result->total();

foreach ($result as $item) {
    echo $item->get_fullname();
}

Imutabilidade: Um Alerta Importante

O objeto query é imutável. Cada método chamado retorna uma nova instância modificada.

Forma Errada (Não funciona):

<?php

$query = $search->newQuery();
$query->where('id', 1); // O retorno é ignorado!
$result = $search->search($query); // Executa uma query vazia (todos os itens)

Forma Correta:

<?php

$query = $search->newQuery();
$query = $query->where('id', 1); // Atribui o novo estado
$result = $search->search($query);

Ou encadeado (Fluent):

<?php

$query = $search->newQuery()->where('id', 1);

Filtros Avançados

O builder suporta operadores complexos e filtros em tabelas relacionadas (metadados).

Metadados (Metadata)

Não é necessário fazer JOINs manuais. O builder abstrai isso:

<?php

$query = $query
    ->whereMeta('sku', operator::LIKE, '%PROD%')
    ->whereMeta('price', operator::GTE, 100);

Operadores Disponíveis

Use o enum local_middag\core\enum\operator para type-safety:

Operador SQL Equivalente
operator::EQ =
operator::NEQ !=
operator::GT / GTE > / >=
operator::LT / LTE < / <=
operator::LIKE LIKE
operator::IN IN (...)
operator::BETWEEN BETWEEN ? AND ?

Eager Loading (Carregamento Ansioso)

Para evitar o problema de performance N+1, você pode instruir o builder a carregar relações e metadados adicionais em lote.

Carregando Metadados

Se você precisa exibir o metadado cpf na listagem, peça antecipadamente:

<?php

$query = $query->withMetadata(['cpf', 'telefone']);

Isso garante que, ao acessar $item->get_meta('cpf'), o valor já esteja na memória, sem disparar nova query.

Carregando Relações

Se o seu item possui relações (ex: parent, children, courses), carregue-as junto:

<?php

$query = $query->withRelation('children');

O executor usará o relation_loader para buscar todos os filhos de todos os itens encontrados em uma única consulta adicional.


Objeto Result

O retorno local_middag\core\search\result é um container inteligente.

<?php

// Iterável (como um array)
foreach ($result as $item) { ... }

// Contagem da página atual
count($result); // ex: 20

// Contagem total no banco (para paginação)
$result->total(); // ex: 5340

// Acesso direto
$first = $result->first();
$last = $result->last();

// Verificar vazio
if ($result->isEmpty()) { ... }

Modos de Retorno

Por padrão, o executor hidrata Objetos de Domínio (item). Se você precisa de performance bruta e apenas dados para leitura (ex: exportação CSV), use o modo stdClass:

<?php

$query = $query->asStdClass(true);
// O resultado conterá objetos stdClass simples, economizando memória/CPU de hidratação.