Roteamento
Visão Geral
O roteamento no Flight PHP mapeia padrões de URL para funções de retorno ou métodos de classe, permitindo um tratamento de requisições rápido e simples. Ele é projetado para ter sobrecarga mínima, ser amigável para iniciantes e extensível sem dependências externas.
Entendendo
O roteamento é o mecanismo central que conecta requisições HTTP à lógica da sua aplicação no Flight. Ao definir rotas, você especifica como diferentes URLs acionam códigos específicos, seja por meio de funções, métodos de classe ou ações de controladores. O sistema de roteamento do Flight é flexível, suportando padrões básicos, parâmetros nomeados, expressões regulares e recursos avançados como injeção de dependência e roteamento de recursos. Essa abordagem mantém seu código organizado e fácil de manter, permanecendo rápido e simples para iniciantes e extensível para usuários avançados.
Nota: Quer entender mais sobre roteamento? Acesse a página "por que um framework?" para uma explicação mais detalhada.
Uso Básico
Definindo uma Rota Simples
O roteamento básico no Flight é feito combinando um padrão de URL com uma função de retorno ou um array de classe e método.
Flight::route('/', function(){
echo 'hello world!';
});As rotas são correspondidas na ordem em que são definidas. A primeira rota que corresponder a uma requisição será invocada.
Usando Funções como Retornos de Chamada
O retorno de chamada pode ser qualquer objeto que seja chamável. Então você pode usar uma função normal:
function hello() {
echo 'hello world!';
}
Flight::route('/', 'hello');Usando Classes e Métodos como um Controlador
Você também pode usar um método (estático ou não) de uma classe:
class GreetingController {
public function hello() {
echo 'hello world!';
}
}
Flight::route('/', [ 'GreetingController','hello' ]);
// ou
Flight::route('/', [ GreetingController::class, 'hello' ]); // método preferido
// ou
Flight::route('/', [ 'GreetingController::hello' ]);
// ou
Flight::route('/', [ 'GreetingController->hello' ]);Ou criando um objeto primeiro e depois chamando o método:
use flight\Engine;
// GreetingController.php
class GreetingController
{
protected Engine $app
public function __construct(Engine $app) {
$this->app = $app;
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
// index.php
$app = Flight::app();
$greeting = new GreetingController($app);
Flight::route('/', [ $greeting, 'hello' ]);Nota: Por padrão, quando um controlador é chamado dentro do framework, a classe
flight\Engineé sempre injetada, a menos que você especifique por meio de um contêiner de injeção de dependência
Roteamento Específico por Método
Por padrão, os padrões de rota são correspondidos a todos os métodos de requisição. Você pode responder a métodos específicos colocando um identificador antes da URL.
Flight::route('GET /', function () {
echo 'I received a GET request.';
});
Flight::route('POST /', function () {
echo 'I received a POST request.';
});
// Você não pode usar Flight::get() para rotas, pois esse é um método
// para obter variáveis, não para criar uma rota.
Flight::post('/', function() { /* código */ });
Flight::patch('/', function() { /* código */ });
Flight::put('/', function() { /* código */ });
Flight::delete('/', function() { /* código */ });Você também pode mapear vários métodos para um único retorno de chamada usando um delimitador |:
Flight::route('GET|POST /', function () {
echo 'I received either a GET or a POST request.';
});Tratamento Especial para Requisições HEAD e OPTIONS
O Flight fornece tratamento integrado para requisições HTTP HEAD e OPTIONS:
Requisições HEAD
- Requisições HEAD são tratadas exatamente como requisições
GET, mas o Flight remove automaticamente o corpo da resposta antes de enviá-la ao cliente. - Isso significa que você pode definir uma rota para
GET, e requisições HEAD para a mesma URL retornarão apenas cabeçalhos (sem conteúdo), conforme esperado pelos padrões HTTP.
Flight::route('GET /info', function() {
echo 'This is some info!';
});
// Uma requisição HEAD para /info retornará os mesmos cabeçalhos, mas sem corpo.Requisições OPTIONS
Requisições OPTIONS são tratadas automaticamente pelo Flight para qualquer rota definida.
- Quando uma requisição OPTIONS é recebida, o Flight responde com um status
204 No Contente um cabeçalhoAllowlistando todos os métodos HTTP suportados para aquela rota. - Você não precisa definir uma rota separada para OPTIONS.
// Para uma rota definida como:
Flight::route('GET|POST /users', function() { /* ... */ });
// Uma requisição OPTIONS para /users responderá com:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONSUsando o Objeto Router
Além disso, você pode obter o objeto Router que possui alguns métodos auxiliares para você usar:
$router = Flight::router();
// mapeia todos os métodos assim como Flight::route()
$router->map('/', function() {
echo 'hello world!';
});
// Requisição GET
$router->get('/users', function() {
echo 'users';
});
$router->post('/users', function() { /* código */});
$router->put('/users/update/@id', function() { /* código */});
$router->delete('/users/@id', function() { /* código */});
$router->patch('/users/@id', function() { /* código */});Expressões Regulares (Regex)
Você pode usar expressões regulares em suas rotas:
Flight::route('/user/[0-9]+', function () {
// Isso corresponderá a /user/1234
});Embora este método esteja disponível, é recomendado usar parâmetros nomeados, ou parâmetros nomeados com expressões regulares, pois são mais legíveis e fáceis de manter.
Parâmetros Nomeados
Você pode especificar parâmetros nomeados em suas rotas, que serão passados para sua função de retorno. Isso é mais para legibilidade da rota do que qualquer outra coisa. Consulte a seção abaixo sobre a importante ressalva.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!";
});Você também pode incluir expressões regulares com seus parâmetros nomeados usando o delimitador ::
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// Isso corresponderá a /bob/123
// Mas não corresponderá a /bob/12345
});Nota: A correspondência de grupos de regex
()com parâmetros posicionais não é suportada. Ex::'\(
Ressalva Importante
Embora no exemplo acima pareça que @name está diretamente ligado à variável $name, não está. A ordem dos parâmetros na função de retorno é o que determina o que é passado para ela. Se você trocar a ordem dos parâmetros na função de retorno, as variáveis também serão trocadas. Aqui está um exemplo:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!";
});E se você acessar a seguinte URL: /bob/123, a saída seria hello, 123 (bob)!. Por favor, tenha cuidado ao configurar suas rotas e suas funções de retorno!
Parâmetros Opcionais
Você pode especificar parâmetros nomeados que são opcionais para correspondência, envolvendo segmentos entre parênteses.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// Isso corresponderá às seguintes URLs:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);Quaisquer parâmetros opcionais que não forem correspondidos serão passados como NULL.
Roteamento Curinga
A correspondência é feita apenas em segmentos individuais de URL. Se você quiser corresponder a vários segmentos, pode usar o curinga *.
Flight::route('/blog/*', function () {
// Isso corresponderá a /blog/2000/02/01
});Para rotear todas as requisições para um único retorno de chamada, você pode fazer:
Flight::route('*', function () {
// Faça algo
});Manipulador de 404 Não Encontrado
Por padrão, se uma URL não puder ser encontrada, o Flight enviará uma resposta HTTP 404 Not Found muito simples e básica. Se você quiser ter uma resposta 404 mais personalizada, pode mapear seu próprio método notFound:
Flight::map('notFound', function() {
$url = Flight::request()->url;
// Você também poderia usar Flight::render() com um modelo personalizado.
$output = <<<HTML
<h1>My Custom 404 Not Found</h1>
<h3>The page you have requested {$url} could not be found.</h3>
HTML;
$this->response()
->clearBody()
->status(404)
->write($output)
->send();
});Manipulador de Método Não Encontrado
Por padrão, se uma URL for encontrada, mas o método não for permitido, o Flight enviará uma resposta HTTP 405 Method Not Allowed muito simples e básica (Ex: Método Não Permitido. Métodos Permitidos são: GET, POST). Ele também incluirá um cabeçalho Allow com os métodos permitidos para aquela URL.
Se você quiser ter uma resposta 405 mais personalizada, pode mapear seu próprio método methodNotFound:
use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
$url = Flight::request()->url;
$methods = implode(', ', $route->methods);
// Você também poderia usar Flight::render() com um modelo personalizado.
$output = <<<HTML
<h1>My Custom 405 Method Not Allowed</h1>
<h3>The method you have requested for {$url} is not allowed.</h3>
<p>Allowed Methods are: {$methods}</p>
HTML;
$this->response()
->clearBody()
->status(405)
->setHeader('Allow', $methods)
->write($output)
->send();
});Uso Avançado
Injeção de Dependência em Rotas
Se você quiser usar injeção de dependência por meio de um contêiner (PSR-11, PHP-DI, Dice, etc.), o único tipo de rotas em que isso está disponível é criando diretamente o objeto você mesmo e usando o contêiner para criar seu objeto, ou você pode usar strings para definir a classe e o método a serem chamados. Você pode ir para a página de Injeção de Dependência para mais informações.
Aqui está um exemplo rápido:
use flight\database\SimplePdo;
// Greeting.php
class Greeting
{
protected SimplePdo $db;
public function __construct(SimplePdo $db) {
$this->db = $db;
}
public function hello(int $id) {
// faça algo com $this->db
$name = $this->db->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! My name is {$name}!";
}
}
// index.php
// Configure o contêiner com os parâmetros que você precisar
// Veja a página de Injeção de Dependência para mais informações sobre PSR-11
$dice = new \Dice\Dice();
// Não se esqueça de reatribuir a variável com '$dice = '!!!!!
$dice = $dice->addRule(SimplePdo::class, [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Registre o manipulador do contêiner
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Rotas como de costume
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// ou
Flight::route('/hello/@id', 'Greeting->hello');
// ou
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();Passando a Execução para a Próxima Rota
Obsoleto
Você pode passar a execução para a próxima rota correspondente retornando true da sua função de retorno.
Flight::route('/user/@name', function (string $name) {
// Verifique alguma condição
if ($name !== "Bob") {
// Continue para a próxima rota
return true;
}
});
Flight::route('/user/*', function () {
// Isto será chamado
});Agora é recomendado usar middleware para lidar com casos de uso complexos como este.
Apelidos de Rota (Aliasing)
Ao atribuir um apelido a uma rota, você pode posteriormente chamar esse apelido em seu aplicativo dinamicamente para ser gerado mais tarde no seu código (ex: um link em um template HTML, ou gerar uma URL de redirecionamento).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// ou
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
// mais tarde em algum lugar do código
class UserController {
public function update() {
// código para salvar o usuário...
$id = $user['id']; // 5 por exemplo
$redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // retornará '/users/5'
Flight::redirect($redirectUrl);
}
}
Isso é especialmente útil se a sua URL mudar. No exemplo acima, digamos que os usuários foram movidos para /admin/users/@id em vez disso. Com o apelido em vigor para a rota, você não precisa mais encontrar todas as URLs antigas no seu código e alterá-las, porque o apelido agora retornará /admin/users/5 como no exemplo acima.
O apelido de rota também funciona em grupos:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// ou
Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});Inspecionando Informações da Rota
Se você quiser inspecionar as informações da rota correspondida, há 2 maneiras de fazer isso:
- Você pode usar a propriedade
executedRouteno objetoFlight::router(). - Você pode pedir para que o objeto de rota seja passado para seu retorno de chamada passando
truecomo o terceiro parâmetro no método de rota. O objeto de rota será sempre o último parâmetro passado para sua função de retorno.
executedRoute
Flight::route('/', function() {
$route = Flight::router()->executedRoute;
// Faça algo com $route
// Matriz de métodos HTTP correspondidos
$route->methods;
// Matriz de parâmetros nomeados
$route->params;
// Expressão regular correspondente
$route->regex;
// Contém o conteúdo de qualquer '*' usado no padrão de URL
$route->splat;
// Mostra o caminho da url... se você realmente precisar
$route->pattern;
// Mostra qual middleware está atribuído a isto
$route->middleware;
// Mostra o apelido atribuído a esta rota
$route->alias;
});Nota: A propriedade
executedRoutesó será definida depois que uma rota for executada. Se você tentar acessá-la antes de uma rota ser executada, ela seráNULL. Você também pode usar executedRoute em middleware também!
Passando true para a definição da rota
Flight::route('/', function(\flight\net\Route $route) {
// Matriz de métodos HTTP correspondidos
$route->methods;
// Matriz de parâmetros nomeados
$route->params;
// Expressão regular correspondente
$route->regex;
// Contém o conteúdo de qualquer '*' usado no padrão de URL
$route->splat;
// Mostra o caminho da url... se você realmente precisar
$route->pattern;
// Mostra qual middleware está atribuído a isto
$route->middleware;
// Mostra o apelido atribuído a esta rota
$route->alias;
}, true);// <-- Este parâmetro true é o que faz isso acontecerAgrupamento de Rotas e Middleware
Pode haver momentos em que você queira agrupar rotas relacionadas (como /api/v1). Você pode fazer isso usando o método group:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Corresponde a /api/v1/users
});
Flight::route('/posts', function () {
// Corresponde a /api/v1/posts
});
});Você pode até aninhar grupos de grupos:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() obtém variáveis, não define uma rota! Veja o contexto de objeto abaixo
Flight::route('GET /users', function () {
// Corresponde a GET /api/v1/users
});
Flight::post('/posts', function () {
// Corresponde a POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Corresponde a PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() obtém variáveis, não define uma rota! Veja o contexto de objeto abaixo
Flight::route('GET /users', function () {
// Corresponde a GET /api/v2/users
});
});
});Agrupamento com Contexto de Objeto
Você ainda pode usar o agrupamento de rotas com o objeto Engine da seguinte forma:
$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
// use a variável $router
$router->get('/users', function () {
// Corresponde a GET /api/v1/users
});
$router->post('/posts', function () {
// Corresponde a POST /api/v1/posts
});
});Nota: Este é o método preferido para definir rotas e grupos com o objeto
$router.
Agrupamento com Middleware
Você também pode atribuir middleware a um grupo de rotas:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Corresponde a /api/v1/users
});
}, [ MyAuthMiddleware::class ]); // ou [ new MyAuthMiddleware() ] se você quiser usar uma instânciaVeja mais detalhes na página de middleware de grupo.
Roteamento de Recursos
Você pode criar um conjunto de rotas para um recurso usando o método resource. Isso criará um conjunto de rotas para um recurso que segue as convenções RESTful.
Para criar um recurso, faça o seguinte:
Flight::resource('/users', UsersController::class);E o que acontecerá em segundo plano é que ele criará as seguintes rotas:
[
'index' => 'GET /users',
'create' => 'GET /users/create',
'store' => 'POST /users',
'show' => 'GET /users/@id',
'edit' => 'GET /users/@id/edit',
'update' => 'PUT /users/@id',
'destroy' => 'DELETE /users/@id'
]E seu controlador usará os seguintes métodos:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}Nota: Você pode visualizar as rotas recém-adicionadas com
runwayexecutandophp runway routes.
Personalizando Rotas de Recurso
Existem algumas opções para configurar as rotas de recurso.
Base do Apelido
Você pode configurar o aliasBase. Por padrão, o apelido é a última parte da URL especificada. Por exemplo, /users/ resultaria em um aliasBase de users. Quando essas rotas são criadas, os apelidos são users.index, users.create, etc. Se você quiser alterar o apelido, defina o aliasBase para o valor desejado.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);Only e Except
Você também pode especificar quais rotas deseja criar usando as opções only e except.
// Permita apenas esses métodos na lista branca e bloqueie o restante
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);// Bloqueie apenas esses métodos e permita o restante
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);Estas são basicamente opções de lista branca e lista negra, para que você possa especificar quais rotas deseja criar.
Middleware
Você também pode especificar middleware para ser executado em cada uma das rotas criadas pelo método resource.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);Respostas em Streaming
Agora você pode transmitir respostas para o cliente usando stream() ou streamWithHeaders(). Isso é útil para enviar arquivos grandes, processos de longa duração ou gerar respostas grandes. O streaming de uma rota é tratado de forma um pouco diferente de uma rota comum.
Nota: Respostas em streaming só estão disponíveis se você tiver
flight.v2.output_bufferingdefinido comofalse.
Streaming com Cabeçalhos Manuais
Você pode transmitir uma resposta para o cliente usando o método stream() em uma rota. Se fizer isso, você deve definir todos os cabeçalhos manualmente antes de enviar qualquer coisa para o cliente. Isso é feito com a função header() do PHP ou com o método Flight::response()->setRealHeader().
Flight::route('/@filename', function($filename) {
$response = Flight::response();
// obviamente você sanitizaria o caminho e tal.
$fileNameSafe = basename($filename);
// Se você tiver cabeçalhos adicionais para definir aqui depois que a rota for executada
// você deve defini-los antes que qualquer coisa seja exibida.
// Todos devem ser uma chamada direta à função header() ou
// uma chamada a Flight::response()->setRealHeader()
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// ou
$response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'File not found');
}
// defina manualmente o comprimento do conteúdo, se quiser
header('Content-Length: '.filesize($filePath));
// ou
$response->setRealHeader('Content-Length: '.filesize($filePath));
// Transmita o arquivo para o cliente enquanto ele é lido
readfile($filePath);
// Esta é a linha mágica aqui
})->stream();Streaming com Cabeçalhos
Você também pode usar o método streamWithHeaders() para definir os cabeçalhos antes de começar o streaming.
Flight::route('/stream-users', function() {
// você pode adicionar quaisquer cabeçalhos adicionais que quiser aqui
// você só precisa usar header() ou Flight::response()->setRealHeader()
// no entanto você obtém seus dados, apenas como exemplo...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// Isso é necessário para enviar os dados ao cliente
ob_flush();
}
echo '}';
// É assim que você definirá os cabeçalhos antes de começar o streaming.
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// código de status opcional, padrão 200
'status' => 200
]);Veja Também
- Middleware - Usando middleware com rotas para autenticação, registro de logs, etc.
- Injeção de Dependência - Simplificando a criação e o gerenciamento de objetos em rotas.
- Por que um Framework? - Entendendo os benefícios de usar um framework como o Flight.
- Estendendo - Como estender o Flight com sua própria funcionalidade, incluindo o método
notFound. - php.net: preg_match - Função PHP para correspondência de expressões regulares.
Solução de Problemas
- Os parâmetros da rota são correspondidos por ordem, não por nome. Garanta que a ordem dos parâmetros do retorno de chamada corresponda à definição da rota.
- Usar
Flight::get()não define uma rota; useFlight::route('GET /...')para roteamento ou o contexto do objeto Router em grupos (ex.:$router->get(...)). - A propriedade executedRoute só é definida depois que uma rota é executada; ela é NULL antes da execução.
- Streaming exige que a funcionalidade legada de buffer de saída do Flight esteja desativada (
flight.v2.output_buffering = false). - Para injeção de dependência, apenas certas definições de rota suportam instanciação baseada em contêiner.
404 Não Encontrado ou Comportamento Inesperado de Rota
Se você está vendo um erro 404 Não Encontrado (mas você jura de pé junto que ele realmente está lá e não é um erro de digitação), isso na verdade pode ser um problema com você retornando um valor no endpoint da rota em vez de apenas exibi-lo. A razão para isso é intencional, mas pode pegar alguns desenvolvedores de surpresa.
Flight::route('/hello', function(){
// Isso pode causar um erro 404 Não Encontrado
return 'Hello World';
});
// O que você provavelmente quer
Flight::route('/hello', function(){
echo 'Hello World';
});A razão para isso é um mecanismo especial embutido no roteador que trata a saída de retorno como um sinal para "ir para a próxima rota". Você pode ver o comportamento documentado na seção Roteamento.
Registro de Alterações
- v3: Adicionado roteamento de recursos, apelidos de rota e suporte a streaming, grupos de rotas e suporte a middleware.
- v1: A grande maioria dos recursos básicos disponíveis.