# Web Layer System - Framework OfficeGest

## Visão Geral

O **Web Layer** do OfficeGest é uma arquitectura moderna e extensível que processa requests HTTP através de três sistemas integrados: **Http System** (gestão de requests/responses), **Middleware System** (pipeline de processamento) e **Routing System** (roteamento e dispatch). Com mais de **75 controllers** implementados, este sistema forma a espinha dorsal da API moderna do framework.

```mermaid
flowchart LR
    subgraph Input["Entrada"]
        Web["HTTP Request"]
        API["API Request"]
        AJAX["AJAX Request"]
    end
    
    subgraph Pipeline["Pipeline de Processamento"]
        Capture["Request::capture()"]
        Global["Global Middleware"]
        Route["Route Matching"]
        RouteM["Route Middleware"]
        Dispatch["Controller Dispatch"]
    end
    
    subgraph Output["Saída"]
        Response["Response"]
        Legacy["Legacy\nAjaxServer"]
    end
    
    Web --> Capture
    API --> Capture
    AJAX --> Capture
    Capture --> Global
    Global --> Route
    Route --> RouteM
    RouteM --> Dispatch
    Dispatch --> Response
    Dispatch --> Legacy
    
    style Pipeline fill:#e8f4fd,stroke:#0284c7
    style Output fill:#f0fdf4,stroke:#22c55e
```

---

## Estrutura de Ficheiros

```
Modules/
├── Controller.php                   # Controller base moderno (com policies)
├── BaseController.php               # Controller base (integração legacy)
├── Common/
│   ├── Http/                        # Sistema HTTP
│   │   ├── Request.php              # Request principal (358 lines)
│   │   ├── Response.php             # Response com métodos convenientes
│   │   ├── BaseRequest.php          # Base para request validation
│   │   ├── Input.php                # Trait para input handling
│   │   ├── RequestDebugger.php      # Debug de requests
│   │   ├── RequestFilterProcessor.php # Filtros para APIs
│   │   ├── UploadedFile.php         # Gestão de uploads
│   │   ├── MiddlewareDispatcher.php # Dispatcher local
│   │   └── Middleware/              # Middlewares HTTP específicos
│   ├── Middleware/                  # Sistema de Middleware Global
│   │   ├── MiddlewareInterface.php  # Interface padrão
│   │   └── MiddlewareManager.php    # Gestor (Pipeline Pattern)
│   └── Routing/                     # Sistema de Roteamento
│       ├── Router.php               # Facade/Proxy
│       ├── RouterCollector.php      # Implementação (1095 lines)
│       ├── RouteDispatcher.php      # Dispatch com DI (292 lines)
│       ├── Route.php                # Representação individual
│       ├── RouteGroup.php           # Agrupamento de rotas
│       └── RouteCache.php           # Cache de rotas
└── [Domain]/
    └── Controllers/                 # Controllers de domínio
```

---

## Controllers

O sistema de controllers do OfficeGest oferece duas classes base que fornecem diferentes níveis de integração com o framework.

### Hierarquia de Controllers

```mermaid
classDiagram
    class BaseClass {
        +Aplication $a
        +User $u
        +Session $s
        +Language $l
    }
    
    class BaseController {
        +Aplication $application
        +$user
        +$modules
        +$database
        +__construct()
    }
    
    class Controller {
        #array $policies
        #string $policy
        +authorize(string, string, ?array)
    }
    
    class DomainController {
        +index()
        +show(int)
        +create()
        +update(int)
        +delete(int)
    }
    
    BaseClass <|-- Controller
    Controller <|-- DomainController
    BaseController <|-- LegacyController
```

---

### BaseController - Integração Legacy

A classe `BaseController` fornece acesso directo aos objectos globais do sistema legacy:

```php
<?php

namespace Og\Modules;

use Aplication;

class BaseController
{
    public Aplication $application;
    public $user;
    public $modules;
    public $database;

    public function __construct()
    {
        global $a, $u, $m, $db;
        $this->application = $a;  // Instância da aplicação
        $this->user = $u;         // Utilizador autenticado
        $this->modules = $m;      // Sistema de módulos legacy
        $this->database = $db;    // Instância de database
    }
}
```

**Uso típico:**
```php
class CustomerController extends BaseController
{
    public function index(): Response
    {
        // Acesso ao módulo legacy 'entidades'
        $customers = $this->modules['entidades']->listaClientesAPI(limit: 250);
        
        return response()->json($customers);
    }
}
```

---

