Compare commits

...

42 Commits

Author SHA1 Message Date
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
Simon Sessingø d0c34255b5 Merge pull request #528 from skipperbent/v4-development
Version 4.3.1.0
2021-03-29 22:31:01 +02:00
Simon Sessingø 5a917a6905 Merge pull request #526 from skipperbent/v4-php8-fix
Version 4.3.1.0-test
2021-03-29 22:28:44 +02:00
Simon Sessingø be2d45f0ad Updated documentation 2021-03-29 22:24:02 +02:00
Simon Sessingø dd9a6eab7d [FEATURE] Added class + method loading to IClassLoader. 2021-03-29 22:15:55 +02:00
Simon Sessingø 5621ffc724 Updated documentation 2021-03-29 22:04:46 +02:00
Simon Sessingø 438193ef59 Added deprecated warning for RoutePartialGroup. 2021-03-29 21:59:30 +02:00
Simon Sessingø adc879bb13 [BUGFIX] Fixed InputHandler::find and InputHandler::value failing when using array methods. 2021-03-29 18:45:49 +02:00
Simon Sessingø 06ee78a48f Updated example for group parameters 2021-03-29 17:41:18 +02:00
Simon Sessingø 4b992f0a2f Merge pull request #527 from skipperbent/v4-feature-group-parameters
[FEATURE] Added support for parameters in group prefix.
2021-03-29 17:14:51 +02:00
Simon Sessingø c423172c23 Added deep parameters pass unit-test 2021-03-29 17:10:12 +02:00
Simon Sessingø d6d83ac5bd Parameters are now correctly passed on to sub-routes 2021-03-29 17:05:45 +02:00
Simon Sessingø b05bbccc28 [FEATURE] Added support for parameters in group prefix. 2021-03-29 16:56:37 +02:00
Simon Sessingø d6bc713e5b [CLEANUP] Added qualifier import. 2021-03-29 15:40:50 +02:00
Simon Sessingø 8eba5ab3d5 [FEATURE] php8 compatibility.
- Fixed possible error causing parameters not to be set properly when using partialGroup.
- Removed unused import reference.
- Added unit-tests.
2021-03-29 15:11:58 +02:00
41 changed files with 862 additions and 279 deletions
+158 -63
View File
@@ -83,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)
@@ -161,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
@@ -467,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
@@ -631,6 +636,23 @@ SimpleRouter::group(['namespace' => 'Admin'], function () {
});
```
You can add parameters to the prefixes of your routes.
Parameters from your previous routes will be injected
into your routes after any route-required parameters, starting from oldest to newest.
```php
SimpleRouter::group(['prefix' => '/lang/{lang}'], function ($language) {
SimpleRouter::get('/about', function($language) {
// Will match /lang/da/about
});
});
```
### Subdomain-routing
Route groups may also be used to handle sub-domain routing. Sub-domains may be assigned route parameters just like route urls, allowing you to capture a portion of the sub-domain for usage in your route or controller. The sub-domain may be specified using the `domain` key on the group attribute array:
@@ -655,30 +677,37 @@ SimpleRouter::group(['prefix' => '/admin'], function () {
});
```
You can also use parameters in your groups:
```php
SimpleRouter::group(['prefix' => '/lang/{language}'], function ($language) {
SimpleRouter::get('/users', function ($language) {
// Matches The "/lang/da/users" URL
});
});
```
## Partial groups
Partial router groups has the same benefits as a normal group, but supports parameters and are only rendered once the url has matched.
Partial groups will render once a part of the url has matched.
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, when a certain criteria or logic has been met.
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.
**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('/lang/{language}', function ($language) {
SimpleRouter::partialGroup('/plugin/{name}', function ($plugin) {
SimpleRouter::get('/', function($language) {
// Matches The "/lang/da" URL
});
// 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:
@@ -1240,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. |
@@ -1368,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
@@ -1493,6 +1590,18 @@ class MyCustomClassLoader implements IClassLoader
return new $class();
}
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return object
*/
public function loadClassMethod($class, string $method, array $parameters)
{
return call_user_func_array([$class, $method], array_values($parameters));
}
/**
* Load closure
@@ -1503,7 +1612,7 @@ class MyCustomClassLoader implements IClassLoader
*/
public function loadClosure(Callable $closure, array $parameters)
{
return \call_user_func_array($closure, $parameters);
return \call_user_func_array($closure, array_values($parameters));
}
}
@@ -1520,6 +1629,8 @@ SimpleRouter::setCustomClassLoader(new MyCustomClassLoader());
php-di support was discontinued by version 4.3, however you can easily add it again by creating your own class-loader like the example below:
```php
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
class MyCustomClassLoader implements IClassLoader
{
@@ -1528,7 +1639,7 @@ class MyCustomClassLoader implements IClassLoader
public function __construct()
{
// Create our new php-di container
$container = (new \DI\ContainerBuilder())
$this->container = (new \DI\ContainerBuilder())
->useAutowiring(true)
->build();
}
@@ -1546,15 +1657,27 @@ class MyCustomClassLoader implements IClassLoader
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
}
if ($this->container !== null) {
try {
return $this->container->get($class);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
return new $class();
try {
return $this->container->get($class);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return object
*/
public function loadClassMethod($class, string $method, array $parameters)
{
try {
return $this->container->call([$class, $method], $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
/**
@@ -1564,19 +1687,14 @@ class MyCustomClassLoader implements IClassLoader
* @param array $parameters
* @return mixed
*/
public function loadClosure(Callable $closure, array $parameters)
public function loadClosure(callable $closure, array $parameters)
{
if ($this->container !== null) {
try {
return $this->container->call($closure, $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
return \call_user_func_array($closure, $parameters);
try {
return $this->container->call($closure, $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
}
```
@@ -1633,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);
}
@@ -2,7 +2,9 @@
namespace Pecee\Http\Exceptions;
class MalformedUrlException extends \Exception
use Exception;
class MalformedUrlException extends Exception
{
}
+6 -6
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);
}
@@ -226,10 +226,10 @@ class InputFile implements IInputItem
/**
* 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();
}
+20 -16
View File
@@ -63,7 +63,7 @@ class InputHandler
public function parseInputs(): void
{
/* Parse get requests */
if (\count($_GET) !== 0) {
if (count($_GET) !== 0) {
$this->originalParams = $_GET;
$this->get = $this->parseInputItem($this->originalParams);
}
@@ -85,12 +85,12 @@ class InputHandler
}
}
if (\count($this->originalPost) !== 0) {
if (count($this->originalPost) !== 0) {
$this->post = $this->parseInputItem($this->originalPost);
}
/* Parse get requests */
if (\count($_FILES) !== 0) {
if (count($_FILES) !== 0) {
$this->originalFile = $_FILES;
$this->file = $this->parseFiles($this->originalFile);
}
@@ -108,13 +108,13 @@ class InputHandler
foreach ($files as $key => $value) {
// Parse multi dept file array
if(isset($value['name']) === false && \is_array($value) === true) {
if(isset($value['name']) === false && is_array($value) === true) {
$list[$key] = $this->parseFiles($value, $key);
continue;
}
// Handle array input
if (\is_array($value['name']) === false) {
if (is_array($value['name']) === false) {
$values['index'] = $parentKey ?? $key;
try {
@@ -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);
@@ -156,7 +156,7 @@ class InputHandler
foreach ($values as $key => $value) {
if (\is_array($original['name'][$key]) === false) {
if (is_array($original['name'][$key]) === false) {
try {
@@ -210,7 +210,7 @@ class InputHandler
foreach ($array as $key => $value) {
// Handle array input
if (\is_array($value) === true) {
if (is_array($value) === true) {
$value = $this->parseInputItem($value);
}
@@ -231,15 +231,19 @@ class InputHandler
{
$element = null;
if (\count($methods) === 0 || \in_array(Request::REQUEST_TYPE_GET, $methods, true) === true) {
if(count($methods) > 0) {
$methods = is_array(...$methods) ? array_values(...$methods) : $methods;
}
if (count($methods) === 0 || in_array(Request::REQUEST_TYPE_GET, $methods, true) === true) {
$element = $this->get($index);
}
if (($element === null && \count($methods) === 0) || (\count($methods) !== 0 && \in_array(Request::REQUEST_TYPE_POST, $methods, true) === true)) {
if (($element === null && count($methods) === 0) || (count($methods) !== 0 && in_array(Request::REQUEST_TYPE_POST, $methods, true) === true)) {
$element = $this->post($index);
}
if (($element === null && \count($methods) === 0) || (\count($methods) !== 0 && \in_array('file', $methods, true) === true)) {
if (($element === null && count($methods) === 0) || (count($methods) !== 0 && in_array('file', $methods, true) === true)) {
$element = $this->file($index);
}
@@ -256,7 +260,7 @@ class InputHandler
$item = $item->getValue();
}
$output[$key] = \is_array($item) ? $this->getValueFromArray($item) : $item;
$output[$key] = is_array($item) ? $this->getValueFromArray($item) : $item;
}
return $output;
@@ -279,13 +283,13 @@ class InputHandler
}
/* Handle collection */
if (\is_array($input) === true) {
if (is_array($input) === true) {
$output = $this->getValueFromArray($input);
return (\count($output) === 0) ? $defaultValue : $output;
return (count($output) === 0) ? $defaultValue : $output;
}
return ($input === null || (\is_string($input) && trim($input) === '')) ? $defaultValue : $input;
return ($input === null || (is_string($input) && trim($input) === '')) ? $defaultValue : $input;
}
/**
@@ -344,7 +348,7 @@ class InputHandler
public function all(array $filter = []): array
{
$output = $this->originalParams + $this->originalPost + $this->originalFile;
$output = (\count($filter) > 0) ? \array_intersect_key($output, \array_flip($filter)) : $output;
$output = (count($filter) > 0) ? array_intersect_key($output, array_flip($filter)) : $output;
foreach ($filter as $filterKey) {
if (array_key_exists($filterKey, $output) === false) {
+7 -4
View File
@@ -2,7 +2,10 @@
namespace Pecee\Http\Input;
class InputItem implements IInputItem, \IteratorAggregate
use ArrayIterator;
use IteratorAggregate;
class InputItem implements IInputItem, IteratorAggregate
{
public $index;
public $name;
@@ -75,11 +78,11 @@ class InputItem implements IInputItem, \IteratorAggregate
public function __toString(): string
{
$value = $this->getValue();
return (\is_array($value) === true) ? json_encode($value) : $value;
return (is_array($value) === true) ? json_encode($value) : $value;
}
public function getIterator(): \ArrayIterator
public function getIterator(): ArrayIterator
{
return new \ArrayIterator($this->getValue());
return new ArrayIterator($this->getValue());
}
}
+32 -8
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;
/**
@@ -30,24 +40,38 @@ class BaseCsrfVerifier implements IMiddleware
*/
protected function skip(Request $request): bool
{
if ($this->except === null || \count($this->except) === 0) {
if ($this->except === null || count($this->except) === 0) {
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] === '*') {
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;
}
}
@@ -2,7 +2,9 @@
namespace Pecee\Http\Middleware\Exceptions;
class TokenMismatchException extends \Exception
use Exception;
class TokenMismatchException extends Exception
{
}
@@ -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);
}
}
}
+6 -2
View File
@@ -369,7 +369,7 @@ class Request
*/
public function isPostBack(): bool
{
return \in_array($this->getMethod(), static::$requestTypesPost, true);
return in_array($this->getMethod(), static::$requestTypesPost, true);
}
/**
@@ -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');
}
}
/**
@@ -475,7 +479,7 @@ class Request
*/
public function getLoadedRoute(): ?ILoadableRoute
{
return (\count($this->loadedRoutes) > 0) ? end($this->loadedRoutes) : null;
return (count($this->loadedRoutes) > 0) ? end($this->loadedRoutes) : null;
}
/**
+3 -2
View File
@@ -2,6 +2,7 @@
namespace Pecee\Http;
use JsonSerializable;
use Pecee\Exceptions\InvalidArgumentException;
class Response
@@ -85,14 +86,14 @@ class Response
/**
* Json encode
* @param array|\JsonSerializable $value
* @param array|JsonSerializable $value
* @param ?int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param int $dept JSON debt.
* @throws InvalidArgumentException
*/
public function json($value, ?int $options = null, int $dept = 512): void
{
if (($value instanceof \JsonSerializable) === false && \is_array($value) === false) {
if (($value instanceof JsonSerializable) === false && is_array($value) === false) {
throw new InvalidArgumentException('Invalid type for parameter "value". Must be of type array or object implementing the \JsonSerializable interface.');
}
@@ -2,6 +2,7 @@
namespace Pecee\Http\Security;
use Exception;
use Pecee\Http\Security\Exceptions\SecurityException;
class CookieTokenProvider implements ITokenProvider
@@ -34,7 +35,7 @@ class CookieTokenProvider implements ITokenProvider
{
try {
return bin2hex(random_bytes(32));
} catch (\Exception $e) {
} catch (Exception $e) {
throw new SecurityException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
@@ -2,7 +2,9 @@
namespace Pecee\Http\Security\Exceptions;
class SecurityException extends \Exception
use Exception;
class SecurityException extends Exception
{
}
+14 -8
View File
@@ -2,9 +2,10 @@
namespace Pecee\Http;
use JsonSerializable;
use Pecee\Http\Exceptions\MalformedUrlException;
class Url implements \JsonSerializable
class Url implements JsonSerializable
{
private $originalUrl;
@@ -409,7 +410,7 @@ class Url implements \JsonSerializable
*/
public static function arrayToParams(array $getParams = [], bool $includeEmpty = true): string
{
if (\count($getParams) !== 0) {
if (count($getParams) !== 0) {
if ($includeEmpty === false) {
$getParams = array_filter($getParams, static function ($item) {
@@ -426,14 +427,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;
@@ -442,9 +447,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 ?? '';
@@ -453,7 +459,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);
}
/**
@@ -22,6 +22,18 @@ class ClassLoader implements IClassLoader
return new $class();
}
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return object
*/
public function loadClassMethod($class, string $method, array $parameters)
{
return call_user_func_array([$class, $method], array_values($parameters));
}
/**
* Load closure
*
@@ -31,7 +43,7 @@ class ClassLoader implements IClassLoader
*/
public function loadClosure(Callable $closure, array $parameters)
{
return \call_user_func_array($closure, $parameters);
return call_user_func_array($closure, array_values($parameters));
}
}
@@ -12,6 +12,15 @@ interface IClassLoader
*/
public function loadClass(string $class);
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return object
*/
public function loadClassMethod($class, string $method, array $parameters);
/**
* Called when loading method
*
@@ -21,4 +30,4 @@ interface IClassLoader
*/
public function loadClosure(Callable $closure, array $parameters);
}
}
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter\Event;
use InvalidArgumentException;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Router;
@@ -91,11 +92,11 @@ class EventArgument implements IEventArgument
/**
* @param string $name
* @param mixed $value
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __set(string $name, $value)
{
throw new \InvalidArgumentException('Not supported');
throw new InvalidArgumentException('Not supported');
}
/**
@@ -2,7 +2,9 @@
namespace Pecee\SimpleRouter\Exceptions;
class HttpException extends \Exception
use Exception;
class HttpException extends Exception
{
}
@@ -2,6 +2,8 @@
namespace Pecee\SimpleRouter\Handlers;
use Closure;
use Exception;
use Pecee\Http\Request;
/**
@@ -17,19 +19,19 @@ class CallbackExceptionHandler implements IExceptionHandler
protected $callback;
public function __construct(\Closure $callback)
public function __construct(Closure $callback)
{
$this->callback = $callback;
}
/**
* @param Request $request
* @param \Exception $error
* @param Exception $error
*/
public function handleError(Request $request, \Exception $error): void
public function handleError(Request $request, Exception $error): void
{
/* Fire exceptions */
\call_user_func($this->callback,
call_user_func($this->callback,
$request,
$error
);
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter\Handlers;
use Closure;
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;
@@ -10,7 +11,7 @@ class DebugEventHandler implements IEventHandler
/**
* Debug callback
* @var \Closure
* @var Closure
*/
protected $callback;
@@ -52,9 +53,9 @@ class DebugEventHandler implements IEventHandler
/**
* Set debug callback
*
* @param \Closure $event
* @param Closure $event
*/
public function setCallback(\Closure $event): void
public function setCallback(Closure $event): void
{
$this->callback = $event;
}
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter\Handlers;
use Closure;
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;
@@ -125,10 +126,10 @@ class EventHandler implements IEventHandler
* Register new event
*
* @param string $name
* @param \Closure $callback
* @param Closure $callback
* @return static
*/
public function register(string $name, \Closure $callback): IEventHandler
public function register(string $name, Closure $callback): IEventHandler
{
if (isset($this->registeredEvents[$name]) === true) {
$this->registeredEvents[$name][] = $callback;
@@ -175,7 +176,7 @@ class EventHandler implements IEventHandler
{
$events = $this->getEvents(static::EVENT_ALL, $name);
/* @var $event \Closure */
/* @var $event Closure */
foreach ($events as $event) {
$event(new EventArgument($name, $router, $eventArgs));
}
@@ -2,14 +2,15 @@
namespace Pecee\SimpleRouter\Handlers;
use Exception;
use Pecee\Http\Request;
interface IExceptionHandler
{
/**
* @param Request $request
* @param \Exception $error
* @param Exception $error
*/
public function handleError(Request $request, \Exception $error): void;
public function handleError(Request $request, Exception $error): void;
}
@@ -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
*/
@@ -34,7 +34,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
foreach ($this->getMiddlewares() as $middleware) {
if (\is_object($middleware) === false) {
if (is_object($middleware) === false) {
$middleware = $router->getClassLoader()->loadClass($middleware);
}
@@ -42,7 +42,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
throw new HttpException($middleware . ' must be inherit the IMiddleware interface');
}
$className = \get_class($middleware);
$className = get_class($middleware);
$router->debug('Loading middleware "%s"', $className);
$middleware->handle($request);
@@ -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
@@ -116,7 +115,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
$group = $this->getGroup();
if ($group !== null && \count($group->getDomains()) !== 0) {
if ($group !== null && count($group->getDomains()) !== 0) {
$url = '//' . $group->getDomains()[0] . $url;
}
@@ -132,7 +131,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
foreach (array_keys($params) as $param) {
if ($parameters === '' || (\is_array($parameters) === true && \count($parameters) === 0)) {
if ($parameters === '' || (is_array($parameters) === true && count($parameters) === 0)) {
$value = '';
} else {
$p = (array)$parameters;
+35 -23
View File
@@ -2,7 +2,6 @@
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
@@ -52,7 +51,7 @@ abstract class Route implements IRoute
*/
public function renderRoute(Request $request, Router $router): ?string
{
$router->debug('Starting rendering route "%s"', \get_class($this));
$router->debug('Starting rendering route "%s"', get_class($this));
$callback = $this->getCallback();
@@ -74,9 +73,14 @@ abstract class Route implements IRoute
}
/* Render callback function */
if (\is_callable($callback) === true) {
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);
}
@@ -98,9 +102,9 @@ abstract class Route implements IRoute
throw new ClassNotFoundHttpException($className, $method, sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404, null);
}
$router->debug('Executing callback');
$router->debug('Executing callback %s -> %s', $className, $method);
return \call_user_func_array([$class, $method], $parameters);
return $router->getClassLoader()->loadClassMethod($class, $method, $parameters);
}
protected function parseParameters($route, $url, $parameterRegex = null): ?array
@@ -127,7 +131,7 @@ abstract class Route implements IRoute
$regex = '';
if ($key < \count($parameters[1])) {
if ($key < count($parameters[1])) {
$name = $parameters[1][$key];
@@ -153,12 +157,27 @@ abstract class Route implements IRoute
if (isset($parameters[1]) === true) {
$groupParameters = $this->getGroup() !== null ? $this->getGroup()->getParameters() : [];
$lastParams = [];
/* Only take matched parameters with name */
foreach ((array)$parameters[1] as $name) {
// Ignore parent parameters
if (isset($groupParameters[$name]) === true) {
$lastParams[$name] = $matches[$name];
continue;
}
$values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
}
$values = array_merge($values, $lastParams);
}
$this->originalParameters = $values;
return $values;
}
@@ -171,7 +190,7 @@ abstract class Route implements IRoute
*/
public function getIdentifier(): string
{
if (\is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
return $this->callback;
}
@@ -270,11 +289,11 @@ abstract class Route implements IRoute
public function getMethod(): ?string
{
if (\is_array($this->callback) === true && \count($this->callback) > 1) {
if (is_array($this->callback) === true && count($this->callback) > 1) {
return $this->callback[1];
}
if (\is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[1];
@@ -285,11 +304,11 @@ abstract class Route implements IRoute
public function getClass(): ?string
{
if (\is_array($this->callback) === true && \count($this->callback) > 0) {
if (is_array($this->callback) === true && count($this->callback) > 0) {
return $this->callback[0];
}
if (\is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[0];
@@ -360,15 +379,15 @@ abstract class Route implements IRoute
$values['namespace'] = $this->namespace;
}
if (\count($this->requestMethods) !== 0) {
if (count($this->requestMethods) !== 0) {
$values['method'] = $this->requestMethods;
}
if (\count($this->where) !== 0) {
if (count($this->where) !== 0) {
$values['where'] = $this->where;
}
if (\count($this->middlewares) !== 0) {
if (count($this->middlewares) !== 0) {
$values['middleware'] = $this->middlewares;
}
@@ -462,7 +481,7 @@ abstract class Route implements IRoute
/* Sort the parameters after the user-defined param order, if any */
$parameters = [];
if (\count($this->originalParameters) !== 0) {
if (count($this->originalParameters) !== 0) {
$parameters = $this->originalParameters;
}
@@ -477,14 +496,6 @@ abstract class Route implements IRoute
*/
public function setParameters(array $parameters): IRoute
{
/*
* If this is the first time setting parameters we store them so we
* later can organize the array, in case somebody tried to sort the array.
*/
if (\count($parameters) !== 0 && \count($this->originalParameters) === 0) {
$this->originalParameters = $parameters;
}
$this->parameters = array_merge($this->parameters, $parameters);
return $this;
@@ -571,6 +582,7 @@ abstract class Route implements IRoute
public function setFilterEmptyParams(bool $enabled): IRoute
{
$this->filterEmptyParams = $enabled;
return $this;
}
@@ -35,7 +35,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$method = substr($name, strrpos($name, '.') + 1);
$newName = substr($name, 0, strrpos($name, '.'));
if (\in_array($method, $this->names, true) === true && strtolower($this->name) === strtolower($newName)) {
if (in_array($method, $this->names, true) === true && strtolower($this->name) === strtolower($newName)) {
return true;
}
}
@@ -67,7 +67,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
foreach (Request::$requestTypes as $requestType) {
if (stripos($method, $requestType) === 0) {
$method = (string)substr($method, \strlen($requestType));
$method = (string)substr($method, strlen($requestType));
break;
}
}
@@ -77,7 +77,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$group = $this->getGroup();
if ($group !== null && \count($group->getDomains()) !== 0) {
if ($group !== null && count($group->getDomains()) !== 0) {
$url .= '//' . $group->getDomains()[0];
}
@@ -102,12 +102,12 @@ class RouteController extends LoadableRoute implements IControllerRoute
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$path = explode('/', $strippedUrl);
if (\count($path) !== 0) {
if (count($path) !== 0) {
$method = (isset($path[0]) === false || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $request->getMethod() . ucfirst($method);
$this->parameters = \array_slice($path, 1);
$this->parameters = array_slice($path, 1);
// Set callback
$this->setCallback([$this->controller, $this->method]);
+30 -8
View File
@@ -7,6 +7,7 @@ use Pecee\SimpleRouter\Handlers\IExceptionHandler;
class RouteGroup extends Route implements IGroupRoute
{
protected $urlRegex = '/^%s\/?/u';
protected $prefix;
protected $name;
protected $domains = [];
@@ -20,7 +21,7 @@ class RouteGroup extends Route implements IGroupRoute
*/
public function matchDomain(Request $request): bool
{
if ($this->domains === null || \count($this->domains) === 0) {
if ($this->domains === null || count($this->domains) === 0) {
return true;
}
@@ -33,7 +34,7 @@ class RouteGroup extends Route implements IGroupRoute
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
if ($parameters !== null && \count($parameters) !== 0) {
if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters;
return true;
}
@@ -55,16 +56,27 @@ class RouteGroup extends Route implements IGroupRoute
return false;
}
// Parse parameter
if ($this->prefix !== null) {
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->prefix, $url);
$prefix = $this->prefix;
/* If no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) {
return false;
}
/* Set the parameters */
$this->setParameters($parameters);
}
$parsedPrefix = $this->prefix;
foreach ($this->getParameters() as $parameter => $value) {
$prefix = str_ireplace('{' . $parameter . '}', $value, $prefix);
$parsedPrefix = str_ireplace('{' . $parameter . '}', $value, $parsedPrefix);
}
/* Skip if prefix doesn't match */
if ($this->prefix !== null && stripos($url, $prefix) === false) {
if ($this->prefix !== null && stripos($url, $parsedPrefix) === false) {
return false;
}
@@ -141,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.
*
@@ -160,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);
}
@@ -204,7 +226,7 @@ class RouteGroup extends Route implements IGroupRoute
$values['as'] = $this->name;
}
if (\count($this->parameters) !== 0) {
if (count($this->parameters) !== 0) {
$values['parameters'] = $this->parameters;
}
@@ -1,47 +1,7 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
class RoutePartialGroup extends RouteGroup implements IPartialGroupRoute
{
/**
* RoutePartialGroup constructor.
*/
public function __construct()
{
$this->urlRegex = '/^%s\/?/u';
}
/**
* Method called to check if route matches
*
* @param string $url
* @param Request $request
* @return bool
*/
public function matchRoute(string $url, Request $request): bool
{
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
}
if ($this->prefix !== null) {
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->prefix, $url);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) {
return false;
}
/* Set the parameters */
$this->setParameters($parameters);
}
return $this->matchDomain($request);
}
}
@@ -120,7 +120,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
}
// Update
if ($id !== null && \in_array($method, [Request::REQUEST_TYPE_PATCH, Request::REQUEST_TYPE_PUT], true) === true) {
if ($id !== null && in_array($method, [Request::REQUEST_TYPE_PATCH, Request::REQUEST_TYPE_PUT], true) === true) {
return $this->call($this->methodNames['update']);
}
+26 -25
View File
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter;
use Exception;
use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Exceptions\MalformedUrlException;
use Pecee\Http\Middleware\BaseCsrfVerifier;
@@ -160,6 +161,7 @@ class Router
{
$this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [
'route' => $route,
'isSubRoute' => $this->isProcessingRoute,
]);
/*
@@ -183,12 +185,11 @@ class Router
*/
protected function renderAndProcess(IRoute $route): void
{
$this->isProcessingRoute = true;
$route->renderRoute($this->request, $this);
$this->isProcessingRoute = false;
if (\count($this->routeStack) !== 0) {
if (count($this->routeStack) !== 0) {
/* Pop and grab the routes added when executing group callback earlier */
$stack = $this->routeStack;
@@ -225,7 +226,7 @@ class Router
/* @var $route IRoute */
foreach ($routes as $route) {
$this->debug('Processing route "%s"', \get_class($route));
$this->debug('Processing route "%s"', get_class($route));
if ($group !== null) {
/* Add the parent group */
@@ -238,7 +239,7 @@ class Router
if ($route->matchRoute($url, $this->request) === true) {
/* Add exception handlers */
if (\count($route->getExceptionHandlers()) !== 0) {
if (count($route->getExceptionHandlers()) !== 0) {
/** @noinspection AdditionOperationOnArraysInspection */
$exceptionHandlers += $route->getExceptionHandlers();
}
@@ -292,7 +293,7 @@ class Router
/* @var $manager IRouterBootManager */
foreach ($this->bootManagers as $manager) {
$className = \get_class($manager);
$className = get_class($manager);
$this->debug('Rendering bootmanager "%s"', $className);
$this->fireEvents(EventHandler::EVENT_RENDER_BOOTMANAGER, [
'bootmanagers' => $this->bootManagers,
@@ -315,7 +316,7 @@ class Router
* @throws NotFoundHttpException
* @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
public function start(): ?string
{
@@ -351,7 +352,7 @@ class Router
*
* @return string|null
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
public function routeRequest(): ?string
{
@@ -365,7 +366,7 @@ class Router
/* @var $route ILoadableRoute */
foreach ($this->processedRoutes as $key => $route) {
$this->debug('Matching route "%s"', \get_class($route));
$this->debug('Matching route "%s"', get_class($route));
/* If the route matches */
if ($route->matchRoute($url, $this->request) === true) {
@@ -375,7 +376,7 @@ class Router
]);
/* Check if request method matches */
if (\count($route->getRequestMethods()) !== 0 && \in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) {
if (count($route->getRequestMethods()) !== 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) {
$this->debug('Method "%s" not allowed', $this->request->getMethod());
// Only set method not allowed is not already set
@@ -425,7 +426,7 @@ class Router
}
}
} catch (\Exception $e) {
} catch (Exception $e) {
$this->handleException($e);
}
@@ -434,7 +435,7 @@ class Router
$this->handleException(new NotFoundHttpException($message, 403));
}
if (\count($this->request->getLoadedRoutes()) === 0) {
if (count($this->request->getLoadedRoutes()) === 0) {
$rewriteUrl = $this->request->getRewriteUrl();
@@ -459,7 +460,7 @@ class Router
* @param string $url
* @return string|null
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
protected function handleRouteRewrite(string $key, string $url): ?string
{
@@ -493,14 +494,14 @@ class Router
}
/**
* @param \Exception $e
* @param Exception $e
* @return string|null
* @throws \Exception
* @throws Exception
* @throws HttpException
*/
protected function handleException(\Exception $e): ?string
protected function handleException(Exception $e): ?string
{
$this->debug('Starting exception handling for "%s"', \get_class($e));
$this->debug('Starting exception handling for "%s"', get_class($e));
$this->fireEvents(EventHandler::EVENT_LOAD_EXCEPTIONS, [
'exception' => $e,
@@ -510,7 +511,7 @@ class Router
/* @var $handler IExceptionHandler */
foreach ($this->exceptionHandlers as $key => $handler) {
if (\is_object($handler) === false) {
if (is_object($handler) === false) {
$handler = new $handler();
}
@@ -520,7 +521,7 @@ class Router
'exceptionHandlers' => $this->exceptionHandlers,
]);
$this->debug('Processing exception-handler "%s"', \get_class($handler));
$this->debug('Processing exception-handler "%s"', get_class($handler));
if (($handler instanceof IExceptionHandler) === false) {
throw new HttpException('Exception handler must implement the IExceptionHandler interface.', 500);
@@ -549,7 +550,7 @@ class Router
return $this->routeRequest();
}
} catch (\Exception $e) {
} catch (Exception $e) {
}
@@ -592,7 +593,7 @@ class Router
}
/* Using @ is most definitely a controller@method or alias@method */
if (\is_string($name) === true && strpos($name, '@') !== false) {
if (is_string($name) === true && strpos($name, '@') !== false) {
[$controller, $method] = array_map('strtolower', explode('@', $name));
if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) {
@@ -604,7 +605,7 @@ class Router
/* Check if callback matches (if it's not a function) */
$callback = $route->getCallback();
if (\is_string($name) === true && \is_string($callback) === true && \is_callable($callback) === false && strpos($name, '@') !== false && strpos($callback, '@') !== false) {
if (is_string($name) === true && is_string($callback) === true && is_callable($callback) === false && strpos($name, '@') !== false && strpos($callback, '@') !== false) {
/* Check if the entire callback is matching */
if (strpos($callback, $name) === 0 || strtolower($callback) === strtolower($name)) {
@@ -647,7 +648,7 @@ class Router
*/
public function getUrl(?string $name = null, $parameters = null, ?array $getParams = null): Url
{
$this->debug('Finding url', \func_get_args());
$this->debug('Finding url', func_get_args());
$this->fireEvents(EventHandler::EVENT_GET_URL, [
'name' => $name,
@@ -655,7 +656,7 @@ class Router
'getParams' => $getParams,
]);
if ($getParams !== null && \is_array($getParams) === false) {
if ($getParams !== null && is_array($getParams) === false) {
throw new InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
@@ -696,7 +697,7 @@ class Router
}
/* Using @ is most definitely a controller@method or alias@method */
if (\is_string($name) === true && strpos($name, '@') !== false) {
if (is_string($name) === true && strpos($name, '@') !== false) {
[$controller, $method] = explode('@', $name);
/* Loop through all the routes to see if we can find a match */
@@ -876,7 +877,7 @@ class Router
*/
protected function fireEvents(string $name, array $arguments = []): void
{
if (\count($this->eventHandlers) === 0) {
if (count($this->eventHandlers) === 0) {
return;
}
+27 -25
View File
@@ -4,12 +4,14 @@
* 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;
use Closure;
use Exception;
use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Request;
@@ -54,7 +56,7 @@ class SimpleRouter
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
* @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
public static function start(): void
{
@@ -79,18 +81,18 @@ class SimpleRouter
ob_start();
static::router()->setDebugEnabled(true)->start();
$routerOutput = ob_get_clean();
} catch (\Exception $e) {
} catch (Exception $e) {
}
// Try to parse library version
$composerFile = \dirname(__DIR__, 3) . '/composer.lock';
$composerFile = dirname(__DIR__, 3) . '/composer.lock';
$version = false;
if (is_file($composerFile) === true) {
$composerInfo = json_decode(file_get_contents($composerFile), true);
if (isset($composerInfo['packages']) === true && \is_array($composerInfo['packages']) === true) {
if (isset($composerInfo['packages']) === true && is_array($composerInfo['packages']) === true) {
foreach ($composerInfo['packages'] as $package) {
if (isset($package['name']) === true && strtolower($package['name']) === 'pecee/simple-router') {
$version = $package['version'];
@@ -180,7 +182,7 @@ class SimpleRouter
* Route the given url to your callback on GET request method.
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
*
* @return RouteUrl
@@ -194,7 +196,7 @@ class SimpleRouter
* Route the given url to your callback on POST request method.
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
*/
@@ -207,7 +209,7 @@ class SimpleRouter
* Route the given url to your callback on PUT request method.
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
*/
@@ -220,7 +222,7 @@ class SimpleRouter
* Route the given url to your callback on PATCH request method.
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
*/
@@ -233,7 +235,7 @@ class SimpleRouter
* Route the given url to your callback on OPTIONS request method.
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
*/
@@ -246,7 +248,7 @@ class SimpleRouter
* Route the given url to your callback on DELETE request method.
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
*/
@@ -259,13 +261,13 @@ class SimpleRouter
* Groups allows for encapsulating routes with special settings.
*
* @param array $settings
* @param \Closure $callback
* @param Closure $callback
* @return RouteGroup
* @throws InvalidArgumentException
*/
public static function group(array $settings, \Closure $callback): IGroupRoute
public static function group(array $settings, Closure $callback): IGroupRoute
{
if (\is_callable($callback) === false) {
if (is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
@@ -283,14 +285,14 @@ class SimpleRouter
* parameters and which are only rendered when the url matches.
*
* @param string $url
* @param \Closure $callback
* @param Closure $callback
* @param array $settings
* @return RoutePartialGroup
* @throws InvalidArgumentException
*/
public static function partialGroup(string $url, \Closure $callback, array $settings = []): IPartialGroupRoute
public static function partialGroup(string $url, Closure $callback, array $settings = []): IPartialGroupRoute
{
if (\is_callable($callback) === false) {
if (is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
@@ -309,7 +311,7 @@ class SimpleRouter
* Alias for the form method
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @see SimpleRouter::form
@@ -324,7 +326,7 @@ class SimpleRouter
* Route the given url to your callback on POST and GET request method.
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @see SimpleRouter::form
@@ -342,7 +344,7 @@ class SimpleRouter
*
* @param array $requestMethods
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl|IRoute
*/
@@ -362,7 +364,7 @@ class SimpleRouter
* This type will route the given url to your callback and allow any type of request method
*
* @param string $url
* @param string|array|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl|IRoute
*/
@@ -418,10 +420,10 @@ class SimpleRouter
/**
* Add exception callback handler.
*
* @param \Closure $callback
* @param Closure $callback
* @return CallbackExceptionHandler $callbackHandler
*/
public static function error(\Closure $callback): CallbackExceptionHandler
public static function error(Closure $callback): CallbackExceptionHandler
{
$routes = static::router()->getRoutes();
@@ -458,7 +460,7 @@ class SimpleRouter
{
try {
return static::router()->getUrl($name, $parameters, $getParams);
} catch (\Exception $e) {
} catch (Exception $e) {
return new Url('/');
}
}
@@ -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);
}
}
@@ -7,8 +7,20 @@ class CustomClassLoader implements \Pecee\SimpleRouter\ClassLoader\IClassLoader
return new DummyController();
}
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return object
*/
public function loadClassMethod($class, string $method, array $parameters)
{
return call_user_func_array([$class, $method], ['result' => true]);
}
public function loadClosure(callable $closure, array $parameters)
{
return \call_user_func_array($closure, ['result' => true]);
return call_user_func_array($closure, ['result' => 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);
}
}
@@ -122,7 +122,20 @@ class InputHandlerTest extends \PHPUnit\Framework\TestCase
$_GET = [];
}
public function testFindInput() {
global $_POST;
$_POST['hello'] = 'motto';
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$inputHandler = TestRouter::request()->getInputHandler();
$value = $inputHandler->value('hello', null, \Pecee\Http\Request::$requestTypesPost);
$this->assertEquals($_POST['hello'], $value);
}
public function testFile()
{
@@ -70,4 +70,47 @@ class RouterPartialGroupTest extends \PHPUnit\Framework\TestCase
}
public function testPhp8CallUserFunc() {
TestRouter::router()->reset();
$result = false;
$lang = 'de';
TestRouter::group(['prefix' => '/lang'], function() use(&$result) {
TestRouter::get('/{lang}', function ($lang) use(&$result) {
$result = $lang;
});
});
TestRouter::debug("/lang/$lang");
$this->assertEquals($lang, $result);
// Test partial group
$lang = 'de';
$userId = 22;
$result1 = false;
$result2 = false;
TestRouter::partialGroup(
'/lang/{lang}/',
function ($lang) use(&$result1, &$result2) {
$result1 = $lang;
TestRouter::get('/user/{userId}', function ($userId) use(&$result2) {
$result2 = $userId;
});
});
TestRouter::debug("/lang/$lang/user/$userId");
$this->assertEquals($lang, $result1);
$this->assertEquals($userId, $result2);
}
}
@@ -271,4 +271,81 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::router()->reset();
}
public function testGroupPrefix() {
$result = false;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use(&$result) {
TestRouter::get('/test', function() use(&$result) {
$result = true;
});
});
TestRouter::debug('/lang/da/test');
$this->assertTrue($result);
// Test group prefix sub-route
$result = null;
$expectedResult = 28;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use(&$result) {
TestRouter::get('/horse/{horseType}', function($horseType) use(&$result) {
$result = false;
});
TestRouter::get('/user/{userId}', function($userId) use(&$result) {
$result = $userId;
});
});
TestRouter::debug("/lang/da/user/$expectedResult");
$this->assertEquals($expectedResult, $result);
}
public function testPassParameter() {
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use(&$result) {
TestRouter::get('/test', function($language) use(&$result) {
$result = $language;
});
});
TestRouter::debug("/lang/$expectedLanguage/test");
$this->assertEquals($expectedLanguage, $result);
}
public function testPassParameterDeep() {
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use(&$result) {
TestRouter::group(['prefix' => '/admin'], function($language) use(&$result) {
TestRouter::get('/test', function($language) use(&$result) {
$result = $language;
});
});
});
TestRouter::debug("/lang/$expectedLanguage/admin/test");
$this->assertEquals($expectedLanguage, $result);
}
}