@extends('layouts.docs') @section('title', 'Resources (Output DTOs) - OG Framework') @section('body')
{{-- Floating shapes for styling --}}
{{-- Left Sidebar: Navigation --}} @include('docs.partials.sidebar') {{-- Main Content --}}
{{-- Page Header --}}
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.

{{-- Architecture Diagram --}}

Arquitectura do Sistema

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

{{-- SVG: Data Flow Diagram --}}
{{-- Input Group --}} Dados de Entrada {{-- DB --}} Base de Dados {{-- Entity --}} Entity/Model {{-- Array --}} Array {{-- Arrows to DTO --}} {{-- Output DTO Box --}} Output DTO JsonResource + ConditionalAttributesTrait + DateSerializerTrait + DelegatesToResource + InteractsWithType {{-- Arrow to Output --}} {{-- JSON Output --}} Saída JSON { "id": 1, "name": "João", "email": "..." } {{-- Arrowhead marker --}}
{{-- Design Patterns Table --}}
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
{{-- JsonResource Class --}}

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.

{{-- Conditional Fields --}}

Campos Condicionais

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

{{-- whenHas --}}

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 --}}

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'),
];
{{-- merge --}}

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,
    ]),
];
{{-- Type Validation --}}

Validação de Tipos

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

{{-- SVG: Type Validation Hierarchy --}}
{{-- Main coordinator --}} InteractsWithType (coordinador) {{-- Lines --}} {{-- Primitive Types --}} ValidatesPrimitiveTypes isInteger(), isString(), isBoolean(), isArray(), isFloat(), isNumeric() {{-- Nullable Types --}} ValidatesNullableTypes isNullOrInteger(), isNullOrString(), isNullOrBoolean() {{-- Class Types --}} ValidatesClassTypes isDateTime(), isCarbon(), isCollection(), isEnum() {{-- Advanced Types --}} ValidatesAdvancedTypes isArrayOf(), isEmail(), isUrl(), isUuid(), isPositiveInteger() {{-- Example usage --}} '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'),
{{-- Collections & Pagination --}}

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,
//     ...
//   }
// }
{{-- Best Practices --}}

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)
{{-- Navigation --}}
{{-- Right Sidebar: Table of Contents --}} @include('docs.partials.toc', ['sections' => [ 'arquitectura' => 'Arquitectura', 'json-resource' => 'JsonResource', 'campos-condicionais' => 'Campos Condicionais', 'validacao-tipos' => 'Validação de Tipos', 'coleccoes' => 'Colecções', 'boas-praticas' => 'Boas Práticas', ]])
@endsection