Roteamento

No Lithe, você pode definir rotas para controlar como seu aplicativo responde às solicitações dos clientes usando URIs (caminhos) e métodos HTTP como GET, POST, entre outros. o Lithe oferece flexibilidade e simplicidade para definir e organizar suas rotas. Personalize o comportamento do seu aplicativo facilmente, conectando rotas a funções ou controladores específicos para manipular requisições de forma eficiente.

Definindo rotas

No Lithe, você define como o aplicativo responde a solicitações para endpoints usando URIs e métodos HTTP como GET e POST. Métodos como get(), post() e use() são usados para lidar com essas requisições e aplicar middlewares. Cada rota pode ter uma ou mais funções de callback (manipuladores), que são executadas quando a rota é correspondida. Para passar o controle entre funções, é necessário utilizar o $next(), permitindo que múltiplos manipuladores sejam usados em sequência. Para definir rotas no Lithe, utilizamos a estrutura:

$app->METHOD($PATH, $HANDLER);

Onde:

  • $app é uma instância da classe Lithe\App.
  • METHOD é o método HTTP (em letras minúsculas) como get, post, etc.
  • $PATH é o caminho na URL que dispara a função.
  • $HANDLER é a função executada quando a rota é correspondida.

Veja abaixo um exemplo simples de rota no Lithe:

$app = new \Lithe\App;

$app->get('/', function ($req, $res) {
    return $res->send('Hello World!');
});

$app->listen(); 

Neste exemplo, definimos uma rota que responde com "Hello World!" quando uma solicitação GET é feita para a página inicial


Métodos de Rota

Os métodos de rota no Lithe são derivados dos métodos HTTP e são anexados à instância da classe App. Você pode definir rotas para diferentes métodos HTTP, como GET, POST, PUT e DELETE. Aqui está um exemplo de como definir rotas na raiz do aplicativo:

$app->get('/', function ($req, $res) {
    return $res->send('Hello World!');
});

$app->post('/', function ($req, $res) {
    return $res->send('Got a POST request');
});

$app->put('/user', function ($req, $res) {
    return $res->send('Got a PUT request at /user');
});

$app->delete('/user', function ($req, $res) {
    return $res->send('Got a DELETE request at /user');
});

O Lithe permite o registro de rotas para qualquer verbo HTTP:

$app->get($path, $handler);
$app->post($path, $handler);
$app->put($path, $handler);
$app->delete($path, $handler);
$app->patch($path, $handler);
$app->options($path, $handler);
$app->head($path, $handler);

Se precisar registrar uma rota que responda a múltiplos verbos HTTP, utilize o método match ou o método any para registrar uma rota que responda a todos os verbos.

$app->match(['get', 'post'], '/', function ($req, $res) {
    // ...
});
 
$app->any('/', function ($req, $res) {
    // ...
});

Definindo Parâmetros nas rotas

Os parâmetros de rota são partes da URL que têm nomes específicos e são usadas para capturar valores. Esses valores capturados são colocados na propriedade params de uma instância da classe Request, onde cada nome de parâmetro de rota se torna uma chave com seu respectivo valor.

Route path: /users/:userId/books/:bookId
Request URL: http://localhost:8000/users/34/books/8989
$req->params: { "userId": "34", "bookId": "8989" }

Parâmetro Simples

Para definir rotas com parâmetros de rota, basta especificar os parâmetros de rota no caminho da rota, conforme mostrado abaixo.

$app->get('/users/:userId/books/:bookId', function ($req, $res) {
    $res->send($req->params);
});

O nome dos parâmetros de rota deve ser composto por “caracteres de palavra” ([A-Za-z0-9_]).

Parâmetros Tipados

Definição de Parâmetros Tipados

Você pode definir parâmetros tipados para garantir que os valores atendam a critérios específicos.

$app->get('/user/:id=int', function ($req, $res) {
    $res->send($req->param('id'));
});

No exemplo acima, o parâmetro id deve ser um número inteiro (int). Se a solicitação recebida não corresponder às restrições do padrão da rota, uma resposta HTTP 404 será retornada.

Os tipos de dados dos parâmetros são convertidos automaticamente nos bastidores, garantindo que o valor do parâmetro seja do tipo especificado.

Uso do Operador | para Tipos Opcionais

Você também pode usar o operador | para definir tipos opcionais nos parâmetros de rota, permitindo diferentes tipos ou formatos.

$app->get('/user/:identifier=int|username', function ($req, $res) {
    $res->send($req->param('identifier'));
});

No exemplo acima, :identifier pode ser um número inteiro (int) ou um username que corresponde a ([a-zA-Z0-9_]{3,16}).

Parâmetros com Expressões Regulares

Para ter mais controle sobre a string exata que pode ser correspondida por um parâmetro de rota, você pode usar expressões regulares com o tipo genérico regex.

