From 1efb37960167108bf181162fde5996c6074acc59 Mon Sep 17 00:00:00 2001 From: flashwave Date: Thu, 28 Mar 2024 20:01:34 +0000 Subject: [PATCH] Work in progress router rewrite. --- VERSION | 2 +- src/Http/Routing/HandlerAttribute.php | 48 +++++++++++ src/Http/Routing/HttpDelete.php | 15 ++++ src/Http/Routing/HttpGet.php | 15 ++++ src/Http/Routing/HttpMiddleware.php | 11 +++ src/Http/Routing/HttpOptions.php | 15 ++++ src/Http/Routing/HttpPatch.php | 15 ++++ src/Http/Routing/HttpPost.php | 15 ++++ src/Http/Routing/HttpPut.php | 15 ++++ src/Http/Routing/HttpRoute.php | 31 ++++++++ src/Http/Routing/HttpRouter.php | 106 +++++++++++++++++++++++++ src/Http/Routing/IRouter.php | 102 ++++++++++++++++++++++++ src/Http/Routing/IRouterHandler.php | 18 +++++ src/Http/Routing/ResolvedRouteInfo.php | 39 +++++++++ src/Http/Routing/RouteHandler.php | 14 ++++ src/Http/Routing/RouteHandlerTrait.php | 16 ++++ src/Http/Routing/RouterTrait.php | 36 +++++++++ src/Http/Routing/ScopedRouter.php | 42 ++++++++++ 18 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 src/Http/Routing/HandlerAttribute.php create mode 100644 src/Http/Routing/HttpDelete.php create mode 100644 src/Http/Routing/HttpGet.php create mode 100644 src/Http/Routing/HttpMiddleware.php create mode 100644 src/Http/Routing/HttpOptions.php create mode 100644 src/Http/Routing/HttpPatch.php create mode 100644 src/Http/Routing/HttpPost.php create mode 100644 src/Http/Routing/HttpPut.php create mode 100644 src/Http/Routing/HttpRoute.php create mode 100644 src/Http/Routing/HttpRouter.php create mode 100644 src/Http/Routing/IRouter.php create mode 100644 src/Http/Routing/IRouterHandler.php create mode 100644 src/Http/Routing/ResolvedRouteInfo.php create mode 100644 src/Http/Routing/RouteHandler.php create mode 100644 src/Http/Routing/RouteHandlerTrait.php create mode 100644 src/Http/Routing/RouterTrait.php create mode 100644 src/Http/Routing/ScopedRouter.php diff --git a/VERSION b/VERSION index 56e670d..a3e96be 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2402.62138 +0.2403.281959 diff --git a/src/Http/Routing/HandlerAttribute.php b/src/Http/Routing/HandlerAttribute.php new file mode 100644 index 0000000..2e59c7c --- /dev/null +++ b/src/Http/Routing/HandlerAttribute.php @@ -0,0 +1,48 @@ +path; + } + + /** + * Reads attributes from methods in a IRouteHandler instance and registers them to a given IRouter instance. + * + * @param IRouter $router Router instance. + * @param IRouteHandler $handler Handler instance. + */ + public static function register(IRouter $router, IRouteHandler $handler): void { + $objectInfo = new ReflectionObject($handler); + $methodInfos = $objectInfo->getMethods(); + + foreach($methodInfos as $methodInfo) { + $attrInfos = $methodInfo->getAttributes(HandlerAttribute::class); + + foreach($attrInfos as $attrInfo) { + $handlerInfo = $attrInfo->newInstance(); + $closure = $methodInfo->getClosure($methodInfo->isStatic() ? null : $handler); + + if($handlerInfo instanceof HttpRoute) + $router->add($handlerInfo->getMethod(), $handlerInfo->getPath(), $closure); + else + $router->use($handlerInfo->getPath(), $closure); + } + } + } +} diff --git a/src/Http/Routing/HttpDelete.php b/src/Http/Routing/HttpDelete.php new file mode 100644 index 0000000..e732d42 --- /dev/null +++ b/src/Http/Routing/HttpDelete.php @@ -0,0 +1,15 @@ +method = $method; + } + + /** + * Returns the target method name. + * + * @return string + */ + public function getMethod(): string { + return $this->method; + } +} diff --git a/src/Http/Routing/HttpRouter.php b/src/Http/Routing/HttpRouter.php new file mode 100644 index 0000000..40ccb69 --- /dev/null +++ b/src/Http/Routing/HttpRouter.php @@ -0,0 +1,106 @@ +middlewares[] = $mwInfo = new stdClass; + $mwInfo->handler = $handler; + + $prepared = self::preparePath($path, true); + $mwInfo->dynamic = $prepared !== false; + + if($mwInfo->dynamic) + $mwInfo->match = $prepared; + else + $mwInfo->prefix = $path; + } + + public function add(string $method, string $path, callable $handler): void { + if($method === '') + throw new InvalidArgumentException('$method may not be empty'); + + $method = strtoupper($method); + if(trim($method) !== $method) + throw new InvalidArgumentException('$method may start or end with whitespace'); + + $prepared = self::preparePath($path, false); + if($prepared === false) { + if(is_array($this->staticRoutes[$path])) + $this->staticRoutes[$path][$method] = $handler; + else + $this->staticRoutes[$path] = [$method => $handler]; + } else { + if(array_key_exists($prepared, $this->dynamicRoutes)) + $this->dynamicRoutes[$prepared][$method] = $handler; + else + $this->dynamicRoutes[$prepared] = [$method => $handler]; + } + } + + public function resolve(string $method, string $path): ResolvedRouteInfo { + $middlewares = []; + + foreach($this->middlewares as $mwInfo) { + if($mwInfo->dynamic) { + if(preg_match($mwInfo->match, $path, $args) !== 1) + continue; + + array_shift($args); + } else { + if(!str_starts_with($path, $mwInfo->prefix)) + continue; + + $args = []; + } + + $middlewares[] = [$mwInfo->handler, $args]; + } + + $methods = []; + $handler = null; + $args = []; + + if(array_key_exists($path, $this->staticRoutes)) { + $methods = $this->staticRoutes[$path]; + } else { + foreach($this->dynamicRoutes as $rPattern => $rMethods) + if(preg_match($rPattern, $path, $args) === 1) { + $methods = $rMethods; + break; + } + } + + $method = strtoupper($method); + if(array_key_exists($method, $methods)) + $handler = $methods[$method]; + + return new ResolvedRouteInfo($middlewares, array_keys($methods), $handler, $args); + } +} diff --git a/src/Http/Routing/IRouter.php b/src/Http/Routing/IRouter.php new file mode 100644 index 0000000..ede9356 --- /dev/null +++ b/src/Http/Routing/IRouter.php @@ -0,0 +1,102 @@ +middlewares as $middleware) { + $result = $middleware[0](...array_merge($args, $middleware[1])); + if($result !== null) + return $result; + } + } + + public function hasHandler(): bool { + return $this->handler !== null; + } + + public function hasOtherMethods(): bool { + return !empty($this->supportedMethods); + } + + public function getSupportedMethods(): array { + return $this->supportedMethods; + } + + public function dispatch(array $args): mixed { + return $this->handler(...array_merge($args, $this->args)); + } +} diff --git a/src/Http/Routing/RouteHandler.php b/src/Http/Routing/RouteHandler.php new file mode 100644 index 0000000..d41078d --- /dev/null +++ b/src/Http/Routing/RouteHandler.php @@ -0,0 +1,14 @@ +add('get', $path, $handler); + } + + public function post(string $path, callable $handler): void { + $this->add('post', $path, $handler); + } + + public function delete(string $path, callable $handler): void { + $this->add('delete', $path, $handler); + } + + public function patch(string $path, callable $handler): void { + $this->add('patch', $path, $handler); + } + + public function put(string $path, callable $handler): void { + $this->add('put', $path, $handler); + } + + public function options(string $path, callable $handler): void { + $this->add('options', $path, $handler); + } + + public function register(IRouteHandler $handler): void { + $handler->registerRoutes($this); + } +} diff --git a/src/Http/Routing/ScopedRouter.php b/src/Http/Routing/ScopedRouter.php new file mode 100644 index 0000000..068e003 --- /dev/null +++ b/src/Http/Routing/ScopedRouter.php @@ -0,0 +1,42 @@ +getParentRouter(); + $this->router = $router; + + // todo: cleanup + $this->prefix = $prefix; + } + + private function getParentRouter(): IRouter { + return $this->router; + } + + public function scopeTo(string $prefix): IRouter { + return $this->router->scopeTo($this->prefix . $prefix); + } + + public function add(string $method, string $path, callable $handler): void { + $this->router->add($method, $this->prefix . $path, $handler); + } + + public function use(string $path, callable $handler): void { + $this->router->use($this->prefix . $path, $handler); + } + + public function resolve(string $method, string $path): ResolvedRouteInfo { + return $this->router->resolve($method, $this->prefix . $path); + } +}