Persistência e Repositórios¶
A camada de persistência do MIDDAG adota o Repository Pattern para abstrair completamente o acesso ao banco de dados ($DB global do Moodle).
A regra de ouro é: Nenhum SQL deve existir fora de um Repositório. Controllers e Services nunca devem escrever queries SQL diretamente.
Arquitetura de Acesso a Dados¶
O sistema divide a responsabilidade de persistência em três componentes distintos, garantindo que a lógica de "como salvar" não se misture com a lógica de "o que salvar".
classDiagram
direction LR
class ServiceLayer {
+create_item(dto)
+find_courses(query)
}
class ItemRepository {
<>
+create(dto)
+update(dto)
+delete(id)
+find_by_id(id)
}
class ItemSearchRepository {
<>
+run_query(criteria)
+count_query(criteria)
+load_metadata_bulk()
}
class ItemMapper {
<>
+db_to_domain(stdClass)
+domain_to_db(Item)
}
ServiceLayer --> ItemRepository : Comandos (Write)
ServiceLayer --> ItemSearchRepository : Consultas (Read)
ItemRepository --> ItemMapper : Usa
ItemSearchRepository --> ItemMapper : Usa
Criar um nome para o tópico que engloba os 3 tópicos abaixo¶
1. Data Mapper (item_mapper)¶
O Mapper (local_middag\core\repository\mapper\item_mapper) é o tradutor universal. Ele não acessa o banco de dados; sua única função é converter estruturas de dados.
Responsabilidades:¶
- Hidratação (DB → Domain): Converte um registro bruto (
stdClass) e um array de metadados em uma Entidade Imutável (item). - Extração (Domain → DB): Converte uma Entidade em um objeto simples para ser salvo pelo Moodle (
$DB->insert_record). - Polimorfismo: Usa o registro
item_typespara decidir qual classe instanciar baseada no campotypedo banco (ex: setype='course', instanciacourse_item).
2. Repositório de Escrita (item_repository)¶
Este repositório foca em operações atômicas de CRUD (Create, Read, Update, Delete) para itens individuais. É aqui que a integridade dos dados é garantida.
Transações Atômicas¶
O MIDDAG armazena dados em duas tabelas principais: middag_items (dados fixos) e middag_itemmeta (dados dinâmicos). O Repositório gerencia transações ($DB->start_delegated_transaction()) automaticamente.
Ao chamar create($dto) ou update($dto), o repositório garante que ou tudo é salvo (item + metadados) ou nada é salvo (rollback), prevenindo dados órfãos.
Exemplo de Uso:
<?php
use local_middag\core\middag;
use local_middag\core\repository\item_repository;
use local_middag\core\dto\item_dto;
$repo = middag::get(item_repository::class);
// Criação segura via DTO
$dto = new item_dto(
fullname: 'Novo Item',
type: 'generic',
metadata: ['sku' => 'ABC-99']
);
$item = $repo->create($dto); // Retorna entidade hidratada
3. Repositório de Leitura (item_search_repository)¶
Enquanto o item_repository lida com itens únicos, o item_search_repository é o motor de SQL responsável por buscas complexas, listagens e relatórios.
Ele atua como o "motor" por trás do Query Builder.
Recursos Avançados:¶
- Construção Dinâmica de SQL: Monta cláusulas
WHERE,JOINeORDER BYde forma segura. - Paginação Real: Executa queries de contagem (
COUNT) e seleção (SELECT) sincronizadas. - Carregamento em Massa (Bulk Loading): Resolve o problema de N+1 Queries. Ao buscar 50 itens, ele faz apenas 1 query adicional para buscar os metadados de todos os 50 itens de uma vez, em vez de 50 queries separadas.
O Problema N+1 e Solução¶
Em implementações ingênuas no Moodle, carregar metadados costuma ser lento:
<?php
// ABORDAGEM RUIM (NÃO USADA NO MIDDAG)
foreach ($items as $item) {
// Executa uma query SQL para cada item no loop!
$meta = $DB->get_records('middag_itemmeta', ['itemid' => $item->id]);
}
Abordagem MIDDAG (item_search_repository):
- Busca os IDs dos itens principais (1 Query).
- Busca todos os metadados onde
itemid IN (ids...)(1 Query). - Hidrata os objetos em memória.
Resultado: Uma lista de 1.000 itens é carregada com apenas 2 consultas ao banco, independente da quantidade de metadados.
Boas Práticas para Desenvolvedores¶
- Sempre use DTOs para escrita: Nunca passe arrays soltos para o
createouupdate. O DTO (item_dto) garante que você saiba quais campos são aceitos. - Não instancie o Mapper: Deixe o Container de Injeção de Dependência injetar o
item_mapperdentro do repositório automaticamente. - Prefira o Query Builder: Evite chamar
item_search_repositorymanualmente. Use a Query Factory para construir suas buscas de forma fluente e legível.