$app->get('/user/:name=regex<[a-z]+>', function ($req, $res) {
    $res->send($req->param('name'));
});

Neste exemplo, o parâmetro name deve atender à expressão regular [a-z]+, que corresponde a uma sequência de letras minúsculas.

Essa abordagem permite maior flexibilidade e controle na definição de rotas, garantindo que os parâmetros atendam a critérios específicos.

Tipos de parâmetros

Aqui está a equivalência dos tipos de parâmetros listados com as expressões regulares correspondentes:

  • int: [0-9]+ (um ou mais dígitos numéricos)
  • string: [^/]+ (um ou mais caracteres que não sejam barra /)
  • uuid: [a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8} (UUID no formato padrão)
  • date: \d{4}\-\d{1,2}\-\d{1,2} (data no formato YYYY-MM-DD)
  • email: [^\s@]+@[^\s@]+\.[^\s@]+ (endereço de email válido)
  • bool: (false|true|0|1) (valores booleanos: false, true, 0 ou 1)
  • float: [-+]?[0-9]*\.?[0-9]+ (número de ponto flutuante)
  • slug: [a-z0-9]+(?:-[a-z0-9]+)* (slug válido, usado em URLs)
  • username: [a-zA-Z0-9_]{3,16} (username com letras, números e _, entre 3 e 16 caracteres)
  • tel: \+?[\d\-\(\)]+ (número de telefone, opcionalmente começando com + e contendo dígitos, hífens e parênteses)
  • alphanumeric: [a-zA-Z0-9]+ (cadeia alfanumérica, letras e números)
  • regex<Pattern>: Qualquer expressão regular válida que você queira aplicar como filtro para o parâmetro. Por exemplo, regex<(true|false)> para aceitar apenas true ou false

Essas expressões regulares são usadas para validar e capturar os parâmetros nas rotas, garantindo que eles correspondam aos critérios esperados. Se um parâmetro não corresponder ao padrão especificado para seu tipo, pode resultar em erro 404.


Manipuladores de Rota

Você pode fornecer múltiplas funções de callback que se comportam como middlewares para lidar com uma requisição. A única exceção é que esses callbacks podem invocar $next() para chamar os callbacks de rota restantes. Você pode usar esse mecanismo para impor pré-condições em uma rota e, em seguida, passar o controle para rotas subsequentes se não houver motivo para continuar com a rota atual.

Os manipuladores de rota podem estar na forma de uma função, uma callable array ou combinações de ambos, conforme mostrado nos exemplos a seguir.

Uma única função de callback pode lidar com uma rota. Por exemplo:

$app->get('/example/a', function ($req, $res) {
  $res->send('Hello from A!');
})

Mais de uma função de callback pode lidar com uma rota (certifique-se de especificar $next como terceiro parâmetro e chamar ele para invocar a próxima função no pipeline de middleware ou controlador). Por exemplo:

$app->get('/example/b', function ($req, $res, $next) {
  error_log('A resposta será enviada pela próxima função ...');
  $next();
}, function ($req, $res) {
  $res->send('Hello from B!');
})

Uma callable array pode lidar com uma rota. Por exemplo:

class UserController
{
    public static function index ($req, $res) {
        return $res->view('users.index');
    }
}

$app->get('/user', [UserController::class, 'index']);

A combinação de funções independentes e callable array pode lidar com uma rota. Por exemplo:

$app->get('/user', function ($req, $res, $next) {
  error_log('A resposta será enviada pela próxima função ...');
  $next();
}, [UserController::class, 'index']);

Manipuladores de Rota Encadeáveis

Você também pode criar manipuladores de rota encadeáveis para um caminho de rota usando o metódo route. Como o caminho é especificado em um único local, criar rotas modulares é útil para reduzir redundâncias e erros de digitação. Para mais informações sobre rotas, consulte a documentação da classe \Lithe\Http\Router.

Aqui está um exemplo de manipuladores de rota encadeados que são definidos usando o metódo route.

$app->route('/livro')
  ->get(function ($req, $res) {
    $res->send('Obter um livro aleatório');
  })
  ->post(function ($req, $res) {
    $res->send('Adicionar um livro');
  })
  ->put(function ($req, $res) {
    $res->send('Atualizar o livro');
  });

Criando Roteadores Modulares

A classe Router do Lithe permite criar manipuladores de rota modulares e montáveis. Isso proporciona um sistema completo de middleware e roteamento, frequentemente referida como uma "mini-aplicação". Vamos explorar como construir e integrar um roteador em sua aplicação de forma clara e organizada.

Estrutura do Roteador

Neste exemplo, você aprenderá a criar um roteador como um módulo separado, configurar middleware, definir rotas e, finalmente, integrar o módulo de roteador à sua aplicação principal. Isso não só melhora a organização do código, mas também facilita a manutenção e a escalabilidade.