### Controller - Controller Moderno com Policies

A classe `Controller` estende `BaseClass` e adiciona suporte a **authorization policies**:

```php
<?php

namespace Og\Modules;

use RuntimeException;

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
        if (!empty($this->policy) && class_exists($this->policy)) {
            $policy = new $this->policy(app('user'), app('app'));
            $policy->authorize($method, $params);
            return;
        }

        // 2. Procura no mapa $this->policies
        $policyClass = $this->policies[$modelClassName] ?? null;
        
        // 3. Inferência automática: Controller → Policy
        if (!$policyClass || !class_exists($policyClass)) {
            $policyClass = replace(
                'Controller', 'Policy',
                replace('Controllers', 'Policies', $modelClassName)
            );
        }

        if (!class_exists($policyClass)) {
            throw new RuntimeException("Policy {$policyClass} não existe");
        }

        $policy = new $policyClass(app('user'), app('app'));
        $policy->authorize($method, $params);
    }
}
```

**Convenção de nomenclatura automática:**
- `App\Controllers\UserController` → `App\Policies\UserPolicy`
- `Og\Modules\Crm\Controllers\AppointmentController` → `Og\Modules\Crm\Policies\AppointmentPolicy`

---

### Controller de Exemplo: AppointmentController

Um exemplo real de controller moderno com todas as boas práticas:

```php
<?php

namespace Og\Modules\Crm\Appointments\Controllers;

use Og\Modules\Common\Http\Request;
use Og\Modules\Controller;
use Og\Modules\Crm\Appointments\Actions\CreateAppointment;
use Og\Modules\Crm\Appointments\Actions\GetAppointment;
use Og\Modules\Crm\Appointments\Actions\GetAppointments;
use Og\Modules\Crm\Appointments\Requests\AppointmentsCreateRequest;
use Og\Modules\Crm\Appointments\Requests\AppointmentsIndexRequest;
use Symfony\Component\HttpFoundation\Response;

class AppointmentController extends Controller
{
    /**
     * Lista agendamentos com validação e autorização.
     */
    public function index(AppointmentsIndexRequest $request): Response
    {
        // 1. Autorização via Policy
        $this->authorize('viewAny', static::class);
        
        // 2. Obter dados validados do Request
        $validData = $request->validated();
        
        // 3. Delegar lógica para Action class
        $appointments = (new GetAppointments())($validData);
        
        return response()->json($appointments);
    }

    /**
     * Obtém um agendamento específico.
     */
    public function show(int $id): Response
    {
        $this->authorize('view', static::class);
        
        $appointment = (new GetAppointment())($id);
        
        return response()->json($appointment);
    }

    /**
     * Cria novo agendamento com Request validation e Action.
     */
    public function create(AppointmentsCreateRequest $request, CreateAppointment $action): Response
    {
        $this->authorize('create', static::class);
        
        $validData = $request->validated();
        
        // Action retorna tupla [data, statusCode]
        [$appointment, $statusCode] = $action->execute($validData);
        
        if ($statusCode !== Response::HTTP_CREATED) {
            return response()->json($appointment, $statusCode);
        }
        
        // Recarrega para incluir relationships
        $appointment = (new GetAppointment())($appointment['id']);
        
        return response()->json($appointment, $statusCode);
    }

    public function update(Request $request, int $id): Response
    {
        $this->authorize('update', static::class);
        
        return response()->json([
            'message' => 'Not Implemented',
        ], Response::HTTP_NOT_IMPLEMENTED);
    }

    public function delete(int $id): Response
    {
        $this->authorize('delete', static::class);
        
        return response()->json([
            'message' => 'Not Implemented',
        ], Response::HTTP_NOT_IMPLEMENTED);
    }
}
```

**Padrões utilizados:**
- **Request Classes**: Validação encapsulada (`AppointmentsCreateRequest`)
- **Action Classes**: Lógica de negócio isolada (`CreateAppointment`)
- **Policy Authorization**: `$this->authorize()` em cada método
- **Dependency Injection**: `CreateAppointment $action` injectada automaticamente

---

### Controller com OpenAPI Annotations: CustomerController

Exemplo de controller com documentação OpenAPI/Swagger integrada:

