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

Core

Routing System

API fluente e expressiva para definição de endpoints. Mapeamento de URLs, grupos, middlewares e injeção de dependência automática.

Introdução

O sistema de rotas é a porta de entrada da aplicação. Mapeia URLs para Controllers, suportando gestão de middleware e validação de parâmetros. As rotas são definidas na pasta Routes/ de cada módulo:

  • Web.php: Interfaces browser (Sessão, CSRF, Cookies).
  • Api.php: Endpoints stateless (JWT/Sanctum, Rate Limiting).

Fluxo do Request

Request RouterCollector (Route Match) Middleware Stack Controller Action Response RouterDispatcher (DI Resolution)

CLI Commands

Ferramentas para acelerar o desenvolvimento e otimizar produção.

Criar Ficheiros de Rotas

# Criar rotas API (Modules/Wmso/Routes/Api.php)
./og make:route Wmso --api

# Criar rotas Web
./og make:route Wmso --web

# Criar para sub-módulo
./og make:route Workshop/Os --api

Gera a classe com namespace correto e implementa RouterInterface.

Cache (Produção)

# Compilar rotas num único JSON
./og route:cache

# Limpar cache (obrigatório após alterações)
./og route:clear

# Listar todas as rotas registadas
./og route:list

Crítico: Em produção, o uso do cache evita o processamento pesado de dezenas de ficheiros a cada request.

💡 route:list

O comando ./og route:list mostra todas as rotas com nome, método HTTP, URI, controller e middlewares aplicados. Útil para debugging.

Roteamento Básico

// Definição simples com Closure
Route::get('/status', function () {
    return 'Sistema Operacional';
});

// Suporte para todos os verbos HTTP
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);

// Múltiplos verbos numa única rota
Route::match(['get', 'post'], '/analytics', [AnalyticsController::class, 'track']);

// Aceitar qualquer verbo HTTP
Route::any('/webhook', [WebhookController::class, 'handle']);

Injeção de Dependências

O RouterDispatcher resolve automaticamente qualquer classe tipada na assinatura da função ou método do controller.

use Og\Modules\Sales\Services\InvoiceService;

Route::get('/invoice/{id}', function (InvoiceService $service, $id) {
    return $service->download($id);
});

Parâmetros de Rota

Capture segmentos da URI definindo parâmetros entre chavetas {}.

Obrigatórios

Route::get('/user/{id}', function ($id) {
    return 'User '.$id;
});

// Múltiplos parâmetros
Route::get('/posts/{post}/comments/{comment}', 
    function ($postId, $commentId) {
        // $postId e $commentId injetados por ordem
    }
);

Opcionais (?)

// Obrigatório definir valor default
Route::get('/posts/{page?}', function ($page = 1) {
    return "Página: $page";
});

// Múltiplos opcionais
Route::get('/search/{query?}/{limit?}', 
    function ($query = '', $limit = 10) {
        // ...
    }
);

⚠️ Ordem dos Parâmetros

Em Closures, a ordem dos argumentos importa (são mapeados pela posição). Em Controllers, o RouterDispatcher pode mapear por nome ou tipo-hint automaticamente.

Validação de Parâmetros (Constraints)

Restrinja os valores dos parâmetros usando expressões regulares com ->where().

// Constraint simples - apenas números
Route::get('/user/{id}', [UserController::class, 'show'])
    ->where('id', '[0-9]+');

// Múltiplos constraints
Route::get('/user/{id}/{name}', [UserController::class, 'profile'])
    ->where(['id' => '[0-9]+', 'name' => '[a-zA-Z]+']);

// Shortcuts comuns
Route::get('/user/{id}', ...)->whereNumber('id');
Route::get('/user/{name}', ...)->whereAlpha('name');
Route::get('/user/{slug}', ...)->whereAlphaNumeric('slug');
Route::get('/category/{uuid}', ...)->whereUuid('uuid');

Global Constraints

Definir padrões globais no RouteServiceProvider:

// Em boot()
Route::pattern('id', '[0-9]+');
Route::pattern('uuid', '[a-f0-9-]{36}');

