@extends('layouts.docs') @section('title', 'Web Layer System - OG Framework') @section('body')
{{-- Floating shapes for styling --}}
{{-- Left Sidebar: Navigation --}} @include('docs.partials.sidebar') {{-- Main Content --}}
{{-- Page Header --}}
Voltar para Documentação

HTTP & API

Web Layer System

Arquitectura moderna e extensível para processamento de requests HTTP. Integra Http System, Middleware Pipeline e Routing System com mais de 75 controllers implementados.

{{-- Architecture SVG Diagram --}}

Pipeline de Request

{{-- SVG: Request Flow --}}
{{-- Input --}} Entrada HTTP Request API Request AJAX Request {{-- Arrow --}} {{-- Pipeline --}} Pipeline de Processamento {{-- Steps --}} capture() Request Global MW Middleware Matching Router Route MW Middleware Dispatch Controller {{-- Routes --}} RouterCollector • MiddlewareManager • RouteDispatcher 1095 lines • Pipeline Pattern • DI automática {{-- Arrow --}} {{-- Output --}} Saída Response::json() AjaxServer
{{-- Controllers --}}

Controllers

O sistema oferece duas classes base com diferentes níveis de integração:

{{-- BaseController --}}

BaseController — Integração Legacy

Acesso directo aos objectos globais do sistema legacy:

class BaseController
{
    public Aplication $application;  // Instância da aplicação
    public $user;                    // Utilizador autenticado
    public $modules;                 // Sistema de módulos legacy
    public $database;                // Instância de database

    public function __construct()
    {
        global $a, $u, $m, $db;
        $this->application = $a;
        $this->user = $u;
        $this->modules = $m;
        $this->database = $db;
    }
}

// Uso típico
class CustomerController extends BaseController
{
    public function index(): Response
    {
        $customers = $this->modules['entidades']->listaClientesAPI(limit: 250);
        return response()->json($customers);
    }
}
{{-- Controller with Policies --}}

Controller — Moderno com Policies

Estende BaseClass e adiciona suporte a authorization policies:

class Controller extends BaseClass
{
    protected array $policies = [];
    protected string $policy;

    public function authorize(string $method, string $modelClassName, ?array $params = null): void
    {
        // 1. Usa $this->policy se definida
        // 2. Procura no mapa $this->policies
        // 3. Inferência automática: Controller → Policy
        //    UserController → UserPolicy
        //    Og\Crm\Controllers\AppointmentController → Og\Crm\Policies\AppointmentPolicy
    }
}
{{-- Controller Example --}}

Exemplo Real: AppointmentController

class AppointmentController extends Controller
{
    public function index(AppointmentsIndexRequest $request): Response
    {
        // 1. Autorização via Policy
        $this->authorize('viewAny', static::class);
        
        // 2. Dados validados
        $validData = $request->validated();
        
        // 3. Delegar para Action
        $appointments = (new GetAppointments())($validData);
        
        return response()->json($appointments);
    }

    public function create(AppointmentsCreateRequest $request, CreateAppointment $action): Response
    {
        $this->authorize('create', static::class);
        
        [$appointment, $statusCode] = $action->execute($request->validated());
        
        return response()->json($appointment, $statusCode);
    }
}

Padrões: Request Classes • Action Classes • Policy Authorization • Dependency Injection

{{-- Request Class --}}

Request Class

Estende Symfony\Component\HttpFoundation\Request com API rica para HTTP:

Request::capture() — Factory Method

// Cria Request a partir de superglobals PHP
$request = Request::capture();

// Processa JSON automaticamente
if (str_contains($headers->get('CONTENT_TYPE'), 'application/json')) {
    $data = json_decode($request->getContent(), true);
    $request->request->replace($data);
}

// Suporta method override (_method)
// POST com _method=PUT → $request->method() retorna 'PUT'

Input Handling com Dot Notation

// Obter apenas campos específicos
$data = $request->only(['name', 'email', 'user.profile.bio']);

// Excluir campos
$data = $request->except(['password', 'password_confirmation']);

// Verificações
$request->filled('name');    // Campo preenchido?
$request->missing('token');  // Campo ausente?
$request->has(['email']);    // Campos existem?
$request->ajax();            // É AJAX?
$request->expectsJson();     // Espera JSON?

// Merge de dados
$request->merge(['active' => true]);
$request->mergeIfMissing(['role' => 'user']);

Input Trait — Helpers Adicionais