```php
<?php

namespace Og\Modules\Entidade\Customers\Controllers;

use Og\Modules\Common\Database\Relations\RelationshipLoader;
use Og\Modules\Controller;
use Og\Modules\Entidade\Common\Relations\EntityRelation;
use Og\Modules\Entidade\Customers\DTOs\EntitiesCustomerInputDTO;
use Og\Modules\Entidade\Customers\DTOs\EntitiesCustomerOutputDTO;
use Og\Modules\Entidade\Customers\Requests\CustomerCreateRequest;
use Og\Modules\Entidade\Customers\Requests\CustomerIndexRequest;
use Symfony\Component\HttpFoundation\Response;

/**
 * @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
    {
        $where = $request->validated();
        $limit = $where['limit'] ?? 250;
        
        // Filtrar campos de pesquisa
        $search = array_filter($where, fn($key) => $key != 'limit', ARRAY_FILTER_USE_KEY);
        $search = EntitiesCustomerInputDTO::make($search);

        // Usar módulo legacy para obter dados
        $customerList = $this->modules['entidades']->listaClientesAPI(
            limit: $limit, 
            search: $search
        );
        
        $customers = \Funcs::collect($customerList)
            ->map(fn($c) => $c)
            ->values()
            ->toArray();
        
        // Carregar relationships
        $customers = (new RelationshipLoader())->load(
            $customers, 
            ['country'], 
            EntityRelation::class
        );

        // Transformar para DTO de output
        return response()->json(EntitiesCustomerOutputDTO::collection($customers));
    }

    /**
     * @OA\Get(
     *     path="/entities/customers/{id}",
     *     summary="Get a customer by ID",
     *     tags={"Customers"},
     *     @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
     *     @OA\Response(response=200, description="OK", 
     *         @OA\JsonContent(ref="#/components/schemas/CustomerOutputDTO")),
     *     @OA\Response(response=404, description="Not Found")
     * )
     */
    public function show(int $id): Response
    {
        $this->authorize('view', static::class);
        
        $customer = $this->modules['entidades']->loadCliente($id);
        
        if (!$customer) {
            return response()->json(
                ['message' => "Customer with id $id not found"], 
                Response::HTTP_NOT_FOUND
            );
        }
        
        $customer = (new RelationshipLoader())->load(
            $customer, 
            ['country'], 
            EntityRelation::class
        );
        
        return response()->json(EntitiesCustomerOutputDTO::make($customer));
    }

    /**
     * @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
    {
        $this->authorize('create', static::class);
        
        $customerId = $this->modules['entidades']->createCliente(
            EntitiesCustomerInputDTO::make($request->validated())
        );

        if (!$customerId) {
            return response()->json(
                ['message' => "Customer not created"], 
                Response::HTTP_UNPROCESSABLE_ENTITY
            );
        }

        $customer = $this->modules['entidades']->loadCliente($customerId);
        $customer = (new RelationshipLoader())->load($customer, ['country'], EntityRelation::class);

        return response()->json(
            EntitiesCustomerOutputDTO::make($customer), 
            Response::HTTP_CREATED
        );
    }
}
```

**Características:**
- Anotações **OpenAPI/Swagger** para documentação automática
- **DTOs** para input e output
- **RelationshipLoader** para carregar relações
- Integração com **módulos legacy**

---

### Controllers no Sistema (75+)

O OfficeGest possui mais de 75 controllers organizados por domínio:

| Domínio | Controllers | Exemplos |
|---------|-------------|----------|
| **AI** | 1 | `AIController` |
| **Ams** | 18 | `BoardingGateController`, `TransportServicesController`, `PassengerAttendanceController` |
| **Auth** | 1 | `AuthApiController` |
| **ChatFlow** | 3 | `ChatFlowController`, `ConnectorController`, `WhatsappController` |
| **Crm** | 5 | `AppointmentController`, `ParameterController`, `AbcController` |
| **Datadrive/Hub4Data** | 3 | `DocumentsController`, `VerifyConnectionController` |
| **Developer** | 2 | `DeveloperQueueJobFailedController`, `DeveloperQueueProfileWorkerController` |
| **Drive360** | 1 | `Drive360Controller` |
| **EBank** | 1 | `EBankController` |
| **Entidade** | 9 | `CustomerController`, `EmployeeController`, `SupplierController`, `ContactController` |
| **IntegrationSJP** | 2 | `CartController`, `ConnectionController` |
| **Oficinas** | 3 | `ReorderStatusRequest`, `SignRequest` |
| **SAFT** | Vários | `SaftAoController`, `ElectronicInvoicingController` |
| **Tickets** | 3 | `TicketController`, `TicketMessageController` |
| **Webpanel/Portal** | Vários | Controllers de portal web |

---

## Http System

### Request Class