Tipagem Forte

PHP valida o tipo automaticamente:

Route::get('/user/{id}', function (int $id) {
    // Se {id} não for int, erro lançado
    return User::find($id);
});

Rotas Nomeadas

Route::get('/user/profile', [ProfileController::class, 'show'])
    ->name('profile.show');

// Gerar URL pelo nome
$url = route('profile.show');

// Redirecionar para rota nomeada
return redirect()->route('profile.show');

📛 Dot Notation (.)

Use sempre pontos para separar contexto, módulo e ação. Exemplo: api.wmso.articles.show.

api.wmso.articles.index

Contexto

api.wmso.articles.index

Módulo

api.wmso.articles.index

Recurso.Ação

Geração de URLs

O helper route() gera URLs baseadas nos nomes das rotas, garantindo flexibilidade e evitando hardcoding.

// URL simples
$url = route('profile.show'); 
// → http://app.com/user/profile

// Com parâmetros
$url = route('user.show', ['id' => 123]); 
// → http://app.com/user/123

// Múltiplos parâmetros
$url = route('post.comment', ['post' => 1, 'comment' => 5]); 
// → http://app.com/posts/1/comments/5

// Parâmetros extras → query string
$url = route('user.index', ['page' => 2, 'sort' => 'name']); 
// → http://app.com/users?page=2&sort=name

Em Views Blade

<a href="{{ route('profile.show') }}">
    Ver Perfil
</a>

<form action="{{ route('user.update', $user) }}">
    ...
</form>

Absolute vs Relative

// URL absoluta (padrão)
route('home'); 
// → http://app.com/

// Path relativo
route('home', [], false); 
// → /

Grupos e Organização

Evite repetição agrupando rotas com prefixos, middlewares ou controllers comuns.

Estrutura Hierárquica

Route::name('admin.')
    ->prefix('admin')
    ->middleware('auth')
    ->group(function () {
        
        Route::get('/users', 'Users@index')
            ->name('users'); 
            // URL: /admin/users
            // Nome: admin.users
            
    });

Controller Grouping

Route::controller(OrderController::class)
    ->group(function () {
        Route::get('/orders', 'index');
        Route::post('/orders', 'store');
        Route::get('/orders/{id}', 'show');
        Route::put('/orders/{id}', 'update');
        Route::delete('/orders/{id}', 'destroy');
    });

// Exemplo real: API do módulo WMSO

Route::name('api.')
    ->prefix('api')
    ->middleware(['api', 'throttle:60,1'])
    ->group(function () {
        
        Route::name('wmso.')
            ->prefix('wmso')
            ->group(function () {
                
                Route::name('articles.')
                    ->controller(ArticleController::class)
                    ->group(function () {
                        Route::get('/', 'index')->name('index');       // api.wmso.articles.index
                        Route::get('/{id}', 'show')->name('show');     // api.wmso.articles.show
                        Route::post('/', 'store')->name('store');      // api.wmso.articles.store
                    });
                    
            });
    });

Middleware

Middlewares filtram e processam requests antes de chegarem ao controller. Podem ser aplicados a rotas individuais ou grupos.

Rota Individual

Route::get('/admin', [AdminController::class, 'index'])
    ->middleware('auth');

// Múltiplos middlewares
Route::get('/dashboard', ...)
    ->middleware(['auth', 'verified']);

Em Grupos

Route::middleware(['auth', 'admin'])
    ->group(function () {
        Route::get('/users', ...);
        Route::get('/settings', ...);
    });

// Middlewares com parâmetros

// Rate limiting: 60 requests por minuto
Route::middleware('throttle:60,1')->group(...);

// Verificar role específica
Route::middleware('role:admin')->group(...);

// Excluir middleware específico
Route::withoutMiddleware('csrf')->post('/webhook', ...);
Middleware Descrição
authRequer autenticação
guestApenas visitantes (não autenticados)
throttle:X,YRate limit (X requests por Y minutos)
verifiedEmail verificado
signedURL com assinatura válida

