Compare commits

...

31 Commits

Author SHA1 Message Date
Simon Sessingø 448a423d5d Merge pull request #544 from skipperbent/v4-development
Version 4.3.3.0
2021-05-02 14:00:10 +02:00
Simon Sessingø df9a855579 Merge pull request #543 from skipperbent/v4-bugfixes
Bugfixes
2021-05-02 13:58:00 +02:00
Simon Sessingø d6bd9bbd72 Development
- [FEATURE] Namespace overwrite now works globally. 'Service¨' will append namespace whereas '\Service' will overwrite it.
- [FEATURE] Exceptionhandlers are now rendered in reverse order from newest to oldest. This allows for exceptions to be handled from parent exceptions which were otherwise ignored.
- Fixed incorrect return type for InputFile::getError to nullable int.
- Added return type to all, match, controller and resource in SimpleRouter class.
- Fixed incorrect usage of parse_str in Url::setQueryString method.
- Fixed incorrect expected value in array_flip in Url::removeParams method.
2021-05-02 13:48:13 +02:00
Simon Sessingø 569b3a8760 Merge pull request #541 from skipperbent/v4-development
Version 4.3.2.3
2021-04-28 10:05:34 +02:00
Simon Sessingø 79a075ef49 Merge pull request #540 from skipperbent/v4-type-hint-notice
Fixed deprecated notice when using class type hinting (issue: #538)
2021-04-28 10:03:55 +02:00
Simon Sessingø c66d7f7df7 Fixed deprecated notice when using class type hinting (issue: #538) 2021-04-28 09:57:47 +02:00
Simon Sessingø c6d0ff3c0e Merge pull request #536 from skipperbent/v4-development
Version 4.3.2.2
2021-04-28 03:41:47 +02:00
Simon Sessingø 718d60c53b Removed return type from IResourceController as the return type can be mixed. 2021-04-04 11:21:26 +02:00
Simon Sessingø 2a573f27fe Merge pull request #534 from skipperbent/v4-development
Version 4.3.2.1
2021-04-01 03:16:29 +02:00
Simon Sessingø ecbb0825e0 Added include param parameter to Url::getAbsoluteUrl method. 2021-04-01 03:14:22 +02:00
Simon Sessingø b94dc4355f Optimisations 2021-04-01 03:11:05 +02:00
Simon Sessingø 52c6c226c0 [BUGFIX] Fixed issue with BaseCsrfVerifier matching urls against urls with parameters.
- Added optional $includeParams parameter to Url::getRelativeUrl method.
2021-04-01 03:04:32 +02:00
Simon Sessingø 982fb9fab4 Merge pull request #532 from skipperbent/v4-development
Version 4.3.2.0
2021-04-01 02:37:52 +02:00
Simon Sessingø ca8fbf2b27 Merge pull request #531 from skipperbent/v4-feature-ip
[FEATURE] IP restrict access
2021-04-01 02:35:40 +02:00
Simon Sessingø e4584a451d Improved phpDoc for prepend methods 2021-03-31 13:31:20 +02:00
Simon Sessingø 8b11377fe8 Fixed typo 2021-03-31 03:25:35 +02:00
Simon Sessingø eccda10169 Added prependPrefix to Group class & updated documentation. 2021-03-31 03:23:04 +02:00
Simon Sessingø d92d50ecdc Updated features in documentation 2021-03-31 03:08:01 +02:00
Simon Sessingø dca0389115 Merge branch 'v4-development' into v4-feature-ip 2021-03-31 03:00:16 +02:00
Simon Sessingø 7adb4e8597 Fixed wrong link for partial-groups 2021-03-31 02:40:24 +02:00
Simon Sessingø b0e4becbba Merge branch 'v4-development' of github.com:skipperbent/simple-php-router into v4-development 2021-03-31 02:36:48 +02:00
Simon Sessingø 3b8e92b406 Updated documentation table of contents 2021-03-31 02:36:11 +02:00
Simon Sessingø 0e393fdc5f Minor changes
- Added better description of partialGroups in the documentation.
- Added custom base path example in documentation.
- Added isSubRoute event parameter for EVENT_ADD_ROUTE.
- Removed deprecation phpDoc from partialGroup.
- Added unit-test for adding custom base path.
2021-03-31 02:31:56 +02:00
Simon Sessingø 56c73640b7 Merge pull request #530 from skipperbent/v4-feature-verifier
[FEATURE] Added include property to BaseCsrfVerifier + unit tests.
2021-03-31 01:02:05 +02:00
Simon Sessingø f91f280975 Added https scheme to Request::setUri (used when calling getAbsoluteUrl). 2021-03-30 21:13:06 +02:00
Simon Sessingø 40f9b72963 Updated documentation 2021-03-30 20:52:39 +02:00
Simon Sessingø 245b909ab6 Updated readme 2021-03-30 20:48:37 +02:00
Simon Sessingø 50b7129cab Changed name of IpBlockAccess to IpRestrictAccess & updated documentation. 2021-03-30 20:44:40 +02:00
Simon Sessingø 57047d23ea [FEATURE] Ip access block 2021-03-30 20:38:18 +02:00
Simon Sessingø a98b5ba842 Fixed unit-tests for CsrfVerifierTest 2021-03-30 18:54:45 +02:00
Simon Sessingø b3d28e9432 [FEATURE] Added include åproperty to BaseCsrfVerifier + unit tests. 2021-03-30 18:49:37 +02:00
23 changed files with 526 additions and 105 deletions
+102 -30
View File
@@ -48,6 +48,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Namespaces](#namespaces)
- [Subdomain-routing](#subdomain-routing)
- [Route prefixes](#route-prefixes)
- [Partial groups](#partial-groups)
- [Form Method Spoofing](#form-method-spoofing)
- [Accessing The Current Route](#accessing-the-current-route)
- [Other examples](#other-examples)
@@ -82,6 +83,8 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Custom EventHandlers](#custom-eventhandlers)
- [Advanced](#advanced)
- [Disable multiple route rendering](#disable-multiple-route-rendering)
- [Restrict access to IP](#restrict-access-to-ip)
- [Setting custom base path](#setting-custom-base-path)
- [Url rewriting](#url-rewriting)
- [Changing current route](#changing-current-route)
- [Bootmanager: loading routes dynamically](#bootmanager-loading-routes-dynamically)
@@ -160,6 +163,8 @@ You can find the demo-project here: [https://github.com/skipperbent/simple-route
- Sub-domain routing
- Custom boot managers to rewrite urls to "nicer" ones.
- Input manager; easily manage `GET`, `POST` and `FILE` values.
- IP based restrictions.
- Easily extendable.
## Installation
@@ -466,7 +471,8 @@ SimpleRouter::get('/posts/{post}/comments/{comment}', function ($postId, $commen
});
```
**Note:** Route parameters are always encased within {} braces and should consist of alphabetic characters. Route parameters may not contain a - character. Use an underscore (_) instead.
**Note:** Route parameters are always encased within `{` `}` braces and should consist of alphabetic characters. Route parameters can only contain certain characters like `A-Z`, `a-z`, `0-9`, `-` and `_`.
If your route contain other characters, please see [Custom regex for matching parameters](#custom-regex-for-matching-parameters).
### Optional parameters
@@ -681,6 +687,27 @@ SimpleRouter::group(['prefix' => '/lang/{language}'], function ($language) {
});
```
## Partial groups
Partial router groups has the same benefits as a normal group, but **are only rendered once the url has matched**
in contrast to a normal group which are always rendered in order to retrieve it's child routes.
Partial groups are therefore more like a hybrid of a traditional route with the benefits of a group.
This can be extremely useful in situations where you only want special routes to be added, but only when a certain criteria or logic has been met.
**NOTE:** Use partial groups with caution as routes added within are only rendered and available once the url of the partial-group has matched.
This can cause `url()` not to find urls for the routes added within before the partial-group has been matched and is rendered.
**Example:**
```php
SimpleRouter::partialGroup('/plugin/{name}', function ($plugin) {
// Add routes from plugin
});
```
## Form Method Spoofing
HTML forms do not support `PUT`, `PATCH` or `DELETE` actions. So, when defining `PUT`, `PATCH` or `DELETE` routes that are called from an HTML form, you will need to add a hidden `_method` field to the form. The value sent with the `_method` field will be used as the HTTP request method:
@@ -1242,7 +1269,7 @@ All event callbacks will retrieve a `EventArgument` object as parameter. This ob
| `EVENT_ALL` | - | Fires when a event is triggered. |
| `EVENT_INIT` | - | Fires when router is initializing and before routes are loaded. |
| `EVENT_LOAD` | `loadedRoutes` | Fires when all routes has been loaded and rendered, just before the output is returned. |
| `EVENT_ADD_ROUTE` | `route` | Fires when route is added to the router. |
| `EVENT_ADD_ROUTE` | `route`<br>`isSubRoute` | Fires when route is added to the router. `isSubRoute` is true when sub-route is rendered. |
| `EVENT_REWRITE` | `rewriteUrl`<br>`rewriteRoute` | Fires when a url-rewrite is and just before the routes are re-initialized. |
| `EVENT_BOOT` | `bootmanagers` | Fires when the router is booting. This happens just before boot-managers are rendered and before any routes has been loaded. |
| `EVENT_RENDER_BOOTMANAGER` | `bootmanagers`<br>`bootmanager` | Fires before a boot-manager is rendered. |
@@ -1370,6 +1397,74 @@ By default the router will try to execute all routes that matches a given url. T
This behavior can be easily disabled by setting `SimpleRouter::enableMultiRouteRendering(false)` in your `routes.php` file. This is the same behavior as version 3 and below.
## Restrict access to IP
You can white and/or blacklist access to IP's using the build in `IpRestrictAccess` middleware.
Create your own custom Middleware and extend the `IpRestrictAccess` class.
The `IpRestrictAccess` class contains two properties `ipBlacklist` and `ipWhitelist` that can be added to your middleware to change which IP's that have access to your routes.
You can use `*` to restrict access to a range of ips.
```php
use \Pecee\Http\Middleware\IpRestrictAccess;
class IpBlockerMiddleware extends IpRestrictAccess
{
protected $ipBlacklist = [
'5.5.5.5',
'8.8.*',
];
protected $ipWhitelist = [
'8.8.2.2',
];
}
```
You can add the middleware to multiple routes by adding your [middleware to a group](#middleware).
## Setting custom base path
Sometimes it can be useful to add a custom base path to all of the routes added.
This can easily be done by taking advantage of the [Event Handlers](#events) support of the project.
```php
$basePath = '/basepath';
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $event) use($basePath) {
$route = $event->route;
// Skip routes added by group as these will inherit the url
if(!$event->isSubRoute) {
return;
}
switch (true) {
case $route instanceof ILoadableRoute:
$route->prependUrl($basePath);
break;
case $route instanceof IGroupRoute:
$route->prependPrefix($basePath);
break;
}
});
TestRouter::addEventHandler($eventHandler);
```
In the example shown above, we create a new `EVENT_ADD_ROUTE` event that triggers, when a new route is added.
We skip all subroutes as these will inherit the url from their parent. Then, if the route is a group, we change the prefix
otherwise we change the url.
## Url rewriting
### Changing current route
@@ -1656,40 +1751,17 @@ You can read more about adding your own custom regular expression for matching p
### Multiple routes matches? Which one has the priority?
The router will match routes in the order they're added.
The router will match routes in the order they're added and will render multiple routes, if they match.
It's possible to render multiple routes.
If you want the router to stop when a route is matched, you simply return a value in your callback or stop the execution manually (using `response()->json()` etc.).
If you want the router to stop when a route is matched, you simply return a value in your callback or stop the execution manually (using `response()->json()` etc.) or simply by returning a result.
Any returned objects that implements the `__toString()` magic method will also prevent other routes from being rendered.
If you want the router only to execute one route per request, you can [disabling multiple route rendering](#disable-multiple-route-rendering).
### Using the router on sub-paths
Using the library on a sub-path like `localhost/project/` is not officially supported, however it is possible to get it working quite easily.
Add an event that appends your sub-path when a new loadable route is added.
**Example:**
```php
// ... your routes.php file
if($isRunningLocally) {
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function (EventArgument $arg) use (&$status) {
if ($arg->route instanceof \Pecee\SimpleRouter\Route\LoadableRoute) {
$arg->route->prependUrl('/local-path');
}
});
TestRouter::addEventHandler($eventHandler);
}
```
Please refer to [Setting custom base path](#setting-custom-base-path) part of the documentation.
## Debugging
+14 -14
View File
@@ -6,43 +6,43 @@ interface IResourceController
{
/**
* @return string|null
* @return mixed
*/
public function index(): ?string;
public function index();
/**
* @param mixed $id
* @return string|null
* @return mixed
*/
public function show($id): ?string;
public function show($id);
/**
* @return string|null
* @return mixed
*/
public function store(): ?string;
public function store();
/**
* @return string|null
* @return mixed
*/
public function create(): ?string;
public function create();
/**
* View
* @param mixed $id
* @return string|null
* @return mixed
*/
public function edit($id): ?string;
public function edit($id);
/**
* @param mixed $id
* @return string|null
* @return mixed
*/
public function update($id): ?string;
public function update($id);
/**
* @param mixed $id
* @return string|null
* @return mixed
*/
public function destroy($id): ?string;
public function destroy($id);
}
+9 -9
View File
@@ -165,7 +165,7 @@ class InputFile implements IInputItem
* @param string $name
* @return static
*/
public function setFilename($name): IInputItem
public function setFilename(string $name): IInputItem
{
$this->filename = $name;
@@ -188,7 +188,7 @@ class InputFile implements IInputItem
* @param string $destination
* @return bool
*/
public function move($destination): bool
public function move(string $destination): bool
{
return move_uploaded_file($this->tmpName, $destination);
}
@@ -216,20 +216,20 @@ class InputFile implements IInputItem
/**
* Get upload-error code.
*
* @return int
* @return int|null
*/
public function getError(): int
public function getError(): ?int
{
return (int)$this->errors;
return $this->errors;
}
/**
* Set error
*
* @param int $error
* @param int|null $error
* @return static
*/
public function setError($error): IInputItem
public function setError(?int $error): IInputItem
{
$this->errors = (int)$error;
@@ -249,7 +249,7 @@ class InputFile implements IInputItem
* @param string $name
* @return static
*/
public function setTmpName($name): IInputItem
public function setTmpName(string $name): IInputItem
{
$this->tmpName = $name;
@@ -261,7 +261,7 @@ class InputFile implements IInputItem
return $this->getTmpName();
}
public function getValue()
public function getValue(): string
{
return $this->getFilename();
}
+1 -1
View File
@@ -147,7 +147,7 @@ class InputHandler
* @param array|null $original
* @return array
*/
protected function rearrangeFile(array $values, &$index, $original): array
protected function rearrangeFile(array $values, array &$index, ?array $original): array
{
$originalIndex = $index[0];
array_shift($index);
+30 -6
View File
@@ -12,7 +12,17 @@ class BaseCsrfVerifier implements IMiddleware
public const POST_KEY = 'csrf_token';
public const HEADER_KEY = 'X-CSRF-TOKEN';
/**
* Urls to ignore. You can use * to exclude all sub-urls on a given path.
* For example: /admin/*
* @var array|null
*/
protected $except;
/**
* Urls to include. Can be used to include urls from a certain path.
* @var array|null
*/
protected $include;
protected $tokenProvider;
/**
@@ -34,20 +44,34 @@ class BaseCsrfVerifier implements IMiddleware
return false;
}
$max = count($this->except) - 1;
for ($i = $max; $i >= 0; $i--) {
$url = $this->except[$i];
foreach($this->except as $url) {
$url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
$skip = $request->getUrl()->contains($url);
} else {
$skip = ($url === $request->getUrl()->getOriginalUrl());
$skip = ($url === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
if ($skip === true) {
if(is_array($this->include) === true && count($this->include) > 0) {
foreach($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
$skip = !$request->getUrl()->contains($includeUrl);
break;
}
$skip = !($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
}
if($skip === false) {
continue;
}
return true;
}
}
@@ -0,0 +1,47 @@
<?php
namespace Pecee\Http\Middleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\HttpException;
abstract class IpRestrictAccess implements IMiddleware
{
protected $ipBlacklist = [];
protected $ipWhitelist = [];
protected function validate(string $ip): bool
{
// Accept ip that is in white-list
if(in_array($ip, $this->ipWhitelist, true) === true) {
return true;
}
foreach ($this->ipBlacklist as $blackIp) {
// Blocks range (8.8.*)
if ($blackIp[strlen($blackIp) - 1] === '*' && strpos($ip, trim($blackIp, '*')) === 0) {
return false;
}
// Blocks exact match
if ($blackIp === $ip) {
return false;
}
}
return true;
}
/**
* @param Request $request
* @throws HttpException
*/
public function handle(Request $request): void
{
if($this->validate((string)$request->getIp()) === false) {
throw new HttpException(sprintf('Restricted ip. Access to %s has been blocked', $request->getIp()), 403);
}
}
}
+4
View File
@@ -391,6 +391,10 @@ class Request
if ($this->url->getHost() === null) {
$this->url->setHost((string)$this->getHost());
}
if($this->isSecure() === true) {
$this->url->setScheme('https');
}
}
/**
+14 -8
View File
@@ -248,8 +248,9 @@ class Url implements JsonSerializable
public function setQueryString(string $queryString): self
{
$params = [];
parse_str($queryString, $params);
if(parse_str($queryString, $params) !== false) {
if(count($params) > 0) {
return $this->setParams($params);
}
@@ -341,7 +342,7 @@ class Url implements JsonSerializable
*/
public function removeParams(...$names): self
{
$params = array_diff_key($this->getParams(), array_flip($names));
$params = array_diff_key($this->getParams(), array_flip(...$names));
$this->setParams($params);
return $this;
@@ -427,14 +428,18 @@ class Url implements JsonSerializable
/**
* Returns the relative url
*
* @param bool $includeParams
* @return string
*/
public function getRelativeUrl(): string
public function getRelativeUrl($includeParams = true): string
{
$params = $this->getQueryString();
$path = $this->path ?? '/';
$path = $this->path ?? '';
$query = $params !== '' ? '?' . $params : '';
if($includeParams === false) {
return $path;
}
$query = $this->getQueryString() !== '' ? '?' . $this->getQueryString() : '';
$fragment = $this->fragment !== null ? '#' . $this->fragment : '';
return $path . $query . $fragment;
@@ -443,9 +448,10 @@ class Url implements JsonSerializable
/**
* Returns the absolute url
*
* @param bool $includeParams
* @return string
*/
public function getAbsoluteUrl(): string
public function getAbsoluteUrl($includeParams = true): string
{
$scheme = $this->scheme !== null ? $this->scheme . '://' : '';
$host = $this->host ?? '';
@@ -454,7 +460,7 @@ class Url implements JsonSerializable
$pass = $this->password !== null ? ':' . $this->password : '';
$pass = ($user || $pass) ? $pass . '@' : '';
return $scheme . $user . $pass . $host . $port . $this->getRelativeUrl();
return $scheme . $user . $pass . $host . $port . $this->getRelativeUrl($includeParams);
}
/**
@@ -53,6 +53,14 @@ interface IGroupRoute extends IRoute
*/
public function setDomains(array $domains): self;
/**
* Prepends prefix while ensuring that the url has the correct formatting.
*
* @param string $url
* @return static
*/
public function prependPrefix(string $url): self;
/**
* Set prefix that child-routes will inherit.
*
@@ -40,7 +40,7 @@ interface ILoadableRoute extends IRoute
public function setUrl(string $url): self;
/**
* Prepend url
* Prepends url while ensuring that the url has the correct formatting.
* @param string $url
* @return ILoadableRoute
*/
@@ -55,7 +55,6 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
public function matchRegex(Request $request, $url): ?bool
{
/* Match on custom defined regular expression */
if ($this->regex === null) {
return null;
}
@@ -86,7 +85,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
}
/**
* Prepend url
* Prepends url while ensuring that the url has the correct formatting.
*
* @param string $url
* @return ILoadableRoute
+20 -2
View File
@@ -76,6 +76,11 @@ abstract class Route implements IRoute
if (is_callable($callback) === true) {
$router->debug('Executing callback');
/* Load class from type hinting */
if (is_array($callback) === true && isset($callback[0], $callback[1]) === true) {
$callback[0] = $router->getClassLoader()->loadClass($callback[0]);
}
/* When the callback is a function */
return $router->getClassLoader()->loadClosure($callback, $parameters);
}
@@ -160,7 +165,7 @@ abstract class Route implements IRoute
foreach ((array)$parameters[1] as $name) {
// Ignore parent parameters
if(isset($groupParameters[$name]) === true) {
if (isset($groupParameters[$name]) === true) {
$lastParams[$name] = $matches[$name];
continue;
}
@@ -244,6 +249,7 @@ abstract class Route implements IRoute
$this->group = $group;
/* Add/merge parent settings with child */
return $this->setSettings($group->toArray(), true);
}
@@ -331,6 +337,17 @@ abstract class Route implements IRoute
*/
public function setNamespace(string $namespace): IRoute
{
$ns = $this->getNamespace();
if ($ns !== null) {
// Don't overwrite namespaces that starts with \
if ($ns[0] !== '\\') {
$namespace .= '\\' . $ns;
} else {
$namespace = $ns;
}
}
$this->namespace = $namespace;
return $this;
@@ -401,7 +418,7 @@ abstract class Route implements IRoute
*/
public function setSettings(array $settings, bool $merge = false): IRoute
{
if ($this->namespace === null && isset($settings['namespace']) === true) {
if (isset($settings['namespace']) === true) {
$this->setNamespace($settings['namespace']);
}
@@ -576,6 +593,7 @@ abstract class Route implements IRoute
public function setFilterEmptyParams(bool $enabled): IRoute
{
$this->filterEmptyParams = $enabled;
return $this;
}
+11 -1
View File
@@ -153,6 +153,17 @@ class RouteGroup extends Route implements IGroupRoute
return $this;
}
/**
* Prepends prefix while ensuring that the url has the correct formatting.
*
* @param string $url
* @return static
*/
public function prependPrefix(string $url): IGroupRoute
{
return $this->setPrefix(rtrim($url, '/') . $this->prefix);
}
/**
* Set prefix that child-routes will inherit.
*
@@ -172,7 +183,6 @@ class RouteGroup extends Route implements IGroupRoute
*/
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($settings['prefix']) === true) {
$this->setPrefix($settings['prefix'] . $this->prefix);
}
@@ -1,8 +1,4 @@
<?php
/**
* @deprecated This class is deprecated and will be removed in future versions.
* @see \Pecee\SimpleRouter\Route\RouteGroup
*/
namespace Pecee\SimpleRouter\Route;
class RoutePartialGroup extends RouteGroup implements IPartialGroupRoute
+2 -2
View File
@@ -161,6 +161,7 @@ class Router
{
$this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [
'route' => $route,
'isSubRoute' => $this->isProcessingRoute,
]);
/*
@@ -184,7 +185,6 @@ class Router
*/
protected function renderAndProcess(IRoute $route): void
{
$this->isProcessingRoute = true;
$route->renderRoute($this->request, $this);
$this->isProcessingRoute = false;
@@ -509,7 +509,7 @@ class Router
]);
/* @var $handler IExceptionHandler */
foreach ($this->exceptionHandlers as $key => $handler) {
foreach (array_reverse($this->exceptionHandlers) as $key => $handler) {
if (is_object($handler) === false) {
$handler = new $handler();
+7 -20
View File
@@ -4,8 +4,8 @@
* Router helper class
* ---------------------------
*
* 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.
* This class is added so calls can be made statically like SimpleRouter::get() making the code look pretty.
* It also adds some extra functionality like default-namespace etc.
*/
namespace Pecee\SimpleRouter;
@@ -348,7 +348,7 @@ class SimpleRouter
* @param array|null $settings
* @return RouteUrl|IRoute
*/
public static function match(array $requestMethods, string $url, $callback, array $settings = null)
public static function match(array $requestMethods, string $url, $callback, array $settings = null): IRoute
{
$route = new RouteUrl($url, $callback);
$route->setRequestMethods($requestMethods);
@@ -368,7 +368,7 @@ class SimpleRouter
* @param array|null $settings
* @return RouteUrl|IRoute
*/
public static function all(string $url, $callback, array $settings = null)
public static function all(string $url, $callback, array $settings = null): IRoute
{
$route = new RouteUrl($url, $callback);
@@ -387,7 +387,7 @@ class SimpleRouter
* @param array|null $settings
* @return RouteController|IRoute
*/
public static function controller(string $url, string $controller, array $settings = null)
public static function controller(string $url, string $controller, array $settings = null): IRoute
{
$route = new RouteController($url, $controller);
@@ -406,7 +406,7 @@ class SimpleRouter
* @param array|null $settings
* @return RouteResource|IRoute
*/
public static function resource(string $url, string $controller, array $settings = null)
public static function resource(string $url, string $controller, array $settings = null): IRoute
{
$route = new RouteResource($url, $controller);
@@ -512,20 +512,7 @@ class SimpleRouter
public static function addDefaultNamespace(IRoute $route): IRoute
{
if (static::$defaultNamespace !== null) {
$ns = static::$defaultNamespace;
$namespace = $route->getNamespace();
if ($namespace !== null) {
// Don't overwrite namespaces that starts with \
if ($namespace[0] !== '\\') {
$ns .= '\\' . $namespace;
} else {
$ns = $namespace;
}
}
$route->setNamespace($ns);
$route->setNamespace(static::$defaultNamespace);
}
return $route;
@@ -0,0 +1,66 @@
<?php
require_once 'Dummy/CsrfVerifier/DummyCsrfVerifier.php';
require_once 'Dummy/Security/SilentTokenProvider.php';
class CsrfVerifierTest extends \PHPUnit\Framework\TestCase
{
public function testTokenPass()
{
global $_POST;
$tokenProvider = new SilentTokenProvider();
$_POST[DummyCsrfVerifier::POST_KEY] = $tokenProvider->getToken();
TestRouter::router()->reset();
$router = TestRouter::router();
$router->getRequest()->setMethod(\Pecee\Http\Request::REQUEST_TYPE_POST);
$router->getRequest()->setUrl(new \Pecee\Http\Url('/page'));
$csrf = new DummyCsrfVerifier();
$csrf->setTokenProvider($tokenProvider);
$csrf->handle($router->getRequest());
// If handle doesn't throw exception, the test has passed
$this->assertTrue(true);
}
public function testTokenFail()
{
$this->expectException(\Pecee\Http\Middleware\Exceptions\TokenMismatchException::class);
global $_POST;
$tokenProvider = new SilentTokenProvider();
$router = TestRouter::router();
$router->getRequest()->setMethod(\Pecee\Http\Request::REQUEST_TYPE_POST);
$router->getRequest()->setUrl(new \Pecee\Http\Url('/page'));
$csrf = new DummyCsrfVerifier();
$csrf->setTokenProvider($tokenProvider);
$csrf->handle($router->getRequest());
}
public function testExcludeInclude()
{
$router = TestRouter::router();
$csrf = new DummyCsrfVerifier();
$request = $router->getRequest();
$request->setUrl(new \Pecee\Http\Url('/exclude-page'));
$this->assertTrue($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/exclude-all/page'));
$this->assertTrue($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/exclude-all/include-page'));
$this->assertFalse($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/include-page'));
$this->assertFalse($csrf->testSkip($router->getRequest()));
}
}
@@ -0,0 +1,71 @@
<?php
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Middleware/IpRestrictMiddleware.php';
class CustomMiddlewareTest extends \PHPUnit\Framework\TestCase
{
public function testIpBlock() {
$this->expectException(\Pecee\SimpleRouter\Exceptions\HttpException::class);
global $_SERVER;
// Test exact ip
$_SERVER['remote-addr'] = '5.5.5.5';
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/fail', 'DummyController@method1');
});
TestRouter::debug('/fail');
// Test ip-range
$_SERVER['remote-addr'] = '8.8.4.4';
TestRouter::router()->reset();
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/fail', 'DummyController@method1');
});
TestRouter::debug('/fail');
}
public function testIpSuccess() {
global $_SERVER;
// Test ip that is not blocked
$_SERVER['remote-addr'] = '6.6.6.6';
TestRouter::router()->reset();
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/success', 'DummyController@method1');
});
TestRouter::debug('/success');
// Test ip in whitelist
$_SERVER['remote-addr'] = '8.8.2.2';
TestRouter::router()->reset();
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/success', 'DummyController@method1');
});
TestRouter::debug('/success');
$this->assertTrue(true);
}
}
@@ -0,0 +1,18 @@
<?php
class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier {
protected $except = [
'/exclude-page',
'/exclude-all/*',
];
protected $include = [
'/exclude-all/include-page',
];
public function testSkip(\Pecee\Http\Request $request) {
return $this->skip($request);
}
}
@@ -0,0 +1,14 @@
<?php
class IpRestrictMiddleware extends \Pecee\Http\Middleware\IpRestrictAccess {
protected $ipBlacklist = [
'5.5.5.5',
'8.8.*',
];
protected $ipWhitelist = [
'8.8.2.2',
];
}
@@ -103,4 +103,49 @@ class EventHandlerTest extends \PHPUnit\Framework\TestCase
}
public function testCustomBasePath() {
$basePath = '/basepath/';
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $data) use($basePath) {
// Skip routes added by group
if($data->isSubRoute === false) {
switch (true) {
case $data->route instanceof \Pecee\SimpleRouter\Route\ILoadableRoute:
$data->route->prependUrl($basePath);
break;
case $data->route instanceof \Pecee\SimpleRouter\Route\IGroupRoute:
$data->route->prependPrefix($basePath);
break;
}
}
});
$results = [];
TestRouter::addEventHandler($eventHandler);
TestRouter::get('/about', function() use(&$results) {
$results[] = 'about';
});
TestRouter::group(['prefix' => '/admin'], function() use(&$results) {
TestRouter::get('/', function() use(&$results) {
$results[] = 'admin';
});
});
TestRouter::router()->setRenderMultipleRoutes(false);
TestRouter::debugNoReset('/basepath/about');
TestRouter::debugNoReset('/basepath/admin');
$this->assertEquals(['about', 'admin'], $results);
}
}
@@ -3,20 +3,20 @@
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
class GroupTest extends \PHPUnit\Framework\TestCase
class RouterGroupTest extends \PHPUnit\Framework\TestCase
{
public function testGroupLoad()
{
$result = false;
TestRouter::group(['prefix' => '/group'], function () use(&$result) {
TestRouter::group(['prefix' => '/group'], function () use (&$result) {
$result = true;
});
try {
TestRouter::debug('/', 'get');
} catch(\Exception $e) {
} catch (\Exception $e) {
}
$this->assertTrue($result);
@@ -81,4 +81,40 @@ class GroupTest extends \PHPUnit\Framework\TestCase
}
public function testNamespaceExtend()
{
TestRouter::group(['namespace' => '\My\Namespace'], function () use (&$result) {
TestRouter::group(['namespace' => 'Service'], function () use (&$result) {
TestRouter::get('/test', function () use (&$result) {
return \Pecee\SimpleRouter\SimpleRouter::router()->getRequest()->getLoadedRoute()->getNamespace();
});
});
});
$namespace = TestRouter::debugOutput('/test');
$this->assertEquals('\My\Namespace\Service', $namespace);
}
public function testNamespaceOverwrite()
{
TestRouter::group(['namespace' => '\My\Namespace'], function () use (&$result) {
TestRouter::group(['namespace' => '\Service'], function () use (&$result) {
TestRouter::get('/test', function () use (&$result) {
return \Pecee\SimpleRouter\SimpleRouter::router()->getRequest()->getLoadedRoute()->getNamespace();
});
});
});
$namespace = TestRouter::debugOutput('/test');
$this->assertEquals('\Service', $namespace);
}
}
@@ -49,9 +49,9 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
}
$expectedStack = [
ExceptionHandlerFirst::class,
ExceptionHandlerSecond::class,
ExceptionHandlerThird::class,
ExceptionHandlerSecond::class,
ExceptionHandlerFirst::class,
];
$this->assertEquals($expectedStack, $stack);