A classe `Request` estende `Symfony\Component\HttpFoundation\Request` e fornece uma API rica para manipulação de requests HTTP:

```php
<?php

namespace Og\Modules\Common\Http;

use Symfony\Component\HttpFoundation\Request as SymfonyRequest;

class Request extends SymfonyRequest implements Arrayable, ArrayAccess
{
    use Input;        // Trait para input handling
    use RouteHelper;  // Trait para helpers de rota
    use Macroable;    // Trait para extensibilidade
    
    protected $json;
```

#### Factory Method: capture()

O método `capture()` cria uma instância de Request a partir dos superglobals PHP:

```php
public static function capture(): SymfonyRequest|Request
{
    static::enableHttpMethodParameterOverride();
    
    $request = static::createFromBase(SymfonyRequest::createFromGlobals());
    
    // Remove handler do query (legacy)
    $request->query->remove('handler');
    
    // Processa JSON automaticamente
    if (str_contains($request->headers->get('CONTENT_TYPE') ?? '', 'application/json')) {
        $content = $request->getContent();
        if (!empty($content)) {
            $data = json_decode($content, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                $request->request->replace(is_array($data) ? $data : []);
            }
        }
    }
    
    return $request;
}
```

#### Method Override (PUT, DELETE, PATCH)

Suporte a `_method` para APIs RESTful via POST:

```php
public function method(): string
{
    $method = parent::getMethod();

    if ($method === 'POST') {
        $override = $this->request->get('_method');
        if ($override && in_array(strtoupper($override), ['PUT', 'DELETE', 'PATCH'])) {
            return strtoupper($override);
        }
    }

    return $method;
}
```

#### Input Handling com Dot Notation

```php
// Obter apenas campos específicos
public function only($keys): array
{
    $results = [];
    $input = $this->all();
    $placeholder = new stdClass();
    
    foreach (is_array($keys) ? $keys : func_get_args() as $key) {
        // Suporta dot notation: 'user.profile.name'
        $value = ArrayHelper::dotArraySearch($input, $key);
        if ($value !== $placeholder) {
            ArrayHelper::set($results, $key, $value);
        }
    }
    
    return $results;
}

// Excluir campos específicos
public function except($keys): array
{
    $keys = is_array($keys) ? $keys : func_get_args();
    $results = $this->all();
    ArrayHelper::forget($results, $keys);
    return $results;
}
```

#### Métodos de Verificação

```php
// Verificar se campos estão preenchidos
public function filled($key): bool
{
    $keys = is_array($key) ? $key : func_get_args();
    foreach ($keys as $value) {
        if ($this->isEmptyString($value)) {
            return false;
        }
    }
    return true;
}

// Verificar se campos estão ausentes
public function missing($key): bool
{
    return !$this->has($keys);
}

// Verificar se é AJAX
public function ajax(): bool
{
    return $this->isXmlHttpRequest();
}

// Verificar se espera JSON
public function expectsJSon(): bool
{
    if ($this->ajax()) {
        return true;
    }
    return (bool)$this->wantsJson();
}
```

#### Merge de Dados

```php
// Fundir dados no request
public function merge(array $input): static
{
    return tap($this, function (self $request) use ($input) {
        $request->getInputSource()
            ->replace((Funcs::collect($input))->reduce(
                fn ($requestInput, $value, $key) => data_set($requestInput, $key, $value),
                $this->getInputSource()->all()
            ));
    });
}

// Fundir apenas se campo não existir
public function mergeIfMissing(array $input): Request|static
{
    return $this->merge(
        funcs::collect($input)
            ->filter(fn($value, $key) => $this->missing($key))
            ->toArray()
    );
}
```

---

### Input Trait

O trait `Input` adiciona funcionalidades avançadas de input handling:

