Error 404 - Página no encontrada'; } /** * __construct */ private function __construct() { } /** * Parsea para deectar las pseudovariables (ej: {variable}) * * @param string $path * Ruta con pseudovariables. * * @param callable $callback * Callback que será llamado cuando la ruta configurada en $path coincida. * * @return array * Arreglo con 2 índices: * path - Contiene la ruta con las pseudovariables reeplazadas por expresiones regulares. * callback - Contiene el callback en formato Namespace\Clase::Método. */ private static function parse(string $path, callable $callback): array { preg_match_all('/{(\w+)}/s', $path, $matches, PREG_PATTERN_ORDER); $paramNames = $matches[1]; $path = preg_quote($path, '/'); $path = preg_replace( ['/\\\{\w+\\\}/s'], ['([^\/]+)'], $path ); return [ 'path' => $path, 'callback' => [$callback], 'paramNames' => $paramNames, ]; } /** * Devuelve el ruta base o raiz del proyecto sobre la que trabajará el router. * * Ej: Si la url del sistema está en "https://ejemplo.com/duckbrain" * entonces la ruta base sería "/duckbrain" * * @return string */ public static function basePath(): string { if (defined('SITE_URL') && !empty(SITE_URL)) { return rtrim(parse_url(SITE_URL, PHP_URL_PATH), '/') . '/'; } return str_replace($_SERVER['DOCUMENT_ROOT'], '/', ROOT_DIR); } /** * Redirije a una ruta relativa interna. * * @param string $path * La ruta relativa a la ruta base. * * Ej: Si nuesto sistema está en "https://ejemplo.com/duckbrain" * llamamos a Router::redirect('/docs'), entonces seremos * redirigidos a "https://ejemplo.com/duckbrain/docs". * @return void */ public static function redirect(string $path): void { header('Location: ' . static::basePath() . ltrim($path, '/')); exit; } /** * Añade un middleware a la última ruta usada. * Solo se puede usar un middleware a la vez. * * @param callable $callback * @param int $prioriry * * @return static * Devuelve la instancia actual. */ public static function middleware(callable $callback, ?int $priority = null): static { if (!isset(static::$last)) { return new static(); } $method = static::$last[0]; $index = static::$last[1]; if (isset($priority) && $priority <= 0) { $priority = 1; } if (is_null($priority) || $priority >= count(static::$$method[$index]['callback'])) { static::$$method[$index]['callback'][] = $callback; } else { static::$$method[$index]['callback'] = array_merge( array_slice(static::$$method[$index]['callback'], 0, $priority), [$callback], array_slice(static::$$method[$index]['callback'], $priority) ); } return new static(); } /** * Reconfigura el callback final de la última ruta. * * @param callable $callback * * @return static */ public static function reconfigure(callable $callback): static { if (empty(static::$last)) { return new static(); } $method = static::$last[0]; $index = static::$last[1]; static::$$method[$index]['callback'][0] = $callback; return new static(); } /** * Configura calquier método para todas las rutas. * * En caso de no recibir un callback, busca la ruta actual * solo configura la ruta como la última configurada * siempre y cuando la misma haya sido configurada previamente. * * @param string $method * Método http. * @param string $path * Ruta con pseudovariables. * @param callable|null $callback * * @return * Devuelve la instancia actual. */ public static function configure(string $method, string $path, ?callable $callback = null): static { if (is_null($callback)) { $path = preg_quote($path, '/'); $path = preg_replace( ['/\\\{\w+\\\}/s'], ['([^\/]+)'], $path ); foreach (static::$$method as $index => $router) { if ($router['path'] == $path) { static::$last = [$method, $index]; break; } } return new static(); } static::$$method[] = static::parse($path, $callback); static::$last = [$method, count(static::$$method) - 1]; return new static(); } /** * Define los routers para el método GET. * * @param string $path * Ruta con pseudovariables. * @param callable|null $callback * Callback que será llamado cuando la ruta configurada en $path coincida. * * @return static * Devuelve la instancia actual. */ public static function get(string $path, ?callable $callback = null): static { return static::configure('get', $path, $callback); } /** * Define los routers para el método POST. * * @param string $path * Ruta con pseudovariables. * @param callable|null $callback * Callback que será llamado cuando la ruta configurada en $path coincida. * * @return static * Devuelve la instancia actual. */ public static function post(string $path, ?callable $callback = null): static { return static::configure('post', $path, $callback); } /** * Define los routers para el método PUT. * * @param string $path * Ruta con pseudovariables. * @param callable|null $callback * Callback que será llamado cuando la ruta configurada en $path coincida. * * @return static * Devuelve la instancia actual */ public static function put(string $path, ?callable $callback = null): static { return static::configure('put', $path, $callback); } /** * Define los routers para el método PATCH. * * @param string $path * Ruta con pseudovariables. * @param callable|null $callback * Callback que será llamado cuando la ruta configurada en $path coincida. * * @return static * Devuelve la instancia actual */ public static function patch(string $path, ?callable $callback = null): static { return static::configure('patch', $path, $callback); } /** * Define los routers para el método DELETE. * * @param string $path * Ruta con pseudovariables * @param callable|null $callback * Callback que será llamado cuando la ruta configurada en $path coincida. * * @return static * Devuelve la instancia actual */ public static function delete(string $path, ?callable $callback = null): static { return static::configure('delete', $path, $callback); } /** * Devuelve la ruta actual tomando como raíz la ruta de instalación de DuckBrain. * * @return string */ public static function currentPath(): string { return preg_replace( '/' . preg_quote(static::basePath(), '/') . '/', '/', strtok($_SERVER['REQUEST_URI'], '?'), 1 ); } /** * Aplica la configuración de rutas. * * @param string|null $path (opcional) Ruta a usar. Si no se define, detecta la ruta actual. * * @return void */ public static function apply(?string $path = null): void { $path = $path ?? static::currentPath(); $routers = match ($_SERVER['REQUEST_METHOD']) { // Según el método selecciona un arreglo de routers 'POST' => static::$post, 'PUT' => static::$put, 'PATCH' => static::$patch, 'DELETE' => static::$delete, default => static::$get }; foreach ($routers as $router) { // revisa todos los routers para ver si coinciden con la ruta actual if (preg_match_all('/^' . $router['path'] . '\/?$/si', $path, $matches, PREG_PATTERN_ORDER)) { unset($matches[0]); // Comprobando y guardando los parámetros variables de la ruta if (isset($matches[1])) { foreach ($matches as $index => $match) { $paramName = $router['paramNames'][$index - 1]; static::$currentParams[$paramName] = urldecode($match[0]); } } // Procesa la cola de callbacks foreach (array_reverse($router['callback']) as $callback) { $data = Synapsis::resolve($callback); } // Por defecto imprime como JSON si se retorna algo if (isset($data)) { header('Content-Type: application/json'); print(json_encode($data)); } return; } } // Si no hay router que coincida llamamos a $notFoundCallBack Synapsis::resolve(static::$notFoundCallback); } }