$request->header('X-API-KEY');           // Header específico
$request->bearerToken();                  // Extract Bearer token
$request->cookie('session_id');           // Cookies
$request->user();                         // Utilizador autenticado
$request->all();                          // Inputs + ficheiros
$request->remove('temp_field');           // Remover campo
{{-- Response Class --}}

Response Class

// JSON Response
return response()->json(['users' => $users], 200);

// XML Response
return response()->xml($xmlContent);

// Text Response
return response()->text('Plain text content');

// Redirect com flash data
return response()->redirect('/dashboard')->with('message', 'Welcome!');

// No Content (204)
return response()->noContent();

// Definir headers
return response()->header('X-Custom', 'value')->json($data);

// Integração Legacy
response()->sendAjax($content);      // Via AjaxServer
response()->ogJson($data, 1000);     // Formato OfficeGest
{{-- Middleware --}}

Middleware System

O MiddlewareManager implementa o Pipeline Pattern para processamento de middlewares:

MiddlewareInterface

interface MiddlewareInterface
{
    public function handle(Request $request, Closure $next, mixed $params = null): mixed;
}

Criar Middleware Personalizado

class ValidateApiKeyMiddleware implements MiddlewareInterface
{
    public function handle(Request $request, Closure $next, mixed $params = null): mixed
    {
        $apiKey = $request->header('X-API-KEY');
        
        if (!$this->isValidApiKey($apiKey)) {
            return response()->json(['error' => 'Invalid API key'], 401);
        }

        // Adicionar info ao request
        $request->merge(['api_client' => $this->getClientByKey($apiKey)]);

        return $next($request);
    }
}

Middleware com Parâmetros

class ThrottleMiddleware implements MiddlewareInterface
{
    public function handle(Request $request, Closure $next, mixed $params = null): mixed
    {
        // Params: ['60', '1'] de 'throttle:60,1'
        $maxAttempts = (int) ($params[0] ?? 60);
        $decayMinutes = (int) ($params[1] ?? 1);
        
        if ($this->tooManyAttempts($key, $maxAttempts)) {
            return response()->json(['error' => 'Too many requests'], 429);
        }
        
        return $next($request);
    }
}

// Uso
Route::middleware(['throttle:100,1'])->group(function() {
    Route::get('/api/heavy', 'ApiController@heavy');
});
{{-- Action Classes --}}

Action Classes

Padrão para isolar lógica de negócio em classes reutilizáveis e testáveis:

class CreateAppointment
{
    public function execute(array $data): array
    {
        // Validação de regras de negócio
        $this->validateBusinessRules($data);
        
        // Criar agendamento
        $appointment = Appointment::create([
            'customer_id' => $data['customer_id'],
            'scheduled_at' => Carbon::parse($data['scheduled_at']),
            'duration' => $data['duration'] ?? 60,
            'notes' => $data['notes'] ?? null,
        ]);
        
        // Enviar notificação
        $appointment->customer->notify(new AppointmentCreated($appointment));
        
        return [$appointment->toArray(), Response::HTTP_CREATED];
    }
}

// Uso no Controller (injectado automaticamente via DI)
public function create(AppointmentsCreateRequest $request, CreateAppointment $action): Response
{
    [$appointment, $statusCode] = $action->execute($request->validated());
    return response()->json($appointment, $statusCode);
}

Benefícios:

  • Single Responsibility — Controller só orquestra, Action executa
  • Reutilização — Mesma action em API, CLI, Jobs
  • Testabilidade — Actions testáveis sem HTTP
  • DI Automática — RouteDispatcher resolve dependências
{{-- OpenAPI Annotations --}}

OpenAPI/Swagger Annotations

Controllers podem incluir anotações OpenAPI para documentação automática da API:

/**
 * @OA\Tag(name="Customers")
 */
class CustomerController extends Controller
{
    /**
     * @OA\Get(
     *     path="/entities/customers",
     *     summary="List all customers",
     *     tags={"Customers"},
     *     @OA\Parameter(name="name", in="query", @OA\Schema(type="string")),
     *     @OA\Parameter(name="taxId", in="query", @OA\Schema(type="string")),
     *     @OA\Response(response=200, description="OK", 
     *         @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/CustomerOutputDTO")))
     * )
     */
    public function index(CustomerIndexRequest $request): Response
    {
        // ...
    }

