diff --git a/README.md b/README.md index 44dd782..70dba98 100644 --- a/README.md +++ b/README.md @@ -354,15 +354,16 @@ By doing this the route will now load the url ```/article/view/1``` instead of ` The last thing we need to do, is to add our custom boot-manager to the ```routes.php``` file. You can create as many bootmanagers as you like and easily add them in your ```routes.php``` file. ## Easily overwrite route about to be loaded -Sometimes it can be useful to manipulate the route that's about to be loaded, for instance if a user is not authenticated or if an error occurred within your Middleware that requires -some other route to be initialised. Simple PHP Router allows you to easily change the route about to be executed. All information about the current route is stored in -the ```\Pecee\SimpleRouter\Http\Request``` object. All information about the current route is as a ```\Pecee\SimpleRouter\Http\Request``` object which can always be obtained on -the `RouterBase` instance. For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::request()`. +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. +All information about the current route is stored in the ```\Pecee\SimpleRouter\RouterBase``` instance. + +For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`. -**Note:** Please note that it's only possible to change the route BEFORE any route has initially been loaded, so doing this in your custom ExceptionHandler or Middleware is highly recommended. ```php -$route = request()->getLoadedRoute(); +use Pecee\SimpleRouter; +$route = SimpleRouter::router()->getLoadedRoute(); $route->setCallback('Example\MyCustomClass@hello'); @@ -372,6 +373,54 @@ $route->setClass('Example\MyCustomClass'); $route->setMethod('hello'); ``` + +### Examples + +#### Faking new route +It's only possible to change the route BEFORE the route has initially been loaded. If you want to redirect to another route, we highly recommend that you +modify the `RouterRoute` object from a `Middleware` or `ExceptionHandler`, for like the examples below. + +The example below will cause the router to re-route the request with the "fake" uri. This does require the `$request` object to be returned, +otherwise the `request` object will be ignored by the router. + +```php +namespace demo\Middlewares; + +use Pecee\Http\Middleware\IMiddleware; +use Pecee\Http\Request; +use Pecee\SimpleRouter\RouterEntry; + +class CustomMiddleware implements Middleware { + + public function handle(Request $request, RouterEntry &$route = null) { + return $request->setUri('/home'); + } +} + +``` + +#### Changing callback +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. + +```php +namespace demo\Middlewares; + +use Pecee\Http\Middleware\IMiddleware; +use Pecee\Http\Request; +use Pecee\SimpleRouter\RouterEntry; + +class CustomMiddleware implements Middleware { + + public function handle(Request $request, RouterEntry &$route = null) { + $route->callback('DefaultController@home'); + } +} +``` + ## Using the Input class to manage parameters We've added the `Input` class to easy access parameters from your Controller-classes. @@ -425,7 +474,7 @@ Below example requires you to have the helper functions added. Please refer to t ```php // Get parameter site_id or default-value 2 -$value = input()->get('site_id', '2'); +$siteId = input()->get('site_id', 2); ``` ## Sites diff --git a/demo-project/app/Middlewares/ApiVerification.php b/demo-project/app/Middlewares/ApiVerification.php index c437a22..2f71561 100644 --- a/demo-project/app/Middlewares/ApiVerification.php +++ b/demo-project/app/Middlewares/ApiVerification.php @@ -6,7 +6,7 @@ use Pecee\Http\Request; class ApiVerification implements IMiddleware { - public function handle(Request $request) { + public function handle(Request &$request) { // Do authentication $request->authenticated = true; diff --git a/src/Pecee/Handler/IExceptionHandler.php b/src/Pecee/Handler/IExceptionHandler.php index 045cab0..7c5cb9d 100644 --- a/src/Pecee/Handler/IExceptionHandler.php +++ b/src/Pecee/Handler/IExceptionHandler.php @@ -6,6 +6,12 @@ use Pecee\SimpleRouter\RouterEntry; interface IExceptionHandler { - public function handleError(Request $request, RouterEntry $router = null, \Exception $error); + /** + * @param Request $request + * @param RouterEntry|null $route + * @param \Exception $error + * @return Request|null + */ + public function handleError(Request $request, RouterEntry &$route = null, \Exception $error); } \ No newline at end of file diff --git a/src/Pecee/Http/Middleware/BaseCsrfVerifier.php b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php index 682a9dc..7b4f082 100644 --- a/src/Pecee/Http/Middleware/BaseCsrfVerifier.php +++ b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php @@ -4,6 +4,7 @@ namespace Pecee\Http\Middleware; use Pecee\CsrfToken; use Pecee\Exception\TokenMismatchException; use Pecee\Http\Request; +use Pecee\SimpleRouter\RouterEntry; class BaseCsrfVerifier implements IMiddleware { @@ -49,11 +50,11 @@ class BaseCsrfVerifier implements IMiddleware { return false; } - public function handle(Request $request) { + public function handle(Request $request, RouterEntry &$route = null) { - if($request->getMethod() != 'get' && !$this->skip($request)) { + if($request->getMethod() !== 'get' && !$this->skip($request)) { - $token = (isset($_POST[static::POST_KEY])) ? $_POST[static::POST_KEY] : null; + $token = $request->getInput()->post->findFirst(static::POST_KEY); // If the token is not posted, check headers for valid x-csrf-token if($token === null) { diff --git a/src/Pecee/Http/Middleware/IMiddleware.php b/src/Pecee/Http/Middleware/IMiddleware.php index ba55a8d..1f2fc2c 100644 --- a/src/Pecee/Http/Middleware/IMiddleware.php +++ b/src/Pecee/Http/Middleware/IMiddleware.php @@ -2,7 +2,15 @@ namespace Pecee\Http\Middleware; use Pecee\Http\Request; +use Pecee\SimpleRouter\RouterEntry; interface IMiddleware { - public function handle(Request $request); + + /** + * @param Request $request + * @param RouterEntry|null $route + * @return Request|null + */ + public function handle(Request $request, RouterEntry &$route = null); + } \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/RouterBase.php b/src/Pecee/SimpleRouter/RouterBase.php index 72dc7de..f7395bf 100644 --- a/src/Pecee/SimpleRouter/RouterBase.php +++ b/src/Pecee/SimpleRouter/RouterBase.php @@ -71,6 +71,14 @@ class RouterBase { */ protected $exceptionHandlers; + /** + * The current loaded route + * @var RouterRoute|null + */ + protected $loadedRoute; + + protected $routeChanges; + public function __construct() { $this->reset(); } @@ -83,6 +91,7 @@ class RouterBase { $this->controllerUrlMap = array(); $this->bootManagers = array(); $this->exceptionHandlers = array(); + $this->routeChanges = array(); } /** @@ -173,18 +182,21 @@ class RouterBase { } } - public function routeRequest($original = true) { + public function routeRequest(Request $newRequest = null) { - $originalUri = $this->request->getUri(); + $this->loadedRoute = null; $routeNotAllowed = false; + // Create a fictive request - so it can be changed in the middleware or exceptionhandler later on... + $request = clone $this->request; + try { // Initialize boot-managers if(count($this->bootManagers)) { /* @var $manager RouterBootManager */ foreach($this->bootManagers as $manager) { - $this->request = $manager->boot($this->request); + $request = $manager->boot($request); if(!($this->request instanceof Request)) { throw new RouterException('Custom router bootmanager "'. get_class($manager) .'" must return instance of Request.'); @@ -192,35 +204,42 @@ class RouterBase { } } - // Loop through each route-request - $this->processRoutes($this->routes); + if($newRequest === null && $this->csrfVerifier !== null) { + + // Loop through each route-request + $this->processRoutes($this->routes); - if($original === true && $this->csrfVerifier !== null) { // Verify csrf token for request $this->csrfVerifier->handle($this->request); } + $request = ($newRequest !== null) ? $newRequest : $request; + /* @var $route RouterEntry */ for ($i = 0; $i < count($this->controllerUrlMap); $i++) { $route = $this->controllerUrlMap[$i]; - if ($route->matchRoute($this->request)) { + if ($route->matchRoute($request)) { - if (count($route->getRequestMethods()) && !in_array($this->request->getMethod(), $route->getRequestMethods())) { + if (count($route->getRequestMethods()) && !in_array($request->getMethod(), $route->getRequestMethods())) { $routeNotAllowed = true; continue; } $routeNotAllowed = false; - $this->request->rewrite_uri = $this->request->getUri(); - $this->request->setUri($originalUri); + $this->loadedRoute = $route; + $request = $this->loadedRoute->loadMiddleware($request, $this->loadedRoute); + $request = ($request === null) ? $this->request : $request; - $this->request->loadedRoute = $route; - $this->request->loadedRoute->loadMiddleware($this->request); + if($request !== null && $request->getUri() !== $this->request->getUri() && !in_array($request->getUri(), $this->routeChanges)) { + $this->routeChanges[] = $request->getUri(); + $this->routeRequest($request); + return; + } - $this->request->loadedRoute->renderRoute($this->request); + $this->loadedRoute->renderRoute($request); break; } @@ -234,18 +253,17 @@ class RouterBase { $this->handleException(new RouterException('Route or method not allowed', 403)); } - if(!$this->request->loadedRoute) { - $this->handleException(new RouterException(sprintf('Route not found: %s', $this->request->getUri()), 404)); + if($this->loadedRoute === null) { + $this->handleException(new RouterException(sprintf('Route not found: %s', $request->getUri()), 404)); } } protected function handleException(\Exception $e) { - $request = null; + $request = clone $this->request; /* @var $route RouterGroup */ foreach ($this->exceptionHandlers as $route) { - $route->loadMiddleware($this->request); $handler = $route->getExceptionHandler(); $handler = new $handler(); @@ -253,13 +271,20 @@ class RouterBase { throw new RouterException('Exception handler must implement the IExceptionHandler interface.'); } - $request = $handler->handleError($this->request, $this->request->loadedRoute, $e); - } + $request = $handler->handleError($request, $this->loadedRoute, $e); + $request = ($request === null) ? $this->request : $request; + + if(!in_array($request->getUri(), $this->routeChanges)) { + $this->routeChanges[] = $request->getUri(); + if($request->getUri() !== $this->request->getUri()) { + $this->routeRequest($request); + } else { + $this->routeChanges[] = $request->getUri(); + $this->loadedRoute->renderRoute($request); + } + return; + } - if($request !== null) { - $this->request = $request; - $this->routeRequest(false); - return; } throw $e; @@ -307,14 +332,6 @@ class RouterBase { $this->bootManagers[] = $bootManager; } - /** - * Get the loaded route - * @return RouterEntry - */ - public function getLoadedRoute() { - return $this->request->loadedRoute; - } - /** * @return array */ @@ -451,8 +468,8 @@ class RouterBase { return $url; } - if($controller === null && $this->request->loadedRoute !== null) { - return $this->processUrl($this->request->loadedRoute, $this->request->loadedRoute->getMethod(), $parameters, $getParams); + if($controller === null && $this->loadedRoute !== null) { + return $this->processUrl($this->loadedRoute, $this->loadedRoute->getMethod(), $parameters, $getParams); } $c = ''; diff --git a/src/Pecee/SimpleRouter/RouterEntry.php b/src/Pecee/SimpleRouter/RouterEntry.php index a8a1466..3ed29b2 100644 --- a/src/Pecee/SimpleRouter/RouterEntry.php +++ b/src/Pecee/SimpleRouter/RouterEntry.php @@ -291,7 +291,7 @@ abstract class RouterEntry { return null; } - public function loadMiddleware(Request $request) { + public function loadMiddleware(Request $request, RouterRoute &$route) { if(count($this->getMiddleware())) { foreach($this->getMiddleware() as $middleware) { $middleware = $this->loadClass($middleware); @@ -300,7 +300,7 @@ abstract class RouterEntry { } /* @var $class IMiddleware */ - $middleware->handle($request); + $middleware->handle($request, $route); } } } diff --git a/src/Pecee/SimpleRouter/RouterGroup.php b/src/Pecee/SimpleRouter/RouterGroup.php index 16344ac..0c21f1a 100644 --- a/src/Pecee/SimpleRouter/RouterGroup.php +++ b/src/Pecee/SimpleRouter/RouterGroup.php @@ -113,8 +113,8 @@ class RouterGroup extends RouterEntry { // Push middleware if multiple if ($this->getMiddleware() !== null && isset($settings['middleware'])) { - if (!is_array($this->getMiddleware())) { - $settings['middleware'] = array($this->getMiddleware(), $settings['middleware']); + if (!is_array($settings['middleware'])) { + $settings['middleware'] = array_merge($this->getMiddleware(), array($settings['middleware'])); } else { $settings['middleware'][] = $this->getMiddleware(); }