```php
<?php

namespace Og\Modules\Common\Http;

trait Input
{
    // Verificar se é JSON
    public function isJson(): bool
    {
        return in_array($this->header('CONTENT_TYPE') ?? '', ['/json', '+json']);
    }

    // Obter header específico
    public function header($key = null, $default = null)
    {
        return $this->retrieveItem('headers', $key, $default);
    }

    // Verificar e obter cookies
    public function hasCookie(string $key): bool
    {
        return !is_null($this->cookie($key));
    }

    public function cookie(?string $key = null, string|array|null $default = null): array|string|null
    {
        return $this->retrieveItem('cookies', $key, $default);
    }

    // Obter Bearer Token
    public function bearerToken(): false|string|null
    {
        $header = $this->header('Authorization', '');
        $position = strripos($header, 'Bearer ');
        
        if (str_contains($header, 'Bearer ') && $position !== false) {
            $header = substr($header, $position + 7);
            return str_contains($header, ',') ? strstr($header, ',', true) : $header;
        }
        
        return null;
    }

    // Obter todos os inputs incluindo ficheiros
    public function all($keys = null): array
    {
        $input = array_replace_recursive($this->input(), $this->allFiles());
        
        if (!$keys) {
            return $input;
        }
        
        $results = [];
        foreach (is_array($keys) ? $keys : func_get_args() as $key) {
            ArrayHelper::set($results, $key, ArrayHelper::get($input, $key));
        }

        return $results;
    }

    // Remover campo do request
    public function remove(string $key): void
    {
        if ($this->request->has($key)) {
            $this->request->remove($key);
        }
        if ($this->query->has($key)) {
            $this->query->remove($key);
        }
        if ($this->files->has($key)) {
            $this->files->remove($key);
        }
    }
}
```

---

### Response Class

A classe `Response` estende `Symfony\Component\HttpFoundation\Response` com métodos convenientes:

```php
<?php

namespace Og\Modules\Common\Http;

use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use AjaxServer;
use RestUtils;

class Response extends SymfonyResponse
{
    use Macroable {
        __call as macroCall;
    }

    // Response JSON
    public function json(array $data, int $code = SymfonyResponse::HTTP_OK): self
    {
        $this->headers->set('Content-Type', 'application/json');
        $this->headers->set('Accept', 'application/json');
        $this->setContent(json_encode($data));
        $this->setStatusCode($code);
        return $this;
    }

    // Response XML
    public function xml(string $xml): self
    {
        $this->headers->set('Content-Type', 'application/xml');
        $this->setContent($xml);
        return $this;
    }

    // Response texto
    public function text(string $text): self
    {
        $this->headers->set('Content-Type', 'text/plain');
        $this->setContent($text);
        return $this;
    }

    // Redirect com fallback JavaScript
    public function redirect(
        string $url,
        int $status = SymfonyResponse::HTTP_FOUND,
        array $headers = []
    ): self {
        $url = !str_starts_with($url, 'http') && !str_starts_with($url, '/')
            ? '/' . $url
            : $url;

        $this->headers->set('Location', $url);

        foreach ($headers as $key => $value) {
            $this->headers->set($key, $value);
        }

        $this->setStatusCode($status);
        $this->setContent('');

        // Fallback para JavaScript se headers já enviados
        if (headers_sent()) {
            echo "<script>window.location.href='$url';</script>";
            exit;
        }

        return $this;
    }

    // 204 No Content
    public function noContent(): static
    {
        $this->setStatusCode(SymfonyResponse::HTTP_NO_CONTENT);
        return $this;
    }

    // Flash data para sessão
    public function with($key, $value = null): static
    {
        $key = is_array($key) ? $key : [$key => $value];

        foreach ($key as $k => $v) {
            app('session')->flash($k, $v);
        }

        return $this;
    }

    // Definir header
    public function header(string $key, string $value): static
    {
        $this->headers->set($key, $value);
        return $this;
    }
}
```

#### Integração Legacy

```php
// Enviar via AjaxServer (legacy)
public function sendAjax(mixed $content = null, ?int $status = null): void
{
    $statusCode = $status ?? $this->getStatusCode();
    http_response_code($statusCode);
    $responseContent = $content ?? $this->getContent();
    AjaxServer::directrespond($responseContent);
    exit;
}

// JSON no formato OfficeGest
public function ogJson(array $data, int $ogCode = 1000): static
{
    $this->headers->set('Content-Type', 'application/json');
    $ogCode = empty($data) ? 1001 : $ogCode;
    RestUtils::sendResponse($ogCode, $data);
    return $this;
}
```

---

## Middleware System

### MiddlewareManager

O `MiddlewareManager` implementa o **Pipeline Pattern** para processamento de middlewares:

```php
<?php

namespace Og\Modules\Common\Middleware;

class MiddlewareManager
{
    private array $globalMiddleware = [];
    private array $middlewareGroups = [];
    private array $middlewareAliases = [];
    private bool $configurationLoaded = false;

    // Configuração estática para persistir entre instâncias
    private static array $staticGlobalMiddleware = [];
    private static array $staticMiddlewareGroups = [];
    private static array $staticMiddlewareAliases = [];
```

#### Registo de Middlewares

