From 187eeec0a2b164169b686d3f4274ac02f87de59f Mon Sep 17 00:00:00 2001 From: Ahmard Date: Wed, 28 Oct 2020 18:33:35 +0100 Subject: [PATCH] Milstone of changes --- README.md | 61 +++++++++------- composer.json | 3 +- src/Route.php | 23 ++---- src/Route/Collector.php | 96 ++++++++++++++++++++++--- src/Route/DispatchResult.php | 63 ++++++++++++++++ src/Route/Dispatcher.php | 66 +++++++++++++++++ src/Route/TheRoute.php | 136 ++++++++++------------------------- src/RouteInterface.php | 14 ---- 8 files changed, 294 insertions(+), 168 deletions(-) create mode 100644 src/Route/DispatchResult.php create mode 100644 src/Route/Dispatcher.php diff --git a/README.md b/README.md index d439224..f81f154 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # QuickRoute An elegant http router built on top of [FastRoute](https://github.com/nikic/FastRoute) to provide more easy of use. +## Information +Due to object-sharing between routes introduced in version 1, some errors cannot be fixed. +
+Here is version 2, which is object-sharing free. ## Installation ```bash @@ -13,16 +17,38 @@ Simple example ```php use QuickRoute\Route; use QuickRoute\Route\Collector; +use QuickRoute\Route\Dispatcher; -Route::get('/', function (){ +require('vendor/autoload.php'); + +Route::get('/', function () { echo 'Hello world'; }); -$collector = Collector::create() - ->collect() - ->register(); - -$routes = $collector->getCollectedRoutes(); +$method = $_SERVER['REQUEST_METHOD']; +$path = $_SERVER['REQUEST_URI']; +if (false !== $pos = strpos($path, '?')) { + $uri = substr($path, 0, $pos); +} +$path = rawurldecode($path); + + +$collector = Collector::create()->collect()->register(); + +$dispatcher = Dispatcher::create($collector)->dispatch($method, $path); + +switch (true) { + case $dispatcher->isFound(): + $routeData = $dispatcher->getRoute(); + $routeData['controller']($dispatcher->getUrlParameters()); + break; + case $dispatcher->isNotFound(): + echo "Page not found"; + break; + case $dispatcher->isMethodNotAllowed(): + echo "Request method not allowed"; + break; +} ``` Controller-like example @@ -35,31 +61,14 @@ Route::get('/home', 'MainController@home'); Advance usage ```php use QuickRoute\Route; -use QuickRoute\RouteInterface; Route::prefix('user')->name('user.') ->namespace('User') ->middleware('UserMiddleware') - ->group(function (RouteInterface $route){ - $route->get('profile', 'UserController@profile'); - $route->put('update', 'UserController@update'); - }); -``` - -More advance usage -```php -use QuickRoute\Route; -use QuickRoute\RouteInterface; - -Route::prefix('notes')->name('notes.') - ->prepend('api') - ->append('{token}') - ->namespace('User') - ->group(function (RouteInterface $route){ - $route->post('add', 'NotesController@add')->name('add'); - $route->put('{noteId}', 'NotesController@update')->name('update'); + ->group(function (){ + Route::get('profile', 'UserController@profile'); + Route::put('update', 'UserController@update'); }); - ``` Routes as configuration diff --git a/composer.json b/composer.json index 11db22e..aea5660 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ } ], "require": { - "nikic/fast-route": "^1.3" + "nikic/fast-route": "^1.3", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^9" diff --git a/src/Route.php b/src/Route.php index da15793..3ab1f78 100644 --- a/src/Route.php +++ b/src/Route.php @@ -11,8 +11,6 @@ * @method static TheRoute name(string $name) * @method static TheRoute namespace(string $namespace) * @method static TheRoute middleware(string $middleware) - * @method static TheRoute prepend(string $prefixToPrepend) - * @method static TheRoute append(string $prefixToAppend) * @method static TheRoute group(callable $closure) * @method static TheRoute with(array $withDat) * @method static TheRoute get(string $route, $controller) @@ -28,25 +26,21 @@ class Route */ protected static array $called = []; - protected static TheRoute $theRouter; - - protected static array $defaultRouteConfig = []; - /** * @param string $name * @param array $args * @return TheRoute */ - public static function __callStatic($name, $args) + public static function __callStatic(string $name, array $args) { - self::$theRouter = new TheRoute(self::$defaultRouteConfig); - - return self::$theRouter->$name(...$args); + $router = new TheRoute(); + self::$called[] = $router; + return $router->$name(...$args); } - public static function use(array $theRoute) + public static function restart() { - self::$defaultRouteConfig = $theRoute; + self::$called = []; } /** @@ -57,9 +51,4 @@ public static function getRoutes(): array { return self::$called; } - - public static function addRoute(TheRoute $route) - { - self::$called[] = $route; - } } diff --git a/src/Route/Collector.php b/src/Route/Collector.php index 4598424..0216020 100644 --- a/src/Route/Collector.php +++ b/src/Route/Collector.php @@ -17,6 +17,9 @@ class Collector protected array $collectedRoutes = []; + private array $routes = []; + + public static function create() { return new self(); @@ -24,28 +27,24 @@ public static function create() public function collectFile(string $filePath, array $routesInfo = []) { - Route::use($routesInfo); require $filePath; - $this->collectedRoutes += Route::getRoutes(); + $routes = Route::getRoutes(); + $this->collectedRoutes += $this->get($routes); return $this; } public function collect(array $routesInfo = []) { - Route::use($routesInfo); - $this->collectedRoutes += Route::getRoutes(); + $routes = Route::getRoutes(); + $this->collectedRoutes += $this->get($routes); return $this; } public function register() { - foreach ($this->collectedRoutes as $route) { - //Notify that we are about to register this route - $route->onRegister(); - $routeData = $route->getRouteData(); - + foreach ($this->collectedRoutes as $routeData) { //Register route to Nikita's fast route. - $this->getFastRouteCollector()->addRoute(strtoupper($routeData['method']), $routeData['prefix'], $route); + $this->getFastRouteCollector()->addRoute(strtoupper($routeData['method']), $routeData['prefix'], $routeData); } return $this; @@ -70,4 +69,81 @@ public function getCollectedRoutes(): array { return $this->collectedRoutes; } + + private function get(array $routes) + { + $routes = $this->loop($routes); + + $this->build($routes); + + return $this->routes; + } + + /** + * @param TheRoute[] $routes + * @return array + */ + private function loop(array $routes) + { + $results = []; + + for ($i = 0; $i < count($routes); $i++) { + $route = $routes[$i]->getRouteData(); + $results[$i]['route'] = $route; + if (!empty($route['group'])) { + $results[$i]['children'] = $this->loop($this->getGroup($route['group'])); + } + } + + $this->routes = []; + + return $results; + } + + private function build(array $routes, array $parent = []) + { + foreach ($routes as $route) { + $routeData = $route['route']; + if (isset($route['route'])) { + + if (isset($parent['prefix'])) { + $parentPrefix = $parent['prefix']; + if (! empty($routeData['prefix'])) { + $parentPrefix = $parentPrefix . $routeData['prefix']; + } + } + + if (isset($parent['middleware'])) { + $parentMiddleware = $parent['middleware']; + if ($routeData['middleware']) { + $parentMiddleware = $parentMiddleware . '|' . $routeData['middleware']; + } + } + + $data = [ + 'prefix' => ($parentPrefix ?? $routeData['prefix']), + 'namespace' => ($parent['namespace'] ?? '') . $routeData['namespace'], + 'name' => ($parent['name'] ?? '') . $route['route']['name'], + 'controller' => $routeData['controller'], + 'method' => $routeData['method'], + 'middleware' => ($parentMiddleware ?? $routeData['middleware']), + ]; + + if (!empty($routeData['method'])) { + $this->routes[] = $data; + } + + if (isset($route['children'])) { + $this->build($route['children'], $data); + } + } + } + } + + private function getGroup(callable $callback) + { + Route::restart(); + $callback(); + return Route::getRoutes(); + } } \ No newline at end of file diff --git a/src/Route/DispatchResult.php b/src/Route/DispatchResult.php new file mode 100644 index 0000000..e4cf281 --- /dev/null +++ b/src/Route/DispatchResult.php @@ -0,0 +1,63 @@ +dispatchResult = $dispatchResult; + } + + + /** + * If url is found + * @return bool + */ + public function isFound() + { + return $this->dispatchResult[0] === FastDispatcher::FOUND; + } + + /** + * If url is not found + * @return bool + */ + public function isNotFound() + { + return $this->dispatchResult[0] === FastDispatcher::NOT_FOUND; + } + + /** + * If url method is not allowed + * @return bool + */ + public function isMethodNotAllowed() + { + return $this->dispatchResult[0] === FastDispatcher::METHOD_NOT_ALLOWED; + } + + /** + * Get dispatched url parameters + * @return mixed|null + */ + public function getUrlParameters() + { + return $this->dispatchResult[2] ?? null; + } + + /** + * Get found url class + * @return array + */ + public function getRoute(): array + { + return $this->dispatchResult[1] ?? []; + } +} \ No newline at end of file diff --git a/src/Route/Dispatcher.php b/src/Route/Dispatcher.php new file mode 100644 index 0000000..4e1b17d --- /dev/null +++ b/src/Route/Dispatcher.php @@ -0,0 +1,66 @@ +collector = $collector; + } + + /** + * Dispatch url routing + * @param string $method + * @param string $path + * @return DispatchResult + */ + public function dispatch(string $method, string $path): DispatchResult + { + //Remove trailing forward slash + $lengthPath = strlen($path) - 1; + if ($lengthPath > 1 && $path[$lengthPath] == '/') { + $path = substr($path, 0, $lengthPath); + } + + $urlData = $this->createDispatcher() + ->dispatch(strtoupper($method), $path); + + return new DispatchResult($urlData); + } + + /** + * Set your own dispatcher + * @param FastDispatcher $dispatcher + */ + public function setDispatcher(FastDispatcher $dispatcher): void + { + $this->dispatcher = $dispatcher; + } + + /** + * @return FastDispatcher + */ + private function createDispatcher() + { + if(! isset($this->dispatcher)){ + $this->dispatcher = GroupCountBased::class; + } + + $dispatcher = $this->dispatcher; + return (new $dispatcher($this->collector->getFastRouteCollector()->getData())); + } +} \ No newline at end of file diff --git a/src/Route/TheRoute.php b/src/Route/TheRoute.php index faca10e..bc133e6 100644 --- a/src/Route/TheRoute.php +++ b/src/Route/TheRoute.php @@ -20,23 +20,9 @@ class TheRoute implements RouteInterface private string $name = ''; private string $append = ''; private string $prepend = ''; - private string $method; - private $controller; - - public function __construct(array $withData = []) - { - if ($withData) { - $this->isWithUsed = true; - $this->namespace = $withData['namespace'] ?? ''; - $this->name = $withData['name'] ?? ''; - $this->prefix = $withData['prefix'] ?? ''; - $this->controller = $withData['controller'] ?? ''; - $this->middleware = $withData['middleware'] ?? ''; - $this->method = $withData['method'] ?? ''; - $this->prepend = $withData['prepend'] ?? ''; - $this->append = $withData['append'] ?? ''; - } - } + private string $method = ''; + private $controller = ''; + private $group = null; /** * Retrieve controllers defined in this object @@ -55,6 +41,7 @@ public function getRouteData(): array 'name' => $this->name, 'prepend' => $this->prepend, 'append' => $this->append, + 'group' => $this->group, ]; } @@ -64,52 +51,10 @@ public function getRouteData(): array */ public function prefix(string $prefix): self { - if ($this->prefix && !$this->isWithUsed) { - $newRouter = new self([ - 'namespace' => $this->namespace, - 'prefix' => $this->buildPrefix($this->prefix, $prefix), - 'name' => $this->name, - 'middleware' => $this->middleware, - 'prepend' => $this->prepend, - 'append' => $this->append, - ]); - - Route::addRoute($newRouter); - - return $newRouter; - } - - $this->prefix = $this->buildPrefix($this->prefix, $prefix); - + $this->prefix = $prefix; return $this; } - protected function buildPrefix(string $prefix1, string $prefix2) - { - $prefix2 = $this->removeTrailingSlash($prefix2); - if ($prefix2 && $prefix2 != '/') { - return $prefix1 . '/' . $prefix2; - } - - return empty($prefix1) ? '/' : $prefix1; - } - - protected function removeTrailingSlash(string $prefix) - { - $totalStr = strlen($prefix) - 1; - if ($totalStr > 0) { - if ($prefix[$totalStr] == '/' && $totalStr != 0) { - $prefix = substr($prefix, 0, $totalStr); - } - - if ($prefix[0] == '/' && $totalStr != 0) { - $prefix = substr($prefix, 1, $totalStr + 1); - } - } - - return $prefix; - } - /** * Group controllers * @param callable $closure @@ -117,7 +62,7 @@ protected function removeTrailingSlash(string $prefix) */ public function group(callable $closure): self { - $closure($this); + $this->group = $closure; return $this; } @@ -131,7 +76,7 @@ public function namespace(string $namespace): self if ($namespace[strlen($namespace) - 1] !== "\\") { $namespace .= "\\"; } - $this->namespace .= $namespace; + $this->namespace = $namespace; return $this; } @@ -142,7 +87,7 @@ public function namespace(string $namespace): self */ public function name(string $name): self { - $this->name .= $name; + $this->name = $name; return $this; } @@ -153,46 +98,47 @@ public function name(string $name): self */ public function middleware(string $middleware): self { - $this->middleware .= $middleware; + $this->middleware = $middleware; return $this; } /** - * Prepend string to nn url - * @param string $prefixToPrepend + * Register route in this class * @return $this */ - public function prepend(string $prefixToPrepend): self + public function onRegister(): RouteInterface { - $this->prepend .= $prefixToPrepend; + if ($this->prefix[0] != '/') { + $this->prefix = '/' . $this->prefix; + } + return $this; } - /** - * Append string to url - * @param string $prefixToAppend - * @return $this - */ - public function append(string $prefixToAppend): self + protected function buildPrefix(string $prefix1, string $prefix2) { - $this->append .= $prefixToAppend; - return $this; + $prefix2 = $this->removeTrailingSlash($prefix2); + if ($prefix2 && $prefix2 != '/') { + return $prefix1 . '/' . $prefix2; + } + + return empty($prefix1) ? '/' : $prefix1; } - /** - * Register route in this class - * @return $this - */ - public function onRegister(): RouteInterface + protected function removeTrailingSlash(string $prefix) { - $this->prefix = $this->buildPrefix($this->prepend, $this->prefix); - $this->prefix = $this->buildPrefix($this->prefix, $this->append); + $totalStr = strlen($prefix) - 1; + if ($totalStr > 0) { + if ($prefix[$totalStr] == '/' && $totalStr != 0) { + $prefix = substr($prefix, 0, $totalStr); + } - if ($this->prefix[0] != '/') { - $this->prefix = '/' . $this->prefix; + if ($prefix[0] == '/' && $totalStr != 0) { + $prefix = substr($prefix, 1, $totalStr + 1); + } } - return $this; + return $prefix; } /** @@ -204,20 +150,10 @@ public function onRegister(): RouteInterface */ public function add(string $method, string $route, $controllerClass): self { - $newRouter = new self([ - 'namespace' => $this->namespace, - 'prefix' => $this->buildPrefix($this->prefix, $route), - 'name' => $this->name, - 'prepend' => $this->prepend, - 'append' => $this->append, - 'method' => $method, - 'controller' => $controllerClass, - 'middleware' => $this->middleware, - ]); - - Route::addRoute($newRouter); - - return $newRouter; + $this->method = $method; + $this->prefix = $this->buildPrefix($this->prefix, $route); + $this->controller = $controllerClass; + return $this; } } diff --git a/src/RouteInterface.php b/src/RouteInterface.php index cd716f0..82f4263 100644 --- a/src/RouteInterface.php +++ b/src/RouteInterface.php @@ -38,20 +38,6 @@ public function middleware(string $middleware): RouteInterface; */ public function name(string $name): RouteInterface; - /** - * Prepend string to nn url - * @param string $prefixToPrepend - * @return $this - */ - public function prepend(string $prefixToPrepend): RouteInterface; - - /** - * Append string to url - * @param string $prefixToAppend - * @return $this - */ - public function append(string $prefixToAppend): RouteInterface; - /** * * @param string $route