Uma das maneiras de estruturar seu roteador é usando uma abordagem funcional, que pode ser mais intuitiva em algumas situações.

  • Cada arquivo é tratado como uma instância separada do roteador, permitindo que você organize suas rotas de maneira mais intuitiva.
  • Com a modularidade, é mais fácil adicionar novos roteadores ou modificar existentes sem afetar outras partes da aplicação.
  • Funções como get e post são específicas para aquela instância do arquivo, o que ajuda na leitura e na manutenção do código.
1

Criação do Arquivo do Roteador

Crie um arquivo chamado birds.php no diretório da sua aplicação. Este arquivo será responsável por definir as rotas relacionadas a pássaros.

src/birds.php
<?php
use function Lithe\Orbis\Http\Router\{get, apply};

// Middleware específico para este roteador
$timeLog = function ($req, $res, $next) {
    echo 'Time: ' . date('Y-m-d');
    $next(); // Chama o próximo middleware ou rota
};

apply($timeLog); // Aplica o middleware ao roteador

// Define a rota da página inicial
get('/', function ($req, $res) {
    $res->send('Página inicial dos pássaros');
});

// Define a rota "Sobre"
get('/sobre', function ($req, $res) {
    $res->send('Sobre os pássaros');
});
  • Aqui, usamos apply($timeLog) para aplicar o middleware, mantendo a sintaxe simples e clara.
2

Integração do Roteador na Aplicação Principal

Para integrar o roteador à sua aplicação, utilize a função router, que converte arquivos únicos em roteadores prontos para gerenciar suas rotas de forma simples e eficiente! Agora, vamos carregar o módulo do roteador na aplicação principal. Abra o arquivo src/App.php e adicione o seguinte código:

// src/App.php

use function Lithe\Orbis\Http\Router\{router};

// Carrega o roteador a partir do arquivo 'birds.php'
$birds = router(__DIR__ . '/birds');

// Monta o roteador na aplicação na rota '/birds'
$app->use('/birds', $birds);

Agora, sua aplicação estará preparada para lidar com requisições para /birds e /birds/sobre, além de invocar o middleware $timeLog, que é específico para o roteador. Um roteador pode conter outros roteadores, permitindo uma estrutura hierárquica que organiza melhor as rotas e funcionalidades da aplicação. Assim como na aplicação principal, os roteadores são montados da mesma maneira, facilitando a modularização e a manutenção do código. Isso promove uma melhor escalabilidade e clareza na organização das rotas.


Organizando suas Rotas de Forma Modular e Automática

O método set('routes', ...) facilita o registro de rotas, tornando o código mais limpo e organizado. Com ele, você só precisa estruturar suas pastas e arquivos de rotas, e o sistema cuidará de montá-las automaticamente.

Como Funciona

Quando você usa set('routes', ...), o sistema localiza e carrega automaticamente todos os arquivos PHP dentro da pasta especificada (e subpastas). As rotas são montadas com base na estrutura das pastas, e cada arquivo representa uma rota com seu próprio caminho:

  • Se você tiver um arquivo cart.php, ele criará a rota /cart.
  • Já o arquivo admin/dashboard.php criará a rota /admin/dashboard.

Dentro do diretório de rotas, o arquivo index.php é sempre interpretado como a rota principal de uma pasta. Por exemplo, se você tiver um arquivo index.php no diretório de rotas, ele será mapeado para a rota /, representando a raiz da aplicação.

Porém, ao utilizar subpastas, como panel/index.php, o sistema não irá mapear para a rota /panel, mas sim para /panel/index. Para garantir que a rota seja corretamente mapeada para /panel, você deve nomear o arquivo como panel.php (sem o index.php), assim:

  • index.php → mapeia para a rota /
  • panel.php → mapeia para a rota /panel

Essa abordagem ajuda a evitar conflitos de rotas e torna a estrutura de arquivos mais clara e intuitiva.

Configuração

Estrutura de Diretórios:

/routes
    cart.php
    checkout.php
    /admin
        dashboard.php
        users.php

Definindo Rotas nos Arquivos:

Em cada arquivo, você pode definir suas rotas usando o estilo de código que preferir, seja a sintaxe funcional ou a sintaxe clássica.

Arquivo cart.php:

get('/', function ($req, $res) { 
  $res->send('Cart'); 
});

Arquivo admin/dashboard.php:

$router->get('/', function ($req, $res) { 
  $res->send('Admin Dashboard'); 
});

Configuração da Aplicação

Para definir o caminho das suas rotas e habilitar o carregamento automático, basta adicionar a seguinte configuração na sua aplicação:

$app->set('routes', __DIR__ . '/routes');  // Define o caminho e carrega rotas automaticamente

A estrutura de diretórios facilita a visualização e manutenção de rotas. Basta organizar suas pastas e arquivos e deixar o sistema gerenciar os caminhos.