OG Framework
OG Framework Documentação
Voltar para Documentação

API

Resources (Output DTOs)

O sistema de Resources implementa o padrão Resource/Transformer para serialização consistente de dados em APIs. Transforma dados brutos de entidades, base de dados e arrays numa representação JSON estruturada e previsível.

Arquitectura do Sistema

O fluxo de transformação passa pelos seguintes componentes:

Dados de Entrada Base de Dados Entity/Model Array Output DTO JsonResource + ConditionalAttributesTrait + DateSerializerTrait + DelegatesToResource + InteractsWithType Saída JSON { "id": 1, "name": "João", "email": "..." }
Padrão Implementação
Resource Pattern Classe base JsonResource com método toArray()
Factory Pattern Métodos estáticos make() e collection()
Decorator Pattern Traits adicionam comportamentos (validação, serialização)
Singleton Pattern MissingValue::getInstance() para valores ausentes

Classe Base: JsonResource

A classe JsonResource é o coração do sistema, fornecendo a estrutura base para transformação de dados.

use Og\Modules\Common\Resource\JsonResource;

final class UserOutputDTO extends JsonResource
{
    public function toArray(): array
    {
        return [
            'id' => $this->codempr,
            'email' => $this->email,
            'name' => $this->nome,
            'short_name' => $this->nomeabreviado,
        ];
    }
}

// Uso
$result = UserOutputDTO::make($userData);
// Resultado: ['id' => 1, 'email' => 'joao@ex.com', ...]

make($data)

Transforma um único objecto/array. Retorna array vazio se $data for null.

collection($data)

Transforma listas. Detecta paginação automaticamente e inclui metadata.

Campos Condicionais

O trait ConditionalAttributesTrait permite controlar quais campos aparecem na saída JSON.

whenHas() — Campo Existe e Tem Valor

public function toArray(): array
{
    return [
        // Sintaxe simples
        'name' => $this->whenHas('name'),
        
        // Com callback de transformação
        'created_at' => $this->whenHas('created_at', 
            fn ($v) => Carbon::parse($v)->toIso8601String()
        ),
        
        // Dot notation para dados aninhados
        'city' => $this->whenHas('address.city'),
        
        // DTO aninhado com lazy loading
        'customer' => $this->whenHas('customer', 
            fn () => CustomerOutputDTO::make($this->customer)
        ),
        
        // Com valor default
        'status' => $this->whenHas('status', null, 'active'),
    ];
}
Entrada Resultado Campo na Saída?
['name' => 'João'] 'João' ✅ Sim
['name' => ''] MissingValue ❌ Não
['name' => null] MissingValue ❌ Não
[] (ausente) MissingValue ❌ Não

when() — Condição Booleana Livre

return [
    'id' => $this->id,
    
    // Só aparece se documento estiver anulado
    'cancelReason' => $this->when(
        $this->resource['anulado'] === 'T',
        $this->resource['cancel_reason']
    ),
    
    // Só aparece para admins
    'adminNotes' => $this->when(
        auth()->user()?->isAdmin(),
        fn () => $this->fetchAdminNotes()
    ),
    
    // Com valor default
    'badge' => $this->when($this->score >= 100, 'gold', 'standard'),
];

mergeWhen() — Múltiplos Campos Condicionais

Importante: O operador spread (...) é obrigatório antes de merge() e mergeWhen().

return [
    'id' => $this->id,
    
    // Mescla múltiplos campos se for admin
    ...$this->mergeWhen($this->isAdmin, [
        'email' => $this->email,
        'permissions' => $this->permissions,
        'last_login' => $this->last_login,
    ]),
    
    // Com closure para lazy loading
    ...$this->mergeWhen($this->includeFinancials, fn () => [
        'total_spent' => $this->orders()->sum('total'),
        'credit_limit' => $this->credit_limit,
    ]),
];

Validação de Tipos

O sistema inclui validação robusta de tipos para garantir integridade de dados na saída.

InteractsWithType (coordinador) ValidatesPrimitiveTypes isInteger(), isString(), isBoolean(), isArray(), isFloat(), isNumeric() ValidatesNullableTypes isNullOrInteger(), isNullOrString(), isNullOrBoolean() ValidatesClassTypes isDateTime(), isCarbon(), isCollection(), isEnum() ValidatesAdvancedTypes isArrayOf(), isEmail(), isUrl(), isUuid(), isPositiveInteger() 'user_id' => $this->isInteger()->whenHas('user_id') 'email' => $this->isEmail('email')

Tipos Primitivos

'id' => $this->isInteger('id'),
'name' => $this->isString('name'),
'active' => $this->isBoolean('active'),
'settings' => $this->isArray('settings'),

Tipos Avançados

'tag_ids' => $this->isArrayOf('integer', 'tag_ids'),
'email' => $this->isEmail('email'),
'website' => $this->isNullOrUrl('website'),
'quantity' => $this->isPositiveInteger('quantity'),

Colecções e Paginação

O método collection() detecta automaticamente dados paginados e inclui metadata.

Colecção Simples

$users = [
    ['codempr' => 1, 'nome' => 'João'],
    ['codempr' => 2, 'nome' => 'Maria'],
];

UserOutputDTO::collection($users);
// [
//   ['id' => 1, 'name' => 'João'],
//   ['id' => 2, 'name' => 'Maria'],
// ]

Com Paginação

$paginated = [
    'items' => [...],
    'per_page' => 10,
    'total' => 50,
    'current_page' => 1,
];

// Resultado inclui 'meta':
// {
//   "items": [...],
//   "meta": {
//     "current_page": 1,
//     "per_page": 10,
//     "total": 50,
//     ...
//   }
// }

Boas Práticas

✅ Correcto

// Usar whenHas() para segurança
'name' => $this->whenHas('name'),

// DTO aninhado com lazy loading
'customer' => $this->whenHas('customer', 
    fn () => CustomerDTO::make($this->customer)
),

❌ Evitar

// Acesso directo pode falhar
'name' => $this->resource['name'],

// DTO sem lazy loading pode lançar erro
'customer' => CustomerDTO::make($this->customer),

Convenção de Nomenclatura

  • Nome da classe: {Domain}{Entity}OutputDTO (ex: CrmAppointmentOutputDTO)
  • Namespace: Og\Modules\{Module}\{Domain}\DTOs
  • Campos de saída: snake_case (ex: employee_id, created_at)