diff --git a/Routes.php b/Routes.php index 4641cfe..5351909 100644 --- a/Routes.php +++ b/Routes.php @@ -12,11 +12,11 @@ namespace Gac\Routing; + use Gac\Routing\Exceptions\CallbackNotFound; use Gac\Routing\Exceptions\RouteNotFoundException; use JetBrains\PhpStorm\Pure; - class Routes { /** @@ -45,33 +45,29 @@ class Routes public const DELETE = "DELETE"; /** - * @var Request $request Instance of a Request class to be passed as an argument to routes callback + * @var string $prefix Routes prefix */ - public Request $request; + private string $prefix = ""; /** - * @var array $routes List of available routs + * @var array $middlewares List of middlewares to be executed before the routes */ - private array $routes = []; + private array $middlewares = []; /** - * Return the list of defined routed - * - * @return array + * @var array $routes List of available routs */ - public function getRoutes(): array { - return $this->routes; - } + private array $routes = []; /** - * @var string $prefix Prefix to be added to routes being created + * @var array $routes Temporary holder of list until the all get stored in the primary $routes array */ - private string $prefix = ""; + private array $tmpRoutes = []; /** - * @var array $middlewares List of middlewares to be executed before accessing a route + * @var Request $request Instance of a Request class to be passed as an argument to routes callback */ - private array $middlewares = []; + public Request $request; /** * Routes constructor @@ -81,18 +77,38 @@ public function __construct() { } /** - * Method used for adding new routes + * Method used to set the prefix for routes * - * @param string $path Path for the route - * @param callable|array|string $callback Callback method, an anonymous function or a class and method name to be executed - * @param string|array $methods Allowed request method(s) (GET, POST, PUT...) + * @param string $prefix Prefix to be added to all the routes in that chain. + * + * @return Routes Returns an instance of it self so that other methods could be chained onto it + */ + public function prefix(string $prefix = ""): self { + $this->prefix = $prefix; + return $this; + } + + /** + * Method used to set the middlewares for routes + * + * @param array $data List of middlewares to be executed before the routes * - * @return Routes returns an instance of it self so that the next method can be chained onto it + * @return Routes Returns an instance of it self so that other methods could be chained onto it */ - public function add(string $path, callable|array|string $callback, string|array $methods = self::GET): self { + public function middleware(array $data): self { + $this->middlewares = $data; + return $this; + } + + /** + * Method used to handle execution of routes and middlewares + * + * @throws RouteNotFoundException|CallbackNotFound + */ + public function route(string $path, callable|array|string $callback, string|array $methods = self::GET): self { if (is_string($methods)) $methods = [$methods]; - if (!empty($this->prefix)) $path = "{$this->prefix}{$path}"; // Prepend prefix to routes + if (!empty($this->prefix)) $path = $this->prefix . $path; // Prepend prefix to routes if ($path !== "/") $path = rtrim($path, "/"); @@ -107,31 +123,65 @@ public function add(string $path, callable|array|string $callback, string|array } foreach ($methods as $method) { - $this->routes[$method][$path] = [ + $this->tmpRoutes[$method][$path] = [ "callback" => $callback, "middlewares" => $this->middlewares ]; if (!is_null($regex)) { - $this->routes[$method][$path]["regex"] = $regex; + $this->tmpRoutes[$method][$path]["regex"] = $regex; if (!is_null($arguments)) { - $this->routes[$method][$path]["arguments"] = $arguments; + $this->tmpRoutes[$method][$path]["arguments"] = $arguments; } } } - $this->middlewares = []; - $this->prefix = ""; - return $this; } + /** + * Method used for adding new routes + * + * @param string $path Path for the route + * @param callable|array|string|null $callback Callback method, an anonymous function or a class and method name to be executed + * @param string|array|null $methods Allowed request method(s) (GET, POST, PUT...) + * + * @throws CallbackNotFound + * @throws RouteNotFoundException + */ + public function add(string $path = "", callable|array|string|null $callback = NULL, string|array|null $methods = self::GET) { + if (empty($this->tmpRoutes)) { + $this->route($path, $callback, $methods); + } + + foreach ($this->tmpRoutes as $method => $route) { + if (!isset($this->routes[$method])) $this->routes[$method] = []; + $path = array_key_first($route); + + if (count($this->middlewares) > 0 && count($route[$path]["middlewares"]) === 0) { + $route[$path]["middlewares"] = $this->middlewares; + } + + if (!empty($this->prefix) && !str_starts_with($path, $this->prefix)) { + $newPath = rtrim("{$this->prefix}$path", "/"); + $route[$newPath] = $route[$path]; + unset($route[$path]); + } + + $this->routes[$method] = array_merge($this->routes[$method], $route); + } + + $this->prefix = ""; + $this->middlewares = []; + $this->tmpRoutes = []; + } + /** * Method used to handle execution of routes and middlewares * * @throws RouteNotFoundException|CallbackNotFound */ - public function route() { + public function handle() { $path = $this->getPath(); $method = $_SERVER["REQUEST_METHOD"] ?? "GET"; @@ -140,7 +190,7 @@ public function route() { $arguments = []; if ($route === false) { $dynamic_routes = array_filter($this->routes[$method], fn($route) => !is_null($route["regex"] ?? NULL)); - foreach ($dynamic_routes as $route_path => $dynamic_route) { + foreach ($dynamic_routes as $dynamic_route) { if (preg_match("/{$dynamic_route["regex"]}/", $path)) { $route = $dynamic_route; preg_match_all("/{$dynamic_route["regex"]}/", $path, $matches); @@ -172,13 +222,13 @@ public function route() { } } - if ($route === false) throw new RouteNotFoundException("Route {$path} not found", 404); + if ($route === false) throw new RouteNotFoundException("Route $path not found", 404); $middlewares = $route["middlewares"] ?? []; $this->execute_middleware($middlewares); $callback = $route["callback"] ?? false; - if ($callback === false) throw new CallbackNotFound("No callback specified for {$path}", 404); + if ($callback === false) throw new CallbackNotFound("No callback specified for $path", 404); if ((is_string($callback) && class_exists($callback)) || is_array($callback)) { $controller = is_string($callback) ? new $callback : new $callback[0]; // make a new instance of a controller class @@ -186,34 +236,10 @@ public function route() { $callback = [$controller, $fn]; } - if (!is_callable($callback)) throw new CallbackNotFound("Unable to execute callback for {$path}", 404); + if (!is_callable($callback)) throw new CallbackNotFound("Unable to execute callback for $path", 404); call_user_func($callback, $this->request, ...$arguments); } - /** - * Method which adds a prefix to route or a group of routes - * - * @param string $prefix Prefix to be added - * - * @return Routes returns an instance of it self so that the next method can be chained onto it - */ - public function prefix(string $prefix = ""): self { - $this->prefix = $prefix; - return $this; - } - - /** - * Method used to set the middleware to be run before accessing API endpoint - * - * @param array $data List of middlewares to be executed before accessing the endpoint - * - * @return Routes returns an instance of it self so that the next method can be chained onto it - */ - public function middleware(array $data): self { - $this->middlewares = $data; - return $this; - } - /** * Method which executes each specified middleware before the routes callback is executed * @@ -228,7 +254,7 @@ private function execute_middleware(array $data) { $function = [new $function[0], $function[1]]; } - if (!is_callable($function)) throw new CallbackNotFound("Middleware method {$function} not found", 404); + if (!is_callable($function)) throw new CallbackNotFound("Middleware method $function not found", 404); call_user_func($function, $this->request); } @@ -246,4 +272,14 @@ private function execute_middleware(array $data) { $path = ($path !== "/") ? rtrim($path, "/") : $path; return ($position === false) ? $path : substr($path, 0, $position); } - } \ No newline at end of file + + /** + * Return the list of defined routed + * + * @return array + */ + public function getRoutes(): array { + return $this->routes; + } + } + diff --git a/sample/index.php b/sample/index.php index fd86572..3148bd4 100644 --- a/sample/index.php +++ b/sample/index.php @@ -1,4 +1,7 @@ add('/', function (Request $request) { + + $routes->add('/', function () { + echo json_encode(["message" => "Hello World"]); + }); + + $routes + ->prefix("/user") + ->middleware(["verify_token"]) + ->route("/", [HomeController::class, "getUsers"], Routes::GET) + ->route("/", [HomeController::class, "addUser"], Routes::POST) + ->route("/", [HomeController::class, "updateUser"], Routes::PATCH) + ->route("/", [HomeController::class, "replaceUser"], Routes::PUT) + ->add("/", [HomeController::class, "deleteUser"], Routes::DELETE); + + $routes->add('/test', function (Request $request) { $request ->status(200, "OK") ->send(["message" => "Welcome"]); }); - $routes->add('/test', "test_route_function", [Routes::GET, Routes::POST]); - - $routes->prefix("/user") - ->middleware(["verify_token"]) - ->add('/', [HomeController::class, "getUsers"], Routes::GET) - ->add("/", [HomeController::class, "addUser"], Routes::POST) - ->add("/", [HomeController::class, "updateUser"], Routes::PATCH) - ->add("/", [HomeController::class, "replaceUser"], Routes::PUT) - ->add("/", [HomeController::class, "deleteUser"], Routes::DELETE); + $routes->add("/test", function () { + }, [Routes::PATCH, Routes::POST]); $routes->add("/test/{int:userID}-{username}/{float:amount}/{bool:valid}", function ( Request $request, @@ -40,7 +50,7 @@ echo "Dynamic route here"; }); - // $routes->add("/test/{int:userID}-{username}/{float:amount}/{bool:valid}", [HomeController::class, "test"]); # It works like this also + $routes->add("/test/{int:userID}-{username}/{float:amount}/{bool:valid}", [HomeController::class, "test"]); # It works like this also $routes ->middleware([ @@ -51,9 +61,7 @@ ->add("/test", function (Request $request) { $request->send(["message" => "Hello"]); }); - - - $routes->route(); + $routes->handle(); } catch (RouteNotFoundException $ex) { $routes->request->status(404, "Route not found")->send(["error" => ["message" => $ex->getMessage()]]); } catch (CallbackNotFound $ex) {