OG Framework
OG Framework Documentação
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.

Pipeline de Request

Entrada HTTP Request API Request AJAX Request Pipeline de Processamento capture() Request Global MW Middleware Matching Router Route MW Middleware Dispatch Controller RouterCollector • MiddlewareManager • RouteDispatcher 1095 lines • Pipeline Pattern • DI automática Saída Response::json() AjaxServer

Controllers

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

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

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

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

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

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

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

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 System

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

Ver Documentação de Routing

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

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