diff --git a/README.md b/README.md index ed14bf2..7bfc8aa 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,9 @@ If you want a great new feature or experience any issues what-so-ever, please fe - [Middlewares](#middlewares) - [Example](#example) -- [ExceptionHandler](#exceptionhandler) - - [Example](#example-1) +- [ExceptionHandlers](#exceptionhandlers) + - [Handling 404, 403 and other errors](#handling-404-403-and-other-errors) + - [Using custom exception handlers](#using-custom-exception-handlers) - [Urls](#urls) - [Get by name (single route)](#get-by-name-single-route) @@ -626,13 +627,30 @@ class CustomMiddleware implements Middleware { --- -# ExceptionHandler +# ExceptionHandlers ExceptionHandler are classes that handles all exceptions. ExceptionsHandlers must implement the `IExceptionHandler` interface. -## Example +## Handling 404, 403 and other errors -Resource controllers can implement the `IRestController` interface, but is not required. +If you simply want to catch a 404 (page not found) etc. you can use the `Router::error($callback)` static helper method. + +This will add a callback method which is fired whenever an error occurs on all routes. + +The basic example below simply redirect the page to `/not-found` if an `NotFoundHttpException` (404) occurred. +The code should be placed in the file that contains your routes. + +```php +Router::get('/not-found', 'PageController@notFound'); + +Router::error(function(Request $request, \Exception $exception) { + if($exception instanceof NotFoundHttpException && $exception->getCode == 404) { + response()->redirect('/not-found'); + } +}); +``` + +## Using custom exception handlers This is a basic example of an ExceptionHandler implementation (please see "[Easily overwrite route about to be loaded](#easily-overwrite-route-about-to-be-loaded)" for examples on how to change callback). diff --git a/src/Pecee/Handlers/CallbackExceptionHandler.php b/src/Pecee/Handlers/CallbackExceptionHandler.php new file mode 100644 index 0000000..d22753b --- /dev/null +++ b/src/Pecee/Handlers/CallbackExceptionHandler.php @@ -0,0 +1,38 @@ +callback = $callback; + } + + /** + * @param Request $request + * @param \Exception $error + * @return Request|null + */ + public function handleError(Request $request, \Exception $error) + { + /* Fire exceptions */ + return call_user_func($this->callback, + $request, + $error + ); + } +} \ No newline at end of file diff --git a/src/Pecee/Http/Request.php b/src/Pecee/Http/Request.php index 371e0af..0d69a71 100644 --- a/src/Pecee/Http/Request.php +++ b/src/Pecee/Http/Request.php @@ -8,7 +8,7 @@ use Pecee\SimpleRouter\SimpleRouter; class Request { - protected $data = []; + private $data = []; protected $headers; protected $host; protected $uri; diff --git a/src/Pecee/SimpleRouter/Exceptions/RouterException.php b/src/Pecee/SimpleRouter/Exceptions/RouterException.php new file mode 100644 index 0000000..974ef6b --- /dev/null +++ b/src/Pecee/SimpleRouter/Exceptions/RouterException.php @@ -0,0 +1,7 @@ +getUrl(); - if ($this->getGroup() !== null && count($this->getGroup()->getDomains()) > 0) { - $url = '//' . $this->getGroup()->getDomains()[0] . $url; + $group = $this->getGroup(); + + if ($group !== null && count($group->getDomains()) > 0) { + $url = '//' . $group->getDomains()[0] . $url; } /* Contains parameters that aren't recognized and will be appended at the end of the url */ diff --git a/src/Pecee/SimpleRouter/Route/Route.php b/src/Pecee/SimpleRouter/Route/Route.php index e212f55..9ef2f89 100644 --- a/src/Pecee/SimpleRouter/Route/Route.php +++ b/src/Pecee/SimpleRouter/Route/Route.php @@ -168,7 +168,7 @@ abstract class Route implements IRoute */ public function getIdentifier() { - if (strpos($this->callback, '@') !== false) { + if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) { return $this->callback; } @@ -265,7 +265,7 @@ abstract class Route implements IRoute public function getMethod() { - if (strpos($this->callback, '@') !== false) { + if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) { $tmp = explode('@', $this->callback); return $tmp[1]; @@ -276,7 +276,7 @@ abstract class Route implements IRoute public function getClass() { - if (strpos($this->callback, '@') !== false) { + if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) { $tmp = explode('@', $this->callback); return $tmp[0]; diff --git a/src/Pecee/SimpleRouter/Route/RouteController.php b/src/Pecee/SimpleRouter/Route/RouteController.php index 6ef22e8..e01854a 100644 --- a/src/Pecee/SimpleRouter/Route/RouteController.php +++ b/src/Pecee/SimpleRouter/Route/RouteController.php @@ -74,8 +74,10 @@ class RouteController extends LoadableRoute implements IControllerRoute $method .= '/'; } - if ($this->getGroup() !== null && count($this->getGroup()->getDomains()) > 0) { - $url .= '//' . $this->getGroup()->getDomains()[0]; + $group = $this->getGroup(); + + if ($group !== null && count($group->getDomains()) > 0) { + $url .= '//' . $group->getDomains()[0]; } $url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . join('/', $parameters); diff --git a/src/Pecee/SimpleRouter/Route/RouteGroup.php b/src/Pecee/SimpleRouter/Route/RouteGroup.php index d7a4379..bd0771e 100644 --- a/src/Pecee/SimpleRouter/Route/RouteGroup.php +++ b/src/Pecee/SimpleRouter/Route/RouteGroup.php @@ -1,6 +1,8 @@ matchDomain($request); } + /** + * Add exception handler + * + * @param IExceptionHandler $handler + * @return static $this + */ + public function addExceptionHandler(IExceptionHandler $handler) + { + $this->exceptionHandlers[] = $handler; + + return $this; + } + /** * Set exception-handlers for group * diff --git a/src/Pecee/SimpleRouter/Router.php b/src/Pecee/SimpleRouter/Router.php index 808e376..2a06f50 100644 --- a/src/Pecee/SimpleRouter/Router.php +++ b/src/Pecee/SimpleRouter/Router.php @@ -1,4 +1,5 @@ getExceptionHandlers()) > 0) { + /** @noinspection AdditionOperationOnArraysInspection */ $exceptionHandlers += $route->getExceptionHandlers(); } @@ -297,7 +299,10 @@ class Router for ($i = 0; $i < $max; $i++) { $handler = $this->exceptionHandlers[$i]; - $handler = new $handler(); + + if (is_object($handler) === false) { + $handler = new $handler(); + } if (($handler instanceof IExceptionHandler) === false) { throw new HttpException('Exception handler must implement the IExceptionHandler interface.', 500); @@ -372,7 +377,7 @@ class Router } /* Using @ is most definitely a controller@method or alias@method */ - if (strpos($name, '@') !== false) { + if (is_string($name) === true && strpos($name, '@') !== false) { list($controller, $method) = array_map('strtolower', explode('@', $name)); if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) { @@ -381,7 +386,7 @@ class Router } /* Check if callback matches (if it's not a function) */ - if (strpos($name, '@') !== false && strpos($route->getCallback(), '@') !== false && !is_callable($route->getCallback())) { + if (is_string($name) === true && is_string($route->getCallback()) && strpos($name, '@') !== false && strpos($route->getCallback(), '@') !== false && is_callable($route->getCallback()) === false) { /* Check if the entire callback is matching */ if (strpos($route->getCallback(), $name) === 0 || strtolower($route->getCallback()) === strtolower($name)) { @@ -451,7 +456,7 @@ class Router } /* Using @ is most definitely a controller@method or alias@method */ - if (strpos($name, '@') !== false) { + if (is_string($name) === true && strpos($name, '@') !== false) { list($controller, $method) = explode('@', $name); /* Loop through all the routes to see if we can find a match */ @@ -517,6 +522,19 @@ class Router return $this->routes; } + /** + * Set routes + * + * @param array $routes + * @return static $this + */ + public function setRoutes(array $routes) + { + $this->routes = $routes; + + return $this; + } + /** * Get current request * diff --git a/src/Pecee/SimpleRouter/SimpleRouter.php b/src/Pecee/SimpleRouter/SimpleRouter.php index c085032..f347743 100644 --- a/src/Pecee/SimpleRouter/SimpleRouter.php +++ b/src/Pecee/SimpleRouter/SimpleRouter.php @@ -7,8 +7,10 @@ * This class is added so calls can be made statically like Router::get() making the code look pretty. * It also adds some extra functionality like default-namespace. */ + namespace Pecee\SimpleRouter; +use Pecee\Handlers\CallbackExceptionHandler; use Pecee\Http\Middleware\BaseCsrfVerifier; use Pecee\Http\Response; use Pecee\SimpleRouter\Exceptions\HttpException; @@ -301,6 +303,31 @@ class SimpleRouter return $route; } + /** + * Add exception callback handler. + * + * @param \Closure $callback + * @return CallbackExceptionHandler $callbackHandler + */ + public static function error($callback) + { + $routes = static::router()->getRoutes(); + + $callbackHandler = new CallbackExceptionHandler($callback); + + $group = new RouteGroup(); + $group->setCallback(function () { + + }); + $group->addExceptionHandler($callbackHandler); + + array_unshift($routes, $group); + + static::router()->setRoutes($routes); + + return $callbackHandler; + } + /** * Get url for a route by using either name/alias, class or method name. * @@ -355,7 +382,7 @@ class SimpleRouter */ public static function router() { - if(static::$router === null) { + if (static::$router === null) { static::$router = new Router(); } diff --git a/test/RouterCallbackExceptionHandlerTest.php b/test/RouterCallbackExceptionHandlerTest.php new file mode 100644 index 0000000..de21d2a --- /dev/null +++ b/test/RouterCallbackExceptionHandlerTest.php @@ -0,0 +1,27 @@ +setExpectedException(ExceptionHandlerException::class); + + // Match normal route on alias + TestRouter::get('/my-new-url', 'DummyController@method2'); + TestRouter::get('/my-url', 'DummyController@method1'); + + TestRouter::error(function (\Pecee\Http\Request $request, \Exception $exception) { + throw new ExceptionHandlerException(); + }); + + TestRouter::debugNoReset('/404-url', 'get'); + TestRouter::router()->reset(); + } + +} \ No newline at end of file