mirror of
https://github.com/skipperbent/simple-php-router.git
synced 2026-06-18 01:07:51 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e34fe47a04 | |||
| f0a4b6e46f | |||
| e38a406957 | |||
| b82e29c864 | |||
| 9c79901316 | |||
| fbc87cc9bd | |||
| 301c2cfe4a | |||
| 01bad94af0 | |||
| a1d5f38af7 | |||
| b5e42dbdfb | |||
| 06a63eb0e7 | |||
| 5268a998ff | |||
| 9fa7ad3e91 | |||
| 0097725ef2 | |||
| 749f252ffb | |||
| 032a2ae7e0 | |||
| c2e2d3bb5d | |||
| 5dd0690009 | |||
| dbcf8f19a3 | |||
| ee61eda1e8 | |||
| 471bbe137f | |||
| b08dea9da5 | |||
| b17ba06a8c | |||
| 69494265a5 | |||
| 0d8915b206 | |||
| b54a25804a | |||
| e3145cc1ec | |||
| 75ea58dd9c | |||
| 470000ad05 | |||
| 3ffe9c8c07 | |||
| 44c2b99513 | |||
| 8b9e43c99e | |||
| c4a9918048 | |||
| 63a9ec65cf | |||
| 14d3577a6a | |||
| 869c65f347 | |||
| 4fc48b4420 | |||
| bef3207fcd | |||
| d7bdee1092 | |||
| 8c5ed8410a |
@@ -62,6 +62,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
|
||||
- [ExceptionHandlers](#exceptionhandlers)
|
||||
- [Handling 404, 403 and other errors](#handling-404-403-and-other-errors)
|
||||
- [Using custom exception handlers](#using-custom-exception-handlers)
|
||||
- [Prevent merge of parent exception-handlers](#prevent-merge-of-parent-exception-handlers)
|
||||
- [Urls](#urls)
|
||||
- [Get the current url](#get-the-current-url)
|
||||
- [Get by name (single route)](#get-by-name-single-route)
|
||||
@@ -971,6 +972,12 @@ If you do not want a redirect, but want the error-page rendered on the current-u
|
||||
$request->setRewriteCallback('ErrorController@notFound');
|
||||
```
|
||||
|
||||
If you will set the correct status for the browser error use:
|
||||
|
||||
```php
|
||||
SimpleRouter::response()->httpCode(404);
|
||||
```
|
||||
|
||||
## 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).
|
||||
@@ -1014,6 +1021,41 @@ class CustomExceptionHandler implements IExceptionHandler
|
||||
}
|
||||
```
|
||||
|
||||
You can add your custom exception-handler class to your group by using the `exceptionHandler` settings-attribute.
|
||||
`exceptionHandler` can be either class-name or array of class-names.
|
||||
|
||||
```php
|
||||
SimpleRouter::group(['exceptionHandler' => \Demo\Handlers\CustomExceptionHandler::class], function() {
|
||||
|
||||
// Your routes here
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
### Prevent merge of parent exception-handlers
|
||||
|
||||
By default the router will merge exception-handlers to any handlers provided by parent groups, and will be executed in the order of newest to oldest.
|
||||
|
||||
If you want your groups exception handler to be executed independently, you can add the `mergeExceptionHandlers` attribute and set it to `false`.
|
||||
|
||||
```php
|
||||
SimpleRouter::group(['prefix' => '/', 'exceptionHandler' => \Demo\Handlers\FirstExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {
|
||||
|
||||
SimpleRouter::group(['prefix' => '/admin', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class], function() {
|
||||
|
||||
// Both SecondExceptionHandler and FirstExceptionHandler will trigger (in that order).
|
||||
|
||||
});
|
||||
|
||||
SimpleRouter::group(['prefix' => '/user', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {
|
||||
|
||||
// Only SecondExceptionHandler will trigger.
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Urls
|
||||
@@ -2037,4 +2079,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"php": ">=7.4",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
|
||||
@@ -81,7 +81,7 @@ class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
|
||||
return isset($this->value[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
public function offsetGet($offset): ?self
|
||||
{
|
||||
if ($this->offsetExists($offset) === true) {
|
||||
return $this->value[$offset];
|
||||
|
||||
@@ -129,7 +129,12 @@ class Request
|
||||
$this->setHost($this->getHeader('http-host'));
|
||||
|
||||
// Check if special IIS header exist, otherwise use default.
|
||||
$this->setUrl(new Url($this->getFirstHeader(['unencoded-url', 'request-uri'])));
|
||||
$url = $this->getHeader('unencoded-url');
|
||||
if($url !== null){
|
||||
$this->setUrl(new Url($url));
|
||||
}else{
|
||||
$this->setUrl(new Url(urldecode($this->getHeader('request-uri'))));
|
||||
}
|
||||
$this->setContentType((string)$this->getHeader('content-type'));
|
||||
$this->setMethod((string)($_POST[static::FORCE_METHOD_KEY] ?? $this->getHeader('request-method')));
|
||||
$this->inputHandler = new InputHandler($this);
|
||||
|
||||
@@ -31,6 +31,21 @@ interface IGroupRoute extends IRoute
|
||||
*/
|
||||
public function setExceptionHandlers(array $handlers): self;
|
||||
|
||||
/**
|
||||
* Returns true if group should overwrite existing exception-handlers.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getMergeExceptionHandlers(): bool;
|
||||
|
||||
/**
|
||||
* When enabled group will overwrite any existing exception-handlers.
|
||||
*
|
||||
* @param bool $merge
|
||||
* @return static
|
||||
*/
|
||||
public function setMergeExceptionHandlers(bool $merge): self;
|
||||
|
||||
/**
|
||||
* Get exception-handlers for group
|
||||
*
|
||||
|
||||
@@ -123,7 +123,8 @@ abstract class Route implements IRoute
|
||||
);
|
||||
|
||||
// Ensures that host names/domains will work with parameters
|
||||
$url = '/' . ltrim($url, '/');
|
||||
|
||||
if($route[0] == '{') $url = '/' . ltrim($url, '/');
|
||||
$urlRegex = '';
|
||||
$parameters = [];
|
||||
|
||||
@@ -131,7 +132,7 @@ abstract class Route implements IRoute
|
||||
$urlRegex = preg_quote($route, '/');
|
||||
} else {
|
||||
|
||||
foreach (preg_split('/((-?\/?){[^}]+})/', $route) as $key => $t) {
|
||||
foreach (preg_split('/((\.?-?\/?){[^}]+})/', $route) as $key => $t) {
|
||||
|
||||
$regex = '';
|
||||
|
||||
@@ -146,7 +147,7 @@ abstract class Route implements IRoute
|
||||
$regex = $parameterRegex ?? $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_REGEX;
|
||||
}
|
||||
|
||||
$regex = sprintf('((\/|-)(?P<%2$s>%3$s))%1$s', $parameters[2][$key], $name, $regex);
|
||||
$regex = sprintf('((\/|-|\.)(?P<%2$s>%3$s))%1$s', $parameters[2][$key], $name, $regex);
|
||||
}
|
||||
|
||||
$urlRegex .= preg_quote($t, '/') . $regex;
|
||||
|
||||
@@ -12,6 +12,7 @@ class RouteGroup extends Route implements IGroupRoute
|
||||
protected $name;
|
||||
protected $domains = [];
|
||||
protected $exceptionHandlers = [];
|
||||
protected $mergeExceptionHandlers = true;
|
||||
|
||||
/**
|
||||
* Method called to check if a domain matches
|
||||
@@ -36,6 +37,7 @@ class RouteGroup extends Route implements IGroupRoute
|
||||
|
||||
if ($parameters !== null && count($parameters) !== 0) {
|
||||
$this->parameters = $parameters;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +78,7 @@ class RouteGroup extends Route implements IGroupRoute
|
||||
}
|
||||
|
||||
/* Skip if prefix doesn't match */
|
||||
if ($this->prefix !== null && stripos($url, $parsedPrefix) === false) {
|
||||
if ($this->prefix !== null && stripos($url, rtrim($parsedPrefix, '/') . '/') === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -174,6 +176,29 @@ class RouteGroup extends Route implements IGroupRoute
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* When enabled group will overwrite any existing exception-handlers.
|
||||
*
|
||||
* @param bool $merge
|
||||
* @return static
|
||||
*/
|
||||
public function setMergeExceptionHandlers(bool $merge): IGroupRoute
|
||||
{
|
||||
$this->mergeExceptionHandlers = $merge;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if group should overwrite existing exception-handlers.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getMergeExceptionHandlers(): bool
|
||||
{
|
||||
return $this->mergeExceptionHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge with information from another route.
|
||||
*
|
||||
@@ -187,6 +212,10 @@ class RouteGroup extends Route implements IGroupRoute
|
||||
$this->setPrefix($settings['prefix'] . $this->prefix);
|
||||
}
|
||||
|
||||
if (isset($settings['mergeExceptionHandlers']) === true) {
|
||||
$this->setMergeExceptionHandlers($settings['mergeExceptionHandlers']);
|
||||
}
|
||||
|
||||
if ($merge === false && isset($settings['exceptionHandler']) === true) {
|
||||
$this->setExceptionHandlers((array)$settings['exceptionHandler']);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ class Router
|
||||
* @var bool
|
||||
*/
|
||||
protected $isProcessingRoute;
|
||||
|
||||
/**
|
||||
* Defines all data from current processing route.
|
||||
* @var ILoadableRoute
|
||||
*/
|
||||
protected $currentProcessingRoute;
|
||||
|
||||
/**
|
||||
* All added routes
|
||||
@@ -160,7 +166,7 @@ class Router
|
||||
public function addRoute(IRoute $route): IRoute
|
||||
{
|
||||
$this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [
|
||||
'route' => $route,
|
||||
'route' => $route,
|
||||
'isSubRoute' => $this->isProcessingRoute,
|
||||
]);
|
||||
|
||||
@@ -203,7 +209,7 @@ class Router
|
||||
/**
|
||||
* Process added routes.
|
||||
*
|
||||
* @param array $routes
|
||||
* @param array|IRoute[] $routes
|
||||
* @param IGroupRoute|null $group
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
@@ -211,9 +217,6 @@ class Router
|
||||
{
|
||||
$this->debug('Processing routes');
|
||||
|
||||
// Loop through each route-request
|
||||
$exceptionHandlers = [];
|
||||
|
||||
// Stop processing routes if no valid route is found.
|
||||
if ($this->request->getRewriteRoute() === null && $this->request->getUrl()->getOriginalUrl() === '') {
|
||||
$this->debug('Halted route-processing as no valid route was found');
|
||||
@@ -223,7 +226,7 @@ class Router
|
||||
|
||||
$url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath();
|
||||
|
||||
/* @var $route IRoute */
|
||||
// Loop through each route-request
|
||||
foreach ($routes as $route) {
|
||||
|
||||
$this->debug('Processing route "%s"', get_class($route));
|
||||
@@ -240,13 +243,22 @@ class Router
|
||||
|
||||
/* Add exception handlers */
|
||||
if (count($route->getExceptionHandlers()) !== 0) {
|
||||
/** @noinspection AdditionOperationOnArraysInspection */
|
||||
$exceptionHandlers += $route->getExceptionHandlers();
|
||||
|
||||
if ($route->getMergeExceptionHandlers() === true) {
|
||||
|
||||
foreach ($route->getExceptionHandlers() as $handler) {
|
||||
$this->exceptionHandlers[] = $handler;
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->exceptionHandlers = $route->getExceptionHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
/* Only render partial group if it matches */
|
||||
if ($route instanceof IPartialGroupRoute === true) {
|
||||
$this->renderAndProcess($route);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -264,8 +276,6 @@ class Router
|
||||
$this->processedRoutes[] = $route;
|
||||
}
|
||||
}
|
||||
|
||||
$this->exceptionHandlers = array_merge($exceptionHandlers, $this->exceptionHandlers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,6 +377,9 @@ class Router
|
||||
foreach ($this->processedRoutes as $key => $route) {
|
||||
|
||||
$this->debug('Matching route "%s"', get_class($route));
|
||||
|
||||
/* Add current processing route to constants */
|
||||
$this->currentProcessingRoute = $route;
|
||||
|
||||
/* If the route matches */
|
||||
if ($route->matchRoute($url, $this->request) === true) {
|
||||
@@ -679,7 +692,7 @@ class Router
|
||||
->setParams($getParams);
|
||||
}
|
||||
|
||||
if($name !== null) {
|
||||
if ($name !== null) {
|
||||
/* We try to find a match on the given name */
|
||||
$route = $this->findRoute($name);
|
||||
|
||||
@@ -923,6 +936,16 @@ class Router
|
||||
{
|
||||
return $this->debugList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current processing route details.
|
||||
*
|
||||
* @return ILoadableRoute
|
||||
*/
|
||||
public function getCurrentProcessingRoute(): ILoadableRoute
|
||||
{
|
||||
return $this->currentProcessingRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the rendering behavior of the router.
|
||||
@@ -942,7 +965,8 @@ class Router
|
||||
public function addExceptionHandler(IExceptionHandler $handler): self
|
||||
{
|
||||
$this->exceptionHandlers[] = $handler;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
|
||||
global $stack;
|
||||
$stack = [];
|
||||
|
||||
TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () use ($stack) {
|
||||
TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () {
|
||||
|
||||
TestRouter::group(['exceptionHandler' => ExceptionHandlerThird::class], function () use ($stack) {
|
||||
TestRouter::group(['prefix' => '/test', 'exceptionHandler' => ExceptionHandlerThird::class], function () {
|
||||
|
||||
TestRouter::get('/my-path', 'DummyController@method1');
|
||||
|
||||
@@ -43,7 +43,7 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
|
||||
});
|
||||
|
||||
try {
|
||||
TestRouter::debug('/my-non-existing-path', 'get');
|
||||
TestRouter::debug('/test/non-existing', 'get');
|
||||
} catch (\ResponseException $e) {
|
||||
|
||||
}
|
||||
@@ -58,6 +58,33 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
}
|
||||
|
||||
public function testStopMergeExceptionHandlers()
|
||||
{
|
||||
global $stack;
|
||||
$stack = [];
|
||||
|
||||
TestRouter::group(['prefix' => '/', 'exceptionHandler' => ExceptionHandlerFirst::class], function () {
|
||||
|
||||
TestRouter::group(['prefix' => '/admin', 'exceptionHandler' => ExceptionHandlerSecond::class, 'mergeExceptionHandlers' => false], function () {
|
||||
|
||||
TestRouter::get('/my-path', 'DummyController@method1');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
TestRouter::debug('/admin/my-path-test', 'get');
|
||||
} catch (\Pecee\SimpleRouter\Exceptions\NotFoundHttpException $e) {
|
||||
|
||||
}
|
||||
|
||||
$expectedStack = [
|
||||
ExceptionHandlerSecond::class,
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedStack, $stack);
|
||||
}
|
||||
|
||||
public function testRewriteExceptionMessage()
|
||||
{
|
||||
$this->expectException(\Pecee\SimpleRouter\Exceptions\NotFoundHttpException::class);
|
||||
|
||||
@@ -175,6 +175,70 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
TestRouter::debug('/test', 'get');
|
||||
|
||||
$this->assertFalse($result);
|
||||
|
||||
}
|
||||
|
||||
public function testFixedSubdomainDynamicDomain()
|
||||
{
|
||||
TestRouter::request()->setHost('other.world.com');
|
||||
|
||||
$result = false;
|
||||
|
||||
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
|
||||
TestRouter::get('/test', function ($domain = null) use (&$result) {
|
||||
|
||||
$result = true;
|
||||
});
|
||||
});
|
||||
|
||||
TestRouter::debug('/test', 'get');
|
||||
|
||||
$this->assertTrue($result);
|
||||
|
||||
}
|
||||
|
||||
public function testFixedSubdomainDynamicDomainParameter()
|
||||
{
|
||||
TestRouter::request()->setHost('other.world.com');
|
||||
|
||||
$result = false;
|
||||
|
||||
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
|
||||
TestRouter::get('/test', 'DummyController@param');
|
||||
TestRouter::get('/test/{key}', 'DummyController@param');
|
||||
});
|
||||
|
||||
$response = TestRouter::debugOutputNoReset('/test', 'get');
|
||||
|
||||
$this->assertEquals('world.com', $response);
|
||||
|
||||
$response = TestRouter::debugOutput('/test/unittest', 'get');
|
||||
|
||||
$this->assertEquals('unittest, world.com', $response);
|
||||
|
||||
}
|
||||
|
||||
public function testWrongFixedSubdomainDynamicDomain()
|
||||
{
|
||||
TestRouter::request()->setHost('wrong.world.com');
|
||||
|
||||
$result = false;
|
||||
|
||||
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
|
||||
TestRouter::get('/test', function ($domain = null) use (&$result) {
|
||||
|
||||
$result = true;
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
TestRouter::debug('/test', 'get');
|
||||
} catch(\Exception $e) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
$this->assertFalse($result);
|
||||
|
||||
}
|
||||
|
||||
@@ -48,4 +48,17 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function debugOutputNoReset(string $testUrl, string $testMethod = 'get', bool $reset = true): string
|
||||
{
|
||||
$response = null;
|
||||
|
||||
// Route request
|
||||
ob_start();
|
||||
static::debugNoReset($testUrl, $testMethod, $reset);
|
||||
$response = ob_get_clean();
|
||||
|
||||
// Return response
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user