```php
// Middleware global (executa em todas as rotas)
public function registerGlobalMiddleware(string $middleware): void
{
    if (!in_array($middleware, $this->globalMiddleware)) {
        $this->globalMiddleware[] = $middleware;
        self::$staticGlobalMiddleware[] = $middleware;
    }
}

// Grupo de middlewares
public function registerMiddlewareGroup(string $groupName, array $middlewares): void
{
    $this->middlewareGroups[$groupName] = $middlewares;
    self::$staticMiddlewareGroups[$groupName] = $middlewares;
}

// Alias para middleware
public function registerMiddlewareAlias(string $alias, string $middleware): void
{
    $this->middlewareAliases[$alias] = $middleware;
    self::$staticMiddlewareAliases[$alias] = $middleware;
}
```

#### Pipeline de Processamento

```php
public function process(
    Request $request, 
    Closure $destination, 
    array $middlewares = [], 
    array $excludedMiddlewares = []
): mixed {
    $this->ensureConfigurationLoaded();
    $middlewareStack = $this->buildMiddlewareStack($middlewares, $excludedMiddlewares);

    return $this->processMiddlewareStack($request, $middlewareStack, $destination);
}

private function processMiddlewareStack(
    Request $request, 
    array $middlewareStack, 
    Closure $destination
): mixed {
    // Pipeline reverso para execução na ordem correcta
    $pipeline = array_reduce(
        array_reverse($middlewareStack),
        function ($carry, $middleware) {
            return function ($request) use ($carry, $middleware) {
                [$middlewareName, $middlewareParams] = $this->parseMiddleware($middleware);
                $middlewareInstance = $this->resolveMiddleware($middlewareName);
                return $middlewareInstance->handle($request, $carry, $middlewareParams);
            };
        },
        $destination
    );

    return $pipeline($request);
}
```

#### Parsing de Parâmetros

```php
// Suporta formato: "auth:admin,user"
protected function parseMiddleware(string $middleware): array
{
    if (!str_contains($middleware, ':')) {
        return [$middleware, null];
    }

    [$name, $parameters] = array_pad(explode(':', $middleware, 2), 2, []);

    if (is_string($parameters)) {
        $parameters = explode(',', $parameters);
    }

    return [$name, $parameters];
}
```

---

### MiddlewareInterface

```php
<?php

namespace Og\Modules\Common\Middleware;

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

---

### Criar Middleware Personalizado

```php
<?php

namespace Og\Modules\Api\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Middleware\MiddlewareInterface;

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',
                'code' => 'INVALID_API_KEY'
            ], 401);
        }

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

        return $next($request);
    }

    private function isValidApiKey(?string $key): bool
    {
        if (!$key) return false;
        
        return cache()->remember("api_key:{$key}", 3600, function() use ($key) {
            return ApiKey::where('key', $key)->where('active', true)->exists();
        });
    }
    
    private function getClientByKey(string $key): ?ApiClient
    {
        return ApiClient::whereHas('apiKey', fn($q) => $q->where('key', $key))->first();
    }
}
```

---

### Middleware com Parâmetros

```php
<?php

namespace Og\Modules\Common\Middleware;

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

        $this->hit($key, $decayMinutes * 60);

        $response = $next($request);
        
        return $this->addHeaders(
            $response,
            $maxAttempts,
            $this->calculateRemainingAttempts($key, $maxAttempts)
        );
    }
}
```

**Uso:**
```php
Route::middleware(['throttle:100,1'])->group(function() {
    Route::get('/api/heavy-operation', 'ApiController@heavyOperation');
});
```

---

## Routing System

### RouterCollector

O `RouterCollector` é o componente central de roteamento com mais de 1095 linhas:

```php
<?php

namespace Og\Modules\Common\Routing;

class RouterCollector
{
    private static bool $isRunCalled = false;
    private array $routes = [];
    private array $prefixes = [];
    private ?string $controller = null;
    private array $groupNames = [];
    private static ?RouterCollector $instance = null;
    private Request $request;
    private RouteCache $routeCache;
    private MiddlewareManager $middlewareManager;
    private ?array $controllerStack = [];
    private array $nameStack = [];
    private array $routeNameMap = [];
    private array $routeMiddleware = [];
    private array $groupMiddleware = [];
    private array $middlewareStack = [];
```

#### Definição de Rotas

```php
// GET
public function get(string $path, string|array|callable $action): RouteRegistration
{
    if (is_string($action)) {
        $action = [$this->controller, $action];
    }
    $path = $this->prefixedPath($path);
    $this->define('GET', $path, $action);
    return new RouteRegistration('GET', $path, $this);
}

// POST
public function post(string $path, string|array|callable $action): RouteRegistration

// PUT
public function put(string $path, string|array|callable $action): RouteRegistration

// PATCH
public function patch(string $path, string|array|callable $action): RouteRegistration

// DELETE
public function delete(string $path, string|array|callable $action): RouteRegistration
```

#### Grupos de Rotas

```php
public function group(callable $groupRoutes): RouteGroup
{
    return (new RouteGroup($this))->group($groupRoutes);
}

public function controller(string $controller): RouteGroup
{
    return (new RouteGroup($this))->controller($controller);
}

public function prefix(string $prefix): RouteGroup
{
    return (new RouteGroup($this))->prefix($prefix);
}

public function name(string $name): RouteGroup|self
{
    $isGroupName = str_ends_with($name, '.');

    if ($isGroupName) {
        return (new RouteGroup($this))->name($name);
    }

    // Se for nome de rota individual, aplicar à última rota definida
    if (!empty($this->lastDefinedRoute)) {
        $method = $this->lastDefinedRoute['method'];
        $route = $this->lastDefinedRoute['route'];

        $fullRouteName = implode('.', $this->groupNames);
        $fullRouteName = $fullRouteName ? $fullRouteName . '.' . $name : $name;

        $this->routeNameMap[$method][$route] = $fullRouteName;
        $this->lastDefinedRoute = [];

        return $this;
    }

    return (new RouteGroup($this))->name($name);
}
```

---

### RouteDispatcher

O `RouteDispatcher` executa handlers com **Dependency Injection** automática:

```php
<?php

namespace Og\Modules\Common\Routing;

readonly class RouteDispatcher
{
    public function __construct(
        private DependencyResolver $resolver,
        private Request $request,
        private Container $container = new Container(),
    ) {}

    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 format.')
        };
    }

    private function dispatchController(string $handler, array $routeParams): mixed
    {
        [$className, $methodName] = explode('@', $handler);

        $className = $this->resolveClassName($className);

        // Validar request automaticamente se tiver BaseRequest type-hint
        $validationResult = $this->validateRequest("$className@$methodName");
        if ($validationResult !== true) {
            return $validationResult;
        }

        $this->resolver->validateClassAndMethod($className, $methodName);
        $instance = $this->resolver->createInstanceWithDependencies($className);

        $method = new ReflectionMethod($instance::class, $methodName);
        return $instance->$methodName(...$this->resolveParameters($method->getParameters(), $routeParams));
    }
}
```

#### Resolução de Parâmetros

```php
private function resolveParameter(ReflectionParameter $parameter, array $routeParams): mixed
{
    $paramName = $parameter->getName();
    $paramType = $parameter->getType();
    
    // 1. Route params (de {id}, {slug}, etc.)
    if (array_key_exists($paramName, $routeParams)) {
        $value = $routeParams[$paramName];
        
        // Conversão automática de tipos
        if ($paramType && is_string($value)) {
            if ($paramType instanceof \ReflectionNamedType) {
                $typeName = $paramType->getName();
                
                if ($typeName === 'int') {
                    return filter_var($value, FILTER_VALIDATE_INT) ?: $value;
                }
                if ($typeName === 'bool') {
                    return filter_var($value, FILTER_VALIDATE_BOOLEAN);
                }
                if ($typeName === 'float') {
                    return is_numeric($value) ? (float) $value : $value;
                }
            }
        }
        return $value;
    }

    // 2. Request injection
    return match (true) {
        $this->isRequestParameter($paramType) => $this->request,
        $this->isClassDependency($paramType) => $this->resolveDependency($paramType),
        $parameter->isOptional() => $parameter->getDefaultValue(),
        default => throw new RuntimeException("Could not resolve: {$paramName}")
    };
}
```

---

## Facades

### Request Facade

```php
use Og\Modules\Common\Facades\Request;

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

### Response Facade

```php
use Og\Modules\Common\Facades\Response;

return Response::json(['success' => true]);
return Response::redirect('/dashboard')->with('message', 'Welcome!');
return Response::noContent();
```

### Route Facade

```php
use Og\Modules\Common\Facades\Route;

Route::get('/users', 'UserController@index')->name('users.index');
Route::post('/users', 'UserController@store')->name('users.store');
Route::get('/users/{id}', 'UserController@show')->name('users.show');

Route::middleware(['auth'])->group(function() {
    Route::prefix('admin')->name('admin.')->group(function() {
        Route::get('/dashboard', 'AdminController@dashboard')->name('dashboard');
    });
});
```

---

## Definição de Rotas - Exemplos Completos

### Rotas Simples

```php
use Og\Modules\Common\Facades\Route;

// GET simples
Route::get('/health', fn() => response()->json(['status' => 'ok']));

// POST com controller
Route::post('/users', 'UserController@store')->name('users.store');

// Route com parâmetros
Route::get('/users/{id}', 'UserController@show')->name('users.show');
Route::get('/posts/{slug}', 'PostController@show')->name('posts.show');

// Parâmetros opcionais
Route::get('/users/{id?}', 'UserController@showOrList');
```

### Grupos com Controller

```php
Route::controller(AppointmentController::class)
    ->prefix('appointments')
    ->name('appointments.')
    ->group(function() {
        Route::get('/', 'index')->name('index');
        Route::get('/{id}', 'show')->name('show');
        Route::post('/', 'create')->name('create');
        Route::put('/{id}', 'update')->name('update');
        Route::delete('/{id}', 'delete')->name('delete');
    });
```

### API Resources Completo

```php
Route::name('api.')
    ->prefix('api/v1')
    ->middleware(['auth:api', 'throttle:60,1'])
    ->group(function() {
        
        // Customers
        Route::controller(CustomerController::class)
            ->prefix('customers')
            ->name('customers.')
            ->group(function() {
                Route::get('/', 'index')->name('index');
                Route::post('/', 'create')->name('create');
                Route::get('/{id}', 'show')->name('show');
                Route::put('/{id}', 'update')->name('update');
                Route::delete('/{id}', 'delete')->name('delete');
                
                // Nested resources
                Route::get('/{id}/orders', 'orders')->name('orders');
                Route::get('/{id}/contacts', 'contacts')->name('contacts');
            });
        
        // Appointments
        Route::controller(AppointmentController::class)
            ->prefix('appointments')
            ->name('appointments.')
            ->middleware(['can:view-appointments'])
            ->group(function() {
                Route::get('/', 'index')->name('index');
                Route::post('/', 'create')->name('create');
                Route::get('/{id}', 'show')->name('show');
            });
    });
```

### Exclusão de Middlewares

```php
Route::middleware(['auth', 'throttle:30,1'])
    ->group(function() {
        // Estas rotas têm auth e throttle
        Route::get('/dashboard', 'DashboardController@index');
        
        // Esta rota exclui throttle mas mantém auth
        Route::get('/realtime-updates', 'UpdateController@stream')
            ->withoutMiddleware(['throttle:30,1']);
    });
```

---

## Referência de Ficheiros

| Componente | Caminho | Linhas |
|------------|---------|--------|
| Request | [Request.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Http/Request.php) | 358 |
| Response | [Response.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Http/Response.php) | 160 |
| Input Trait | [Input.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Http/Input.php) | 148 |
| Controller | [Controller.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Controller.php) | 52 |
| BaseController | [BaseController.php](file:///home/paulo/www/guisoft/ogdevel/Modules/BaseController.php) | 22 |
| RouterCollector | [RouterCollector.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Routing/RouterCollector.php) | 1095 |
| RouteDispatcher | [RouteDispatcher.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Routing/RouteDispatcher.php) | 292 |
| MiddlewareManager | [MiddlewareManager.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Common/Middleware/MiddlewareManager.php) | 295 |
| AppointmentController | [AppointmentController.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Crm/Appointments/Controllers/AppointmentController.php) | 78 |
| CustomerController | [CustomerController.php](file:///home/paulo/www/guisoft/ogdevel/Modules/Entidade/Customers/Controllers/CustomerController.php) | 114 |

---

## Conclusão

O Web Layer do OfficeGest oferece:

- **Request/Response poderosos**: Extensão do Symfony com funcionalidades específicas (JSON automático, dot notation, method override)
- **Controllers flexíveis**: Duas bases (legacy e moderna) com suporte a policies, DTOs e Actions
- **Middleware Pipeline**: Pipeline pattern com grupos, aliases e parâmetros
- **Routing expressivo**: API fluente com grupos, nomes, prefixos e middleware
- **75+ controllers reais**: Implementações de produção em diversos domínios
- **Dependency Injection**: Resolução automática de parâmetros e type-hints
- **Integração legacy**: Coexistência harmoniosa com código existente