From 50c6499efb0c4f2208eca5473d4be0ffa4961bc9 Mon Sep 17 00:00:00 2001 From: Simon Sessingo Date: Sat, 24 Feb 2018 05:35:05 +0100 Subject: [PATCH] Simplified url-rewriting and callback handling. --- README.md | 116 +------------------ src/Pecee/Http/Request.php | 27 ++++- src/Pecee/SimpleRouter/Route/Route.php | 29 +++-- src/Pecee/SimpleRouter/Router.php | 79 +++++++------ test/Dummy/Middlewares/RewriteMiddleware.php | 16 +++ test/RouterRewriteTest.php | 109 ++++++++++++++++- 6 files changed, 208 insertions(+), 168 deletions(-) create mode 100644 test/Dummy/Middlewares/RewriteMiddleware.php diff --git a/README.md b/README.md index fbf6665..fe07304 100644 --- a/README.md +++ b/README.md @@ -1136,125 +1136,18 @@ $siteId = input('site_id', 2, ['post', 'get']); ## Url rewriting Sometimes it can be useful to manipulate the route about to be loaded. -simple-php-router allows you to easily change the route about to be executed. +simple-php-router allows you to easily manipulate and change the routes which are about to be rendered. All information about the current route is stored in the `\Pecee\SimpleRouter\Router` instance's `loadedRoute` property. -For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`. +For easy access you can use the shortcut helper function `request()` instead of calling the class directly `\Pecee\SimpleRouter\SimpleRouter::router()`. ```php -use Pecee\SimpleRouter; -$request = SimpleRouter::request(); -$request->setRewriteCallback('Example\MyCustomClass@hello'); +request()->setRewriteCallback('Example\MyCustomClass@hello'); // -- or you can rewrite by url -- -$request->setRewriteUrl('/my-rewrite-url'); -``` - -**Note:** It's only possible to change the route BEFORE the route has initially been rendered. You can use the `Request` object to manipulate the route which are about to be loaded. - -### Rewrite using callback - -This method is most efficient, as it will render the route immediately. - -This method is useful for rendering 404-pages etc. - -You can also change the callback by modifying the `$route` parameter. This is perfect if you just want to display a view quickly - or change the callback depending -on some criteria's for the request. - -The callback below will fire immediately after the `Middleware` or `ExceptionHandler` has been loaded, as they are loaded before the route is rendered. -If you wish to change the callback from outside, please have this in mind. - -The example below will render `DefaultController@notFound` regardless of the url. - -**NOTE: Use this method if you want to load another controller. No additional middlewares or rules will be loaded.** - -##### Middleware example - -```php -namespace Demo\Middlewares; - -use Pecee\Http\Middleware\IMiddleware; -use Pecee\Http\Request; - -class CustomMiddleware implements IMiddleware { - - public function handle(Request $request) { - - $request->setRewriteCallback('Demo\Controllers\DefaultController@notFound'); - return $request; - - } - -} -``` - -##### Exception handler example - -```php -namespace Demo\Handlers; - -use Pecee\Handlers\IExceptionHandler; -use Pecee\Http\Request; -use Pecee\SimpleRouter\Exceptions\NotFoundHttpException; - -class CustomExceptionHandler implements IExceptionHandler -{ - public function handleError(Request $request, \Exception $error) - { - /* The router will throw the NotFoundHttpException on 404 */ - if($error instanceof NotFoundHttpException) { - - /* - * Render your own custom 404-view, rewrite the request to another route, - * or simply return the $request object to ignore the error and continue on rendering the route. - * - * The code below will make the router render our page.notfound route. - */ - - $request->setRewriteCallback('Demo\Controllers\DefaultController@notFound'); - return $request; - - } - - throw $error; - - } - -} -``` - -### Rewrite using url - -The example below will cause the router to reload the request and reinitialize all the routes. This method is slower, but will ensure that all middlewares and rules for the route is loaded. - -This method is useful if you want to redirect a url to another-url which is dependent on a middleware. You can also add a custom rule by calling `$request->setRewriteRoute($route)` if -you want to customize request-methods or use another route-type like `RouteController` etc. - -We are using the `url()` helper function to get the uri to another route added in the `routes.php` file. - -**NOTE: Use this method if you want to fully load another route using it's settings (request method, middlewares etc).** - -##### Middleware example - -The example below will redirect the request to the `home`-route. - -```php -namespace Demo\Middlewares; - -use Pecee\Http\Middleware\IMiddleware; -use Pecee\Http\Request; - -class CustomMiddleware implements IMiddleware { - - public function handle(Request $request) { - - $request->setRewriteUrl(url('home')); - return $request; - - } -} +request()->setRewriteUrl('/my-rewrite-url'); ``` ### Bootmanager: loading routes dynamically @@ -1282,7 +1175,6 @@ class CustomRouterRules implement IRouterBootManager { if($request->getUri()->getPath() === $url) { $request->setRewriteUrl($rule); - return $request; } } diff --git a/src/Pecee/Http/Request.php b/src/Pecee/Http/Request.php index 7f738fe..0abe1c6 100644 --- a/src/Pecee/Http/Request.php +++ b/src/Pecee/Http/Request.php @@ -16,6 +16,8 @@ class Request protected $method; protected $input; + protected $hasRewrite = false; + /** * @var ILoadableRoute|null */ @@ -231,6 +233,7 @@ class Request */ public function setRewriteRoute(ILoadableRoute $route) { + $this->hasRewrite = true; $this->rewriteRoute = SimpleRouter::addDefaultNamespace($route); return $this; @@ -264,7 +267,8 @@ class Request */ public function setRewriteUrl($rewriteUrl) { - $this->rewriteUrl = $rewriteUrl; + $this->hasRewrite = true; + $this->rewriteUrl = rtrim($rewriteUrl, '/') . '/'; return $this; } @@ -276,7 +280,9 @@ class Request */ public function setRewriteCallback($callback) { - return $this->setRewriteRoute(new RouteUrl($this->uri, $callback)); + $this->hasRewrite = true; + + return $this->setRewriteRoute(new RouteUrl($this->getUri()->getPath(), $callback)); } /** @@ -301,6 +307,23 @@ class Request return $this; } + public function hasRewrite() + { + return $this->hasRewrite; + } + + public function setHasRewrite($value) + { + $this->hasRewrite = $value; + + return $this; + } + + public function isRewrite($url) + { + return ($this->rewriteUrl === $url); + } + public function __isset($name) { return array_key_exists($name, $this->data); diff --git a/src/Pecee/SimpleRouter/Route/Route.php b/src/Pecee/SimpleRouter/Route/Route.php index ad54312..defd7ff 100644 --- a/src/Pecee/SimpleRouter/Route/Route.php +++ b/src/Pecee/SimpleRouter/Route/Route.php @@ -128,7 +128,7 @@ abstract class Route implements IRoute // Ensures that hostnames/domains will work with parameters $url = '/' . ltrim($url, '/'); - if (preg_match_all('/' . $regex . '/u', $route, $parameters) > 0) { + if (preg_match_all('/' . $regex . '/u', $route, $parameters) !== 0) { $urlParts = preg_split('/((\-?\/?)\{[^}]+\})/', $route); @@ -166,22 +166,21 @@ abstract class Route implements IRoute $urlRegex = preg_quote($route, '/'); } - if (preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) > 0) { - - $values = []; - - if (isset($parameters[1]) === true) { - - /* Only take matched parameters with name */ - foreach ((array)$parameters[1] as $name) { - $values[$name] = (isset($matches[$name]) && $matches[$name] !== '') ? $matches[$name] : null; - } - } - - return $values; + if (preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === 0) { + return null; } - return null; + $values = []; + + if (isset($parameters[1]) === true) { + + /* Only take matched parameters with name */ + foreach ((array)$parameters[1] as $name) { + $values[$name] = (isset($matches[$name]) && $matches[$name] !== '') ? $matches[$name] : null; + } + } + + return $values; } /** diff --git a/src/Pecee/SimpleRouter/Router.php b/src/Pecee/SimpleRouter/Router.php index d1a0f8b..3f1f891 100644 --- a/src/Pecee/SimpleRouter/Router.php +++ b/src/Pecee/SimpleRouter/Router.php @@ -224,6 +224,8 @@ class Router /* Verify csrf token for request */ $this->csrfVerifier->handle($this->request); } + } else { + $this->request->setHasRewrite(false); } $url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath(); @@ -231,7 +233,6 @@ class Router /* @var $route ILoadableRoute */ foreach ($this->processedRoutes as $key => $route) { - /* If the route matches */ if ($route->matchRoute($url, $this->request) === true) { @@ -243,29 +244,28 @@ class Router $route->loadMiddleware($this->request); - $rewriteRoute = $this->request->getRewriteRoute(); - - if ($rewriteRoute !== null) { - $rewriteRoute->loadMiddleware($this->request); - - return $rewriteRoute->renderRoute($this->request); - } - - /* If the request has changed */ - $rewriteUrl = $this->request->getRewriteUrl(); - - if ($rewriteUrl !== null && $rewriteUrl !== $url) { + if ($this->hasRewrite($url) === true) { unset($this->processedRoutes[$key]); - $this->processedRoutes = array_values($this->processedRoutes); return $this->routeRequest(true); } /* Render route */ $routeNotAllowed = false; + $this->request->setLoadedRoute($route); - return $route->renderRoute($this->request); + $output = $route->renderRoute($this->request); + + if ($output !== null) { + return $output; + } + + if ($this->hasRewrite($url) === true) { + unset($this->processedRoutes[$key]); + + return $this->routeRequest(true); + } } } @@ -294,6 +294,31 @@ class Router return null; } + protected function hasRewrite($url) + { + + /* If the request has changed */ + if ($this->request->hasRewrite() === true) { + + if ($this->request->getRewriteRoute() !== null) { + /* Render rewrite-route */ + $this->processedRoutes[] = $this->request->getRewriteRoute(); + + return true; + } + + if ($this->request->isRewrite($url) === false) { + + /* Render rewrite-url */ + $this->processedRoutes = array_values($this->processedRoutes); + + return true; + } + } + + return false; + } + /** * @param \Exception $e * @throws HttpException @@ -302,8 +327,6 @@ class Router */ protected function handleException(\Exception $e) { - $url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath(); - /* @var $handler IExceptionHandler */ foreach ($this->exceptionHandlers as $key => $handler) { @@ -317,25 +340,13 @@ class Router try { - if ($handler->handleError($this->request, $e) !== null) { + $handler->handleError($this->request, $e); - $rewriteRoute = $this->request->getRewriteRoute(); + if ($this->request->hasRewrite() === true) { + unset($this->exceptionHandlers[$key]); + $this->exceptionHandlers = array_values($this->exceptionHandlers); - if ($rewriteRoute !== null) { - $rewriteRoute->loadMiddleware($this->request); - - return $rewriteRoute->renderRoute($this->request); - } - - $rewriteUrl = $this->request->getRewriteUrl(); - - /* If the request has changed */ - if ($rewriteUrl !== null && $rewriteUrl !== $url) { - unset($this->exceptionHandlers[$key]); - $this->exceptionHandlers = array_values($this->exceptionHandlers); - - return $this->routeRequest(true); - } + return $this->routeRequest(true); } } catch (\Exception $e) { diff --git a/test/Dummy/Middlewares/RewriteMiddleware.php b/test/Dummy/Middlewares/RewriteMiddleware.php new file mode 100644 index 0000000..7bd1fef --- /dev/null +++ b/test/Dummy/Middlewares/RewriteMiddleware.php @@ -0,0 +1,16 @@ +setRewriteCallback(function() { + return 'ok'; + }); + + } + +} \ No newline at end of file diff --git a/test/RouterRewriteTest.php b/test/RouterRewriteTest.php index d55aaa8..a601bad 100644 --- a/test/RouterRewriteTest.php +++ b/test/RouterRewriteTest.php @@ -5,6 +5,7 @@ require_once 'Dummy/Handler/ExceptionHandlerFirst.php'; require_once 'Dummy/Handler/ExceptionHandlerSecond.php'; require_once 'Dummy/Handler/ExceptionHandlerThird.php'; require_once 'Helpers/TestRouter.php'; +require_once 'Dummy/Middlewares/RewriteMiddleware.php'; class RouteRewriteTest extends PHPUnit_Framework_TestCase { @@ -33,9 +34,9 @@ class RouteRewriteTest extends PHPUnit_Framework_TestCase global $stack; $stack = []; - TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () { + TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () use ($stack) { - TestRouter::group(['exceptionHandler' => ExceptionHandlerThird::class], function () { + TestRouter::group(['exceptionHandler' => ExceptionHandlerThird::class], function () use ($stack) { TestRouter::get('/my-path', 'DummyController@method1'); @@ -64,10 +65,8 @@ class RouteRewriteTest extends PHPUnit_Framework_TestCase TestRouter::error(function (\Pecee\Http\Request $request, \Exception $error) { - if (strtolower($request->getUri()->getPath()) == '/my/test') { + if (strtolower($request->getUri()->getPath()) === '/my/test/') { $request->setRewriteUrl('/another-non-existing'); - - return $request; } }); @@ -75,4 +74,104 @@ class RouteRewriteTest extends PHPUnit_Framework_TestCase TestRouter::debug('/my/test', 'get'); } + public function testRewriteUrlFromRoute() + { + + TestRouter::get('/old', function () { + TestRouter::request()->setRewriteUrl('/new'); + }); + + TestRouter::get('/new', function () { + echo 'ok'; + }); + + TestRouter::get('/new1', function () { + echo 'ok'; + }); + + TestRouter::get('/new2', function () { + echo 'ok'; + }); + + $output = TestRouter::debugOutput('/old'); + + $this->assertEquals('ok', $output); + + } + + public function testRewriteCallbackFromRoute() + { + + TestRouter::get('/old', function () { + TestRouter::request()->setRewriteUrl('/new'); + }); + + TestRouter::get('/new', function () { + return 'ok'; + }); + + TestRouter::get('/new1', function () { + return 'fail'; + }); + + TestRouter::get('/new/2', function () { + return 'fail'; + }); + + $output = TestRouter::debugOutput('/old'); + + TestRouter::router()->reset(); + + $this->assertEquals('ok', $output); + + } + + public function testRewriteRouteFromRoute() + { + + TestRouter::get('/match', function () { + TestRouter::request()->setRewriteRoute(new \Pecee\SimpleRouter\Route\RouteUrl('/match', function () { + return 'ok'; + })); + }); + + TestRouter::get('/old1', function () { + return 'fail'; + }); + + TestRouter::get('/old/2', function () { + return 'fail'; + }); + + TestRouter::get('/new2', function () { + return 'fail'; + }); + + $output = TestRouter::debugOutput('/match'); + + TestRouter::router()->reset(); + + $this->assertEquals('ok', $output); + + } + + public function testMiddlewareRewrite() + { + + TestRouter::group(['middleware' => 'RewriteMiddleware'], function () { + TestRouter::get('/', function () { + return 'fail'; + }); + + TestRouter::get('no/match', function () { + return 'fail'; + }); + }); + + $output = TestRouter::debugOutput('/'); + + $this->assertEquals('ok', $output); + + } + } \ No newline at end of file