Rotas de Conveniência

Shortcuts para casos comuns que não precisam de controller.

Route::redirect()

// Redirect 302 (temporário)
Route::redirect('/here', '/there');

// Redirect 301 (permanente)
Route::permanentRedirect(
    '/old', 
    '/new'
);

Route::view()

// Renderizar view diretamente
Route::view('/about', 'pages.about');

// Com dados
Route::view('/terms', 'legal.terms', [
    'version' => '2.1'
]);

Route::fallback()

// 404 customizado
Route::fallback(function () {
    return response()
        ->view('errors.404')
        ->setStatusCode(404);
});

Helpers e Introspecção

O trait RouteHelper no Request permite verificar onde estamos e aceder a parâmetros da rota.

routeIs($pattern)

Verifica nome da rota (suporta wildcards).

if ($req->routeIs('admin.*')) { }
if ($req->routeIs('*.store')) { }

is($pattern)

Verifica URL (path).

if ($req->is('api/v1/*')) { }

route($param)

Obtém valor de parâmetro.

$id = $req->route('id');
// URL: /api/wmso/articles/10?page=2

// Path sem query string
$request->path();          // "api/wmso/articles/10"

// URL completa
$request->url();           // "http://domain.com/api/wmso/articles/10"

// URL com query string
$request->fullUrl();       // "http://domain.com/api/wmso/articles/10?page=2"

// Segmentos da URL (array)
$request->segments();      // ['api', 'wmso', 'articles', '10']

// Segmento específico (1-based index)
$request->segment(2);      // 'wmso'
$request->segment(4);      // '10'

// Todos os parâmetros da rota
$request->routeParameters(); // ['id' => '10']

Form Method Spoofing

Formulários HTML não suportam PUT, PATCH ou DELETE nativamente. Use a diretiva @method().

<form action="/users/1" method="POST">
    @method('PUT')
    @csrf
    
    <!-- campos do formulário -->
</form>

<!-- Para DELETE -->
<form action="/users/1" method="POST">
    @method('DELETE')
    @csrf
    <button type="submit">Eliminar</button>
</form>

Troubleshooting

❌ Route [name] not defined

Verifique se definiu o ->name(). Lembre-se que em grupos, os prefixos concatenam-se. Use ./og route:list para confirmar os nomes finais.

⚠️ Parâmetro Nulo

Se $request->route('id') for null, certifique-se que a rota tem {id}. Query strings (?id=1) usam $request->input('id').

🔄 Cache Desatualizado

Após alterar rotas em produção, execute ./og route:clear && ./og route:cache. O cache antigo não reflete as novas alterações.

🔍 Debug de Rotas

Para ver todas as rotas registadas: dd(RouterCollector::getInstance()->getRoutes()) ou use ./og route:list.

Boas Práticas

✅ Faça

  • Use dot notation consistente nos nomes
  • Agrupe rotas por controller sempre que possível
  • Aplique constraints em IDs numéricos
  • Use route() em vez de URLs hardcoded
  • Mantenha um ficheiro de rotas por contexto (Api vs Web)
  • Execute route:cache em produção

❌ Evite

  • Closures complexas nas rotas (dificulta cache)
  • Nomes genéricos como index sem prefixo
  • Lógica de negócio dentro de route files
  • Misturar convenções de nomenclatura
  • Rotas duplicadas (mesmo método + URI)
  • Ignorar middlewares de segurança

Referência Rápida

Método/Helper Descrição Exemplo
Route::get/post/put/...Definir rota com verbo HTTPRoute::get('/users', ...)
->name()Atribuir nome à rota->name('users.index')
->where()Constraint regex->where('id', '[0-9]+')
->middleware()Aplicar middleware->middleware('auth')
route($name, $params)Gerar URL por nomeroute('user.show', ['id' => 1])
$req->routeIs()Verificar nome da rota$req->routeIs('admin.*')
$req->route($param)Obter parâmetro da rota$req->route('id')
$req->segment($n)Segmento N da URL (1-based)$req->segment(2)