    /**
     * @OA\Post(
     *     path="/entities/customers",
     *     summary="Create a new customer",
     *     tags={"Customers"},
     *     @OA\RequestBody(ref="#/components/requestBodies/CreateCustomerRequest"),
     *     @OA\Response(response=201, description="Created"),
     *     @OA\Response(response=422, description="Unprocessable Entity")
     * )
     */
    public function create(CustomerCreateRequest $request): Response
    {
        // ...
    }
}
{{-- RelationshipLoader --}}

RelationshipLoader

Utilitário para carregar relações dinamicamente em arrays de dados:

use Og\Modules\Common\Database\Relations\RelationshipLoader;

// Carregar relações para array de clientes
$customers = (new RelationshipLoader())->load(
    $customers,                // Array de dados
    ['country', 'contacts'],  // Relações a carregar
    EntityRelation::class      // Classe que define as relações
);

// Uso típico em controller
public function show(int $id): Response
{
    $customer = $this->modules['entidades']->loadCliente($id);
    
    // Carrega relação 'country' automaticamente
    $customer = (new RelationshipLoader())->load(
        $customer, 
        ['country'], 
        EntityRelation::class
    );
    
    return response()->json(EntitiesCustomerOutputDTO::make($customer));
}
{{-- UploadedFile --}}

UploadedFile

Classe para gestão de ficheiros enviados via upload:

$file = $request->file('avatar');

// Informações do ficheiro
$file->getClientOriginalName();  // 'photo.jpg'
$file->getClientMimeType();      // 'image/jpeg'
$file->getSize();                // 245632 bytes
$file->getExtension();           // 'jpg'

// Mover para destino
$file->move('/uploads/avatars', 'user_123.jpg');

// Store com nome automático
$path = $file->store('avatars');  // 'avatars/abc123.jpg'

// Validação (ver página Validação)
$rules = [
    'avatar' => ['uploaded', 'is_image', 'max_size[2048]'],
];
{{-- Routing Reference --}}

Routing System

O sistema de rotas é documentado em detalhe na sua própria página:

Ver Documentação de Routing
{{-- RouteDispatcher --}}

RouteDispatcher

Executa handlers com Dependency Injection automática:

readonly class RouteDispatcher
{
    public function dispatch(callable|string $handler, array $routeParams = []): mixed
    {
        return match (true) {
            $handler instanceof Closure => $this->dispatchClosure($handler, $routeParams),
            $this->isValidHandlerString($handler) => $this->dispatchController($handler, $routeParams),
            default => throw new RuntimeException('Invalid route handler.')
        };
    }

    // Resolução automática de parâmetros:
    // - Route params: {id}, {slug} → conversão automática de tipos (int, bool, float)
    // - Request injection: Request $request
    // - BaseRequest validation: AppointmentsCreateRequest $request
    // - Dependency injection: CreateAppointment $action
}
{{-- Facades --}}

Facades

use Og\Modules\Common\Facades\Request;
use Og\Modules\Common\Facades\Response;
use Og\Modules\Common\Facades\Route;

// Request Facade
$data = Request::all();
$name = Request::get('name', 'default');
$token = Request::bearerToken();
$user = Request::user();

// Response Facade
return Response::json(['success' => true]);
return Response::redirect('/dashboard')->with('message', 'OK');

// Route Facade
Route::get('/users', 'UserController@index')->name('users.index');
Route::middleware(['auth'])->group(function() { /* ... */ });
{{-- Controllers Table --}}

Controllers no Sistema (75+)

Domínio Qty Exemplos
Ams18BoardingGateController, TransportServicesController
Entidade9CustomerController, EmployeeController, SupplierController
Crm5AppointmentController, ParameterController, AbcController
ChatFlow3ChatFlowController, WhatsappController
Tickets3TicketController, TicketMessageController
Outros37+AI, Auth, Developer, SAFT, Portal...
{{-- Navigation --}}
{{-- Right Sidebar: Table of Contents --}} @include('docs.partials.toc', ['sections' => [ 'arquitectura' => 'Pipeline de Request', 'controllers' => 'Controllers', 'request' => 'Request Class', 'response' => 'Response Class', 'middleware' => 'Middleware System', 'actions' => 'Action Classes', 'openapi' => 'OpenAPI Annotations', 'relationships' => 'RelationshipLoader', 'uploads' => 'UploadedFile', 'routing' => 'Routing System', 'dispatcher' => 'RouteDispatcher', 'facades' => 'Facades', 'controllers-list' => 'Controllers (75+)', ]])
@endsection