feat(router): atualizar versão do AxiumPHP e melhorar gerenciamento de rotas

This commit is contained in:
Claudecio Martins
2025-10-26 21:07:28 +01:00
parent 9cfa79db4b
commit 6071550f20
5 changed files with 176 additions and 198 deletions

View File

@@ -2,27 +2,19 @@
namespace AxiumPHP\Core;
use Exception;
use AxiumPHP\Helpers\RequestHelper;
class Router {
private static $routes = [];
private static $params = [];
private static $ROUTER_MODE = null;
private static $APP_SYS_MODE = null;
private static $ROUTER_ALLOWED_ORIGINS = ['*'];
private static string $basePath = '';
private static $currentGroupPrefix = '';
private static $currentGroupMiddlewares = [];
private static array $requiredConstants = [
'ROUTER_MODE',
'APP_SYS_MODE'
];
private static array $allowedHttpRequests = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS'
];
private static $ROUTER_ALLOWED_ORIGINS = ['*'];
private static array $requiredConstants = ['ROUTER_MODE', 'APP_SYS_MODE'];
private static array $allowedHttpRequests = ['GET','POST','PUT','PATCH','DELETE','OPTIONS'];
/**
* Construtor que vai garantir que as constantes necessárias estejam definidas antes de
@@ -32,6 +24,11 @@
// Verificar as constantes no momento da criação da instância
self::checkRequiredConstant();
// Verifica se tem diretório base
if(defined(constant_name: 'ROUTER_BASE_PATH')) {
self::$basePath = ROUTER_BASE_PATH;
}
// Define constante
self::$ROUTER_MODE = strtoupper(string: ROUTER_MODE);
self::$APP_SYS_MODE = strtoupper(string: APP_SYS_MODE);
@@ -74,99 +71,26 @@
}
}
/**
* Adiciona uma rota com método GET à lista de rotas da aplicação.
*
* Este método é um atalho para adicionar rotas com o método HTTP GET. Ele
* chama o método `addRoute` internamente, passando os parâmetros
* fornecidos e o método 'GET'.
*
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
* @param array $handler Um array contendo o nome do controlador e o nome da ação
* que devem ser executados quando a rota for
* corresponder (ex: ['UsuarioController', 'index']).
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
* devem ser executados antes do handler da rota.
*
* @return void
*/
// =======================
// Métodos HTTP
// =======================
public static function GET(string $uri, array $handler, array $middlewares = []): void {
self::addRoute(method: "GET", uri: $uri, handler: $handler, middlewares: $middlewares);
}
/**
* Adiciona uma rota com método POST à lista de rotas da aplicação.
*
* Este método é um atalho para adicionar rotas com o método HTTP POST. Ele
* chama o método `addRoute` internamente, passando os parâmetros
* fornecidos e o método 'POST'.
*
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
* @param array $handler Um array contendo o nome do controlador e o nome da ação
* que devem ser executados quando a rota for
* corresponder (ex: ['UsuarioController', 'salvar']).
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
* devem ser executados antes do handler da rota.
*
* @return void
*/
public static function POST(string $uri, array $handler, array $middlewares = []): void {
self::addRoute(method: "POST", uri: $uri, handler: $handler, middlewares: $middlewares);
}
/**
* Adiciona uma rota com método PUT à lista de rotas da aplicação.
*
* Este método é um atalho para adicionar rotas com o método HTTP PUT. Ele
* chama o método `addRoute` internamente, passando os parâmetros
* fornecidos e o método 'PUT'.
*
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
* @param array $handler Um array contendo o nome do controlador e o nome da ação
* que devem ser executados quando a rota for
* corresponder (ex: ['UsuarioController', 'salvar']).
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
* devem ser executados antes do handler da rota.
*
* @return void
*/
public static function PUT(string $uri, array $handler, array $middlewares = []): void {
self::addRoute(method: "PUT", uri: $uri, handler: $handler, middlewares: $middlewares);
}
/**
* Adiciona uma nova rota que responde a requisições PATCH.
*
* Este é um método estático de conveniência que simplifica o registro de rotas
* para o método HTTP PATCH. Ele delega a tarefa principal para o método
* privado `addRoute`, passando o método HTTP, a URI, a função de manipulação
* (`handler`) e quaisquer middlewares associados.
*
* @param string $uri A URI da rota (ex: '/api/resource/{id}').
* @param array $handler Um array de dois elementos que define o manipulador da rota: o nome da classe do controlador e o nome do método a ser executado.
* @param array $middlewares Um array opcional de middlewares a serem executados antes do manipulador da rota.
* @return void
*/
public static function PATCH(string $uri, array $handler, array $middlewares = []): void {
self::addRoute(method: "PATCH", uri: $uri, handler: $handler, middlewares: $middlewares);
}
/**
* Adiciona uma rota com método DELETE à lista de rotas da aplicação.
*
* Este método é um atalho para adicionar rotas com o método HTTP DELETE. Ele
* chama o método `addRoute` internamente, passando os parâmetros
* fornecidos e o método 'DELETE'.
*
* @param string $uri O caminho da rota (ex: '/usuarios', '/produtos').
* @param array $handler Um array contendo o nome do controlador e o nome da ação
* que devem ser executados quando a rota for
* corresponder (ex: ['UsuarioController', 'salvar']).
* @param array $middlewares Um array opcional contendo os nomes dos middlewares que
* devem ser executados antes do handler da rota.
*
* @return void
*/
public static function DELETE(string $uri, array $handler, array $middlewares = []): void {
self::addRoute(method: "DELETE", uri: $uri, handler: $handler, middlewares: $middlewares);
}
@@ -189,7 +113,7 @@
* @return void
*/
private static function addRoute(string $method, string $uri, array $handler, array $middlewares = []): void {
self::$routes[] = [
self::$routes[$method][] = [
'method' => strtoupper(string: $method),
'path' => '/' . trim(string: self::$currentGroupPrefix . '/' . trim(string: $uri, characters: '/'), characters: '/'),
'controller' => $handler[0],
@@ -198,6 +122,46 @@
];
}
/**
* Redireciona para um caminho específico.
*
* Este método estático constrói uma URL de redirecionamento com base no
* caminho fornecido e no `basePath` configurado. Ele garante que o
* `basePath` e o `path` comecem com uma barra (`/`), monta a URL
* completa, limpa quaisquer duplicações de barras e executa o
* redirecionamento HTTP.
*
* @param string|null $path O caminho para o qual redirecionar. Se nulo ou vazio,
* redireciona para o `basePath`.
*
* @return void
*/
public static function redirect(?string $path): void {
// Garante que o basePath começa com "/"
$basePath = self::$basePath ? '/' . trim(string: self::$basePath, characters: '/') : '/';
// Se não tiver caminho, redireciona pro basePath direto
if (empty($path)) {
header(header: "Location: {$basePath}/");
exit;
}
// Garante que o path começa com "/"
if (!str_starts_with(haystack: $path, needle: '/')) {
$path = "/{$path}";
}
// Monta o destino completo
$redirectPath = "{$basePath}{$path}";
// Limpa duplicações tipo "//"
$redirectPath = preg_replace(pattern: '#/+#', replacement: '/', subject: $redirectPath);
// Executa o redirecionamento
header(header: "Location: {$redirectPath}");
exit;
}
/**
* Verifica se um caminho de rota corresponde a um caminho de requisição.
*
@@ -497,6 +461,50 @@
return $preparedParams;
}
/**
* Exibe a página de erro 404 (Página não encontrada).
*
* Este método estático define o código de resposta HTTP como 404 e renderiza
* a view "/Errors/404" para exibir a página de erro. Após a renderização,
* o script é encerrado.
*
* @return void
*/
private static function pageNotFound(): void {
switch (self::$ROUTER_MODE) {
case 'VIEW':
// Notifica erro em caso constante não definida
if(!defined(constant_name: 'ERROR_404_VIEW_PATH')) {
RequestHelper::sendJsonResponse(
response_code: 500,
status: 'error',
message: "Constante 'ERROR_404_VIEW_PATH' não foi definida."
);
}
// Caso o arquivo da constante não exista, notifica erro
if(!file_exists(filename: ERROR_404_VIEW_PATH)) {
RequestHelper::sendJsonResponse(
response_code: 500,
status: 'error',
message: "Arquivo da constante 'ERROR_404_VIEW_PATH' não foi encontrado."
);
}
http_response_code(response_code: 404);
require_once ERROR_404_VIEW_PATH;
break;
case 'JSON':
RequestHelper::sendJsonResponse(
response_code: 404,
status: 'error',
message: "Página não encontrada."
);
break;
}
}
/**
* Despacha a requisição para o controlador e ação correspondentes.
*
@@ -513,10 +521,21 @@
// Verificar as constantes necessárias antes de processar a requisição
self::checkRequiredConstant();
// Inicializa dados da requisição
$requestData = null;
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url(url: $_SERVER['REQUEST_URI'], component: PHP_URL_PATH);
$uri = trim(string: rtrim(string: $uri, characters: '/'), characters: '/');
// Remove o basePath do início da URI (se tiver)
$basePath = rtrim(string: self::$basePath ?? '', characters: '/');
if (!empty($basePath) && str_starts_with(haystack: $uri, needle: $basePath)) {
$uri = substr(string: $uri, offset: strlen(string: $basePath));
}
// Limpa possíveis barras extras
$uri = trim(string: $uri, characters: '/');
// Suporte ao _method em POST para PUT/DELETE/PATCH
if ($method === 'POST' && isset($_POST['_method'])) {
$method = strtoupper(string: $_POST['_method']);
@@ -524,13 +543,11 @@
// Verifica se o método HTTP é permitido
if(!in_array(needle: $method, haystack: self::$allowedHttpRequests)) {
http_response_code(response_code: 405);
header(header: 'Content-Type: application/json; charset=utf-8');
echo json_encode(value: [
"status" => 'error',
"message" => "Método HTTP '{$method}' não permitido."
]);
exit;
RequestHelper::sendJsonResponse(
response_code: 405,
status: 'error',
message: "Método HTTP '{$method}' não permitido."
);
}
// =============================
@@ -572,43 +589,54 @@
// =============================
// EXTRAI DADOS DE PUT, DELETE, PATCH
// =============================
$requestData = match($method) {
'GET' => null,
'POST' => null,
default => self::extractRequestData(method: $method)
};
if(in_array(needle: $method, haystack: ['PUT', 'DELETE', 'PATCH'])) {
$requestData = self::extractRequestData(method: $method);
}
// Seleciona apenas as rotas do método atual
$routes = self::$routes[$method] ?? [];
// =============================
// LOOP PARA PROCESSAR ROTAS
// =============================
foreach (self::$routes as $route) {
if (self::matchRoute(method: $method, uri: $uri, route: $route)) {
// Executa middlewares
if (!empty($route['middlewares']) && !self::runMiddlewares(middlewares: $route['middlewares'])) {
return; // Middleware bloqueou
}
if(!empty($routes)) {
foreach ($routes as $route) {
if (self::matchRoute(method: $method, uri: $uri, route: $route)) {
// Executa middlewares
if (!empty($route['middlewares']) && !self::runMiddlewares(middlewares: $route['middlewares'])) {
return; // Middleware bloqueou
}
$controller = new $route['controller']();
$action = $route['action'];
$params = self::prepareMethodParameters(method: $method, params: [$requestData]);
$controller = new $route['controller']();
$action = $route['action'];
$params = self::prepareMethodParameters(method: $method, params: [$requestData]);
switch (self::$ROUTER_MODE) {
case 'VIEW':
if (method_exists(object_or_class: $controller, method: $action)) {
http_response_code(response_code: 200);
call_user_func_array(callback: [$controller, $action], args: $params);
exit;
}
break;
switch (self::$ROUTER_MODE) {
case 'VIEW':
if (method_exists(object_or_class: $controller, method: $action)) {
http_response_code(response_code: 200);
call_user_func_array(callback: [$controller, $action], args: $params);
exit;
}
break;
case 'JSON':
if (method_exists(object_or_class: $controller, method: $action)) {
http_response_code(response_code: 200);
header(header: 'Content-Type: application/json; charset=utf-8');
call_user_func_array(callback: [$controller, $action], args: $params);
exit;
}
break;
case 'JSON':
if (method_exists(object_or_class: $controller, method: $action)) {
http_response_code(response_code: 200);
header(header: 'Content-Type: application/json; charset=utf-8');
call_user_func_array(callback: [$controller, $action], args: $params);
exit;
}
break;
default:
RequestHelper::sendJsonResponse(
response_code: 500,
status: 'error',
message: "Modo de roteamento inválido."
);
break;
}
}
}
}
@@ -619,54 +647,4 @@
self::pageNotFound();
exit;
}
/**
* Exibe a página de erro 404 (Página não encontrada).
*
* Este método estático define o código de resposta HTTP como 404 e renderiza
* a view "/Errors/404" para exibir a página de erro. Após a renderização,
* o script é encerrado.
*
* @return void
*/
private static function pageNotFound(): void {
switch (self::$ROUTER_MODE) {
case 'VIEW':
// Notifica erro em caso constante não definida
if(!defined(constant_name: 'ERROR_404_VIEW_PATH')) {
http_response_code(response_code: 500);
header(header: 'Content-Type: application/json; charset=utf-8');
echo json_encode( value: [
"status" => 'error',
"message" => "Constante 'ERROR_404_VIEW_PATH' não foi definida.",
]);
exit;
}
// Caso o arquivo da constante não exista, notifica erro
if(!file_exists(filename: ERROR_404_VIEW_PATH)) {
http_response_code(response_code: 500);
header(header: 'Content-Type: application/json; charset=utf-8');
echo json_encode( value: [
"status" => 'error',
"message" => "Arquivo da constante 'ERROR_404_VIEW_PATH' não foi encontrado.",
]);
exit;
}
http_response_code(response_code: 404);
require_once ERROR_404_VIEW_PATH;
break;
case 'JSON':
http_response_code(response_code: 404);
header(header: 'Content-Type: application/json; charset=utf-8');
echo json_encode( value: [
"status" => 'error',
"message" => "Página não encontrada.",
]);
break;
}
}
}