Arquitectura
Sistema de Traits
Composição de funcionalidades reutilizáveis através do padrão Mixin — 11 traits especializados organizados em 3 categorias para extensibilidade, debugging, gestão de memória e processamento de dados.
Visão Geral
O sistema de traits do OfficeGest implementa o padrão Mixin através de traits PHP 8.3+, permitindo composição de funcionalidades reutilizáveis entre classes sem herança múltipla.
| Trait | Responsabilidade |
|---|---|
| Macroable | Extensão dinâmica de classes em runtime |
| Dumpable | Debugging com rastreamento de origem |
| Filterable | Normalização de filtros de entrada |
| EnumCaseToArray | Conversão de enums para arrays/SQL |
| MemoryOptimizedTrait | Processamento streaming de grandes datasets |
| MemoryManagementTrait | Gestão e monitoramento de memória |
| ErrorHandlingTrait | Handling inteligente de erros em jobs |
| ResolvesDumpSource | Resolução de origem em debugging |
| DeadLetterQueueTrait | Gestão de Dead Letter Queue |
| ConditionalAttributesTrait | Atributos condicionais em Resources |
| DateSerializerTrait | Serialização de datas em Resources |
Arquitectura e Padrões
Mixin Pattern
Traits adicionam conjuntos coesos de métodos e propriedades sem estabelecer hierarquias rígidas.
Composition over Inheritance
Classes combinam múltiplos traits para compor funcionalidades, evitando problemas de herança múltipla.
Exemplo de Composição
<?php
class AdvancedProcessor
{
use MemoryOptimizedTrait; // Processamento de grandes dados
use MemoryManagementTrait; // Monitoramento de memória
use ErrorHandlingTrait; // Handling robusto de erros
use Dumpable; // Debugging
public function process(): void
{
$this->configureMemoryLimits('2G');
$this->withLenientErrorHandling(function () {
foreach ($this->processRecordsInChunks('data', ['*']) as $record) {
$this->processRecord($record);
}
});
}
}
Core Traits
Localizados em Modules/Common/Traits/, são os traits fundamentais do framework.
Macroable
Implementa o padrão Macro, permitindo adicionar métodos a classes dinamicamente em runtime.
| Método | Descrição |
|---|---|
| macro(string $name, callable $macro) | Regista nova macro |
| mixin(object $mixin, bool $replace) | Adiciona todos os métodos de um objeto |
| hasMacro(string $name) | Verifica se macro existe |
// Extensão dinâmica em runtime
Request::macro('isApiRequest', function(): bool {
return $this->wantsJson() || $this->expectsJson();
});
Request::macro('tenantId', function(): ?int {
return $this->header('X-Tenant-ID') ?: session('tenant_id');
});
// Uso posterior
$request = Request::capture();
if ($request->isApiRequest()) {
// Lógica específica para API
}
Dumpable
Sistema avançado de debugging com rastreamento automático de origem (ficheiro + linha).
| Método | Retorno | Descrição |
|---|---|---|
| dump(?string $message) | $this | Dump com mensagem, continua |
| dd(?string $message) | void | Dump e termina |
| dumpOnly(string ...$props) | $this | Dump de propriedades específicas |
| dumpWith(string $message) | $this | Dump com mensagem, continua |
class CustomerService
{
use Dumpable;
private int $id;
private string $name;
public function process(): void
{
// Debug completo com contexto
$this->dumpWith('Processing customer');
// Output: "Processing customer"
// 🔍 Called from: /path/to/file.php:42
// Debug apenas propriedades específicas
$this->dumpOnly('id', 'name');
// Chaining para debug durante desenvolvimento
$this->dumpWith('Before save')->save()->dumpWith('After save');
}
}
EnumCaseToArray
Trait especializado para enums PHP 8.1+ com conversões para arrays, SQL CASE statements e formatos específicos.
enum CustomerType: string
{
use EnumCaseToArray;
case COMPANY = 'E';
case PRIVATE = 'P';
public function label(): string
{
return match($this) {
self::COMPANY => 'Empresa',
self::PRIVATE => 'Particular',
};
}
}
// Array de valores
CustomerType::valuesToArray();
// ['E', 'P']
// Labels para UI
CustomerType::labels();
// ['COMPANY' => 'Empresa', ...]
// Formato Select2
CustomerType::toArraySelect2();
// [['value' => 'E', 'text' => 'Empresa'], ...]
// SQL CASE
CustomerType::toSqlCase('type', '= ?');
ErrorHandlingTrait
Sistema robusto de handling de erros para jobs críticos. Categoriza erros, notifica via Mattermost e fornece logging estruturado.
class SaftExportJob extends Job
{
use ErrorHandlingTrait;
public function handle(): void
{
$this->withLenientErrorHandling(function () {
// Erros menores (array offset, undefined key) são ignorados
// Erros críticos (connection refused, API error) param o job
// Notificações Mattermost enviadas automaticamente
$result = app('saftpt.export')->execute($this->data);
if (!$result['success']) {
throw new \Exception(json_encode($result['errors']));
}
});
}
}
Padrões de Erro: O trait define 28+ padrões fatais (undefined function, memory exhausted, connection refused) e keywords críticos que triggeram notificações automáticas.
MemoryOptimizedTrait
Processamento usando Generators para grandes datasets. Processa milhões de registos sem esgotar memória.
| Método | Descrição |
|---|---|
| processRecordsInChunks() | Processa registos DB em chunks via generator |
| processCustomQueryInChunks() | Processa SQL customizado em chunks |
| streamCSVFromGenerator() | Gera CSV streaming |
| writeDataToFile() | Escreve dataset grande para ficheiro |
| getOptimalChunkSize() | Calcula chunk size ideal baseado na memória |
class LargeReportGenerator
{
use MemoryOptimizedTrait;
public function generateOrdersReport(): array
{
// Processa 100k+ orders sem esgotar memória
$generator = $this->processRecordsInChunks(
table: 'orders',
select: ['id', 'customer_id', 'total'],
conditions: ['status' => 'completed'],
chunkSize: 1000
);
// Escreve para ficheiro CSV
return $this->writeDataToFile(
dataGenerator: $generator,
filePath: '/tmp/orders_report.csv',
format: 'csv'
);
// Retorna: ['records_written' => 150000, 'file_size_bytes' => 8500000]
}
}
MemoryManagementTrait
Gestão completa de memória com monitoring, thresholds (75% warning, 90% critical) e optimização automática.
| Método | Descrição |
|---|---|
| configureMemoryLimits() | Define limite PHP |
| checkMemoryThreshold() | Verifica threshold |
| optimizeMemoryUsage() | Força GC |
| getMemoryStats() | Estatísticas atuais |
| processInBatches() | Lotes com optimização |
$this->configureMemoryLimits('2G');
$this->logMemoryUsage('Start');
$this->processInBatches(
data: $records,
processor: fn($record) => $this->migrate($record),
batchSize: 100
);
$stats = $this->getMemoryStats();
// ['current_mb' => 245.5, 'peak_mb' => 512.3]
Filterable
Normaliza parâmetros de filtro em requests, transformando formatos simples em estruturas padronizadas.
// Input simples
['filter' => ['status' => 'active']]
// Normalizado para
['filter' => ['status' => ['value' => 'active', 'operator' => 'equal']]]
ResolvesDumpSource
Resolve a origem real de dumps através de call stacks complexos. Usado pelos custom CliDumper e HtmlDumper para integração com Symfony VarDumper.
Queue Traits
Localizados em Modules/Common/Queue/Traits/.
DeadLetterQueueTrait
Gestão de jobs que falham permanentemente, movendo-os para uma Dead Letter Queue para análise posterior.
| Método | Descrição |
|---|---|
| moveToDeadLetter() | Move job falhado para DLQ |
| retryDeadLetter(string $id) | Tenta reprocessar job da DLQ |
| purgeDeadLetters(int $maxAge) | Remove jobs antigos da DLQ (default: 7 dias) |
protected function moveToDeadLetter(
string $jobId,
string $queue,
string $payload,
Throwable $exception
): void {
$dlq = app(DeadLetterQueueInterface::class);
$dlq->store($connection, $queue, $payload, $originalPayload, $exception);
}
// Retry de job específico
public function retryDeadLetter(string $deadLetterId): ?string
{
$dlq = app(DeadLetterQueueInterface::class);
return $dlq->retry((int)$deadLetterId) ? $deadLetterId : null;
}
Resource Traits
Localizados em Modules/Common/Resource/Traits/, para transformação de dados em API Resources.
ConditionalAttributesTrait
Sistema de atributos condicionais para API Resources, inspirado no Laravel.
| Método | Descrição |
|---|---|
| whenHas(mixed $value, callable $cb) | Inclui se existe e não está vazio |
| when(mixed $condition, callable $cb) | Inclui condicionalmente |
| mergeWhen(mixed $cond, mixed $value) | Merge condicional de arrays |
class CustomerResource extends Resource
{
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
// Incluir apenas se existe
'email' => $this->whenHas('email'),
// Com transformação
'phone' => $this->whenHas('phone', fn($phone) => "+351 {$phone}"),
// Merge condicional de relacionamentos
...$this->mergeWhen($this->relationLoaded('orders'), [
'orders_count' => $this->orders->count(),
'total_spent' => $this->orders->sum('total'),
]),
];
}
}
DateSerializerTrait
Serialização consistente de datas em Resources, usando o formato configurado em config('app.date_format').
protected static function serializeValue($value)
{
// Carbon formata para string
if ($value instanceof Carbon) {
return $value->format(config('app.date_format') ?? 'Y-m-d H:i:s');
}
// Collections recursivas
if ($value instanceof Collection) {
return $value->map(fn($item) => self::serializeValue($item));
}
// Arrays recursivos (remove MissingValues)
if (is_array($value)) {
return array_filter(
array_map(fn($item) => self::serializeValue($item), $value),
fn($item) => !($item instanceof MissingValue)
);
}
return $value;
}
Boas Práticas
✅ Recomendações
- • Single Responsibility: Cada trait com uma função clara
- • Nomes únicos: Prefixar métodos se necessário
- • Propriedades privadas: Evitar conflitos
- • Documentar dependências: Se trait precisa de propriedades da classe
- • Retornar $this: Para method chaining
❌ Evitar
- • Sobrescrever métodos sem verificar existência
- • Propriedades públicas que podem conflitar
- • Nomes genéricos (get, set, process)
- • Dependências não documentadas
- • Traits com múltiplas responsabilidades
Template para Novos Traits
<?php
namespace Og\Modules\Common\Traits;
/**
* Trait ExampleTrait
*
* Descrição clara do propósito do trait.
*/
trait ExampleTrait
{
/**
* Propriedades devem ser privadas quando possível
*/
private int $counter = 0;
/**
* Métodos públicos bem documentados
*
* @param mixed $input Descrição do parâmetro
* @return self Para method chaining
*/
public function publicMethod(mixed $input): self
{
$this->internalHelper($input);
return $this;
}
/**
* Verificar existência de métodos/propriedades opcionais
*/
protected function optionalIntegration(): void
{
if (method_exists($this, 'optionalMethod')) {
$this->optionalMethod();
}
}
private function internalHelper(mixed $input): void
{
// Lógica interna
}
}
Referência Rápida
| Trait | Namespace | Uso Principal |
|---|---|---|
| Macroable | Og\Modules\Common\Traits | Extensão dinâmica de classes |
| Dumpable | Og\Modules\Common\Traits | Debugging com contexto |
| Filterable | Og\Modules\Common\Traits | Normalização de filtros API |
| EnumCaseToArray | Og\Modules\Common\Traits | Conversão de enums |
| ErrorHandlingTrait | Og\Modules\Common\Traits | Handling de erros em jobs |
| MemoryOptimizedTrait | Og\Modules\Common\Traits | Processamento streaming |
| MemoryManagementTrait | Og\Modules\Common\Traits | Gestão de memória |
| ResolvesDumpSource | Og\Modules\Common\Traits | Origem de dumps |
| DeadLetterQueueTrait | Og\Modules\Common\Queue\Traits | Dead Letter Queue |
| ConditionalAttributesTrait | Og\Modules\Common\Resource\Traits | Atributos condicionais |
| DateSerializerTrait | Og\Modules\Common\Resource\Traits | Serialização de datas |