v4-development sync

This commit is contained in:
DeveloperMarius
2021-03-25 13:41:04 +01:00
35 changed files with 831 additions and 389 deletions
-22
View File
@@ -1,22 +0,0 @@
engines:
phpmd:
enabled: true
checks:
Design/TooManyPublicMethods:
enabled: true
Naming/ShortVariable:
enabled: true
CleanCode/StaticAccess:
enabled: true
Controversial/CamelCaseMethodName:
enabled: true
fixme:
enabled: true
duplication:
enabled: true
config:
languages:
- php:
ratings:
paths:
- src/**
+59
View File
@@ -0,0 +1,59 @@
name: CI
on: [push, pull_request]
jobs:
build-test:
runs-on: ${{ matrix.os }}
env:
PHP_EXTENSIONS: json
PHP_INI_VALUES: assert.exception=1, zend.assertions=1
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
php-version:
- 7.1
- 7.4
phpunit-version:
- 7.5.20
dependencies:
- lowest
- highest
name: PHPUnit Tests
steps:
- name: Configure git to avoid issues with line endings
if: matrix.os == 'windows-latest'
run: git config --global core.autocrlf false
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v5, phpunit:${{ matrix.phpunit-versions }}
coverage: xdebug
extensions: ${{ env.PHP_EXTENSIONS }}
ini-values: ${{ env.PHP_INI_VALUES }}
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
restore-keys: |
php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-
- name: Install lowest dependencies with composer
if: matrix.dependencies == 'lowest'
run: composer update --no-ansi --no-interaction --no-progress --prefer-lowest
- name: Install highest dependencies with composer
if: matrix.dependencies == 'highest'
run: composer update --no-ansi --no-interaction --no-progress
- name: Run tests with phpunit
run: composer test
+1 -1
View File
@@ -1,4 +1,4 @@
composer.lock composer.lock
vendor/ vendor/
tests/tmp/*
.idea/ .idea/
.phpunit.result.cache
-13
View File
@@ -1,13 +0,0 @@
build:
tests:
override:
-
command: './vendor/bin/phpunit --coverage-clover=coverage.clover'
coverage:
file: 'coverage.clover'
format: 'clover'
checks:
php:
code_rating: true
duplication: true
-13
View File
@@ -1,13 +0,0 @@
sudo: false
language: php
php:
- 7.1
before_script:
- curl -sS http://getcomposer.org/installer | php
- php composer.phar install --prefer-source --no-interaction
script:
- ./vendor/bin/phpunit
+127 -91
View File
@@ -14,8 +14,7 @@ SimpleRouter::get('/', function() {
### Support the project ### Support the project
If you like simple-router and wish to see the continued development and maintenance of the project, If you like simple-router and wish to see the continued development and maintenance of the project, please consider showing your support by buying me a coffee. Supporters will be listed under the credits section of this documentation.
please consider showing your support by buying me a coffee. Supporters will be listed under the credits section of this documentation.
You can donate any amount of your choice by [clicking here](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NNX4D2RUSALCN). You can donate any amount of your choice by [clicking here](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NNX4D2RUSALCN).
@@ -52,9 +51,6 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Partial groups](#partial-groups) - [Partial groups](#partial-groups)
- [Form Method Spoofing](#form-method-spoofing) - [Form Method Spoofing](#form-method-spoofing)
- [Accessing The Current Route](#accessing-the-current-route) - [Accessing The Current Route](#accessing-the-current-route)
- [Dependency injection](#dependency-injection)
- [Enabling dependency injection](#enabling-dependency-injection)
- [More reading](#more-reading)
- [Other examples](#other-examples) - [Other examples](#other-examples)
- [CSRF-protection](#csrf-protection) - [CSRF-protection](#csrf-protection)
- [Adding CSRF-verifier](#adding-csrf-verifier) - [Adding CSRF-verifier](#adding-csrf-verifier)
@@ -86,10 +82,13 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Registering new event](#registering-new-event) - [Registering new event](#registering-new-event)
- [Custom EventHandlers](#custom-eventhandlers) - [Custom EventHandlers](#custom-eventhandlers)
- [Advanced](#advanced) - [Advanced](#advanced)
- [Disable multiple route rendering](#disable-multiple-route-rendering)
- [Url rewriting](#url-rewriting) - [Url rewriting](#url-rewriting)
- [Changing current route](#changing-current-route) - [Changing current route](#changing-current-route)
- [Bootmanager: loading routes dynamically](#bootmanager-loading-routes-dynamically) - [Bootmanager: loading routes dynamically](#bootmanager-loading-routes-dynamically)
- [Adding routes manually](#adding-routes-manually) - [Adding routes manually](#adding-routes-manually)
- [Custom class-loader](#custom-class-loader)
- [Integrating with php-di](#Integrating-with-php-di)
- [Parameters](#parameters) - [Parameters](#parameters)
- [Extending](#extending) - [Extending](#extending)
- [Help and support](#help-and-support) - [Help and support](#help-and-support)
@@ -251,6 +250,7 @@ To add `favicon.ico` to the IIS ignore-list, add the following line to the `<con
``` ```
You can also make one exception for files with some extensions: You can also make one exception for files with some extensions:
``` ```
<add input="{REQUEST_FILENAME}" pattern="\.ico|\.png|\.css|\.jpg" negate="true" ignoreCase="true" /> <add input="{REQUEST_FILENAME}" pattern="\.ico|\.png|\.css|\.jpg" negate="true" ignoreCase="true" />
``` ```
@@ -258,6 +258,7 @@ You can also make one exception for files with some extensions:
If you are using `$_SERVER['ORIG_PATH_INFO']`, you will get `\index.php\` as part of the returned value. If you are using `$_SERVER['ORIG_PATH_INFO']`, you will get `\index.php\` as part of the returned value.
**Example:** **Example:**
``` ```
/index.php/test/mypage.php /index.php/test/mypage.php
``` ```
@@ -299,8 +300,6 @@ We recommend that you add these helper functions to your project. These will all
To implement the functions below, simply copy the code to a new file and require the file before initializing the router or copy the `helpers.php` we've included in this library. To implement the functions below, simply copy the code to a new file and require the file before initializing the router or copy the `helpers.php` we've included in this library.
```php ```php
<?php
use Pecee\SimpleRouter\SimpleRouter as Router; use Pecee\SimpleRouter\SimpleRouter as Router;
use Pecee\Http\Url; use Pecee\Http\Url;
use Pecee\Http\Response; use Pecee\Http\Response;
@@ -348,7 +347,7 @@ function request(): Request
/** /**
* Get input class * Get input class
* @param string|null $index Parameter index name * @param string|null $index Parameter index name
* @param string|null $defaultValue Default return value * @param string|mixed|null $defaultValue Default return value
* @param array ...$methods Default methods * @param array ...$methods Default methods
* @return \Pecee\Http\Input\InputHandler|array|string|null * @return \Pecee\Http\Input\InputHandler|array|string|null
*/ */
@@ -696,88 +695,6 @@ SimpleRouter::request()->getLoadedRoute();
request()->getLoadedRoute(); request()->getLoadedRoute();
``` ```
## Dependency injection
simple-router supports dependency injection using the [`php-di`](http://php-di.org/) library.
Dependency injection allows the framework to automatically "inject" (load) classes added as parameters. This can simplify your code, as you can avoid creating new instances of objects you are using often in your `Controllers` etc.
Here's a basic example of a controller class using dependency injection:
```php
namespace Demo\Controllers;
class DefaultController {
public function login(User $user): string
{
// ...
}
}
```
The example above will automatically create a new instance of the `User` from the `$user` parameter. This means that the `$user` class contains a new instance of the `User` class and we won't need to create a new instance our self.
**WARNING:** dependency injection can have some negative impact in performance. If you experience any performance issues, we recommend disabling this functionality.
### Enabling dependency injection
Dependency injection is disabled per default to avoid any performance issues.
Before enabling dependency injection, we recommend that you read the [Container configuration](http://php-di.org/doc/container-configuration.html) section of the php-di documentation. This section covers how to configure php-di to different environments and speed-up the performance.
#### Enabling for development environment
The example below should ONLY be used on a development environment.
```php
// Create our new php-di container
$container = (new \DI\ContainerBuilder())
->useAutowiring(true)
->build();
// Add our container to simple-router and enable dependency injection
SimpleRouter::enableDependencyInjection($container);
```
Please check the [More reading](#more-reading) section of the documentation for useful php-di links and tutorials.
#### Enabling for production environment
The example below compiles the injections, which can help speed up performance.
**Note:** You should change the `$cacheDir` to a cache-storage within your project.
```php
// Cache directory
$cacheDir = sys_get_temp_dir('simple-router');
// Create our new php-di container
$container = (new \DI\ContainerBuilder())
->enableCompilation($cacheDir)
->writeProxiesToFile(true, $cacheDir . '/proxies')
->useAutowiring(true)
->build();
// Add our container to simple-router and enable dependency injection
SimpleRouter::enableDependencyInjection($container);
```
Please check the [More reading](#more-reading) section of the documentation for useful php-di links and tutorials.
### More reading
For more information about dependency injection, configuration and settings - we recommend that you check the php-di documentation or some of the useful links we've gathered below.
#### Useful links
- [php-di documentation](http://php-di.org/doc/)
- [Understanding dependency injection](http://php-di.org/doc/understanding-di.html)
- [Best practices guide](http://php-di.org/doc/best-practices.html)
- [Configuring the container](http://php-di.org/doc/container-configuration.html)
- [Definitions](http://php-di.org/doc/definition.html)
## Other examples ## Other examples
You can find many more examples in the `routes.php` example-file below: You can find many more examples in the `routes.php` example-file below:
@@ -1444,6 +1361,12 @@ class DatabaseDebugHandler implements IEventHandler
# Advanced # Advanced
## Disable multiple route rendering
By default the router will try to execute all routes that matches a given url. To stop the router from executing any further routes any method can return a value.
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.
## Url rewriting ## Url rewriting
### Changing current route ### Changing current route
@@ -1495,7 +1418,7 @@ class CustomRouterRules implement IRouterBootManager
// If the current url matches the rewrite url, we use our custom route // If the current url matches the rewrite url, we use our custom route
if($request->getUrl()->getPath() === $url) { if($request->getUrl()->contains($url)) {
$request->setRewriteUrl($rule); $request->setRewriteUrl($rule);
} }
} }
@@ -1543,6 +1466,119 @@ $route->setPrefix('v1');
$router->addRoute($route); $router->addRoute($route);
``` ```
## Custom class loader
You can easily extend simple-router to support custom injection frameworks like php-di by taking advantage of the ability to add your custom class-loader.
Class-loaders must inherit the `IClassLoader` interface.
**Example:**
```php
class MyCustomClassLoader implements IClassLoader
{
/**
* Load class
*
* @param string $class
* @return object
* @throws NotFoundHttpException
*/
public function loadClass(string $class)
{
if (\class_exists($class) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
}
return new $class();
}
/**
* Load closure
*
* @param Callable $closure
* @param array $parameters
* @return mixed
*/
public function loadClosure(Callable $closure, array $parameters)
{
return \call_user_func_array($closure, $parameters);
}
}
```
Next, we need to configure our `routes.php` so the router uses our `MyCustomClassLoader` class for loading classes. This can be done by adding the following line to your `routes.php` file.
```php
SimpleRouter::setCustomClassLoader(new MyCustomClassLoader());
```
### Integrating with php-di
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
class MyCustomClassLoader implements IClassLoader
{
protected $container;
public function __construct()
{
// Create our new php-di container
$container = (new \DI\ContainerBuilder())
->useAutowiring(true)
->build();
}
/**
* Load class
*
* @param string $class
* @return object
* @throws NotFoundHttpException
*/
public function loadClass(string $class)
{
if (class_exists($class) === false) {
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();
}
/**
* Load closure
*
* @param Callable $closure
* @param array $parameters
* @return mixed
*/
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);
}
}
```
## Parameters ## Parameters
This section contains advanced tips & tricks on extending the usage for parameters. This section contains advanced tips & tricks on extending the usage for parameters.
+7 -3
View File
@@ -28,13 +28,17 @@
], ],
"require": { "require": {
"php": ">=7.1", "php": ">=7.1",
"ext-json": "*", "ext-json": "*"
"php-di/php-di": "^6.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^6.0", "phpunit/phpunit": "^7",
"mockery/mockery": "^1" "mockery/mockery": "^1"
}, },
"scripts": {
"test": [
"phpunit tests"
]
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Pecee\\": "src/Pecee/" "Pecee\\": "src/Pecee/"
+1 -1
View File
@@ -47,7 +47,7 @@ function request(): Request
/** /**
* Get input class * Get input class
* @param string|null $index Parameter index name * @param string|null $index Parameter index name
* @param string|null $defaultValue Default return value * @param string|mixed|null $defaultValue Default return value
* @param array ...$methods Default methods * @param array ...$methods Default methods
* @return \Pecee\Http\Input\InputHandler|array|string|null * @return \Pecee\Http\Input\InputHandler|array|string|null
*/ */
+3 -3
View File
@@ -9,15 +9,15 @@
convertNoticesToExceptions="true" convertNoticesToExceptions="true"
convertWarningsToExceptions="true" convertWarningsToExceptions="true"
processIsolation="false" processIsolation="false"
stopOnFailure="false" stopOnFailure="false">
syntaxCheck="false">
<testsuites> <testsuites>
<testsuite name="SimpleRouter Test Suite"> <testsuite name="SimpleRouter Test Suite">
<directory>tests/Pecee/SimpleRouter/</directory> <directory>tests/Pecee/SimpleRouter/</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter> <filter>
<whitelist processUncoveredFilesFromWhitelist="true"> <whitelist addUncoveredFilesFromWhitelist="true"
processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory> <directory suffix=".php">src</directory>
</whitelist> </whitelist>
</filter> </filter>
+1 -1
View File
@@ -15,7 +15,7 @@ interface IInputItem
public function getValue(); public function getValue();
public function setValue(string $value): self; public function setValue($value): self;
public function __toString(): string; public function __toString(): string;
+3 -3
View File
@@ -261,16 +261,16 @@ class InputFile implements IInputItem
return $this->getTmpName(); return $this->getTmpName();
} }
public function getValue(): ?string public function getValue()
{ {
return $this->getFilename(); return $this->getFilename();
} }
/** /**
* @param string $value * @param mixed $value
* @return static * @return static
*/ */
public function setValue(string $value): IInputItem public function setValue($value): IInputItem
{ {
$this->filename = $value; $this->filename = $value;
+38 -12
View File
@@ -92,22 +92,29 @@ class InputHandler
/* Parse get requests */ /* Parse get requests */
if (\count($_FILES) !== 0) { if (\count($_FILES) !== 0) {
$this->originalFile = $_FILES; $this->originalFile = $_FILES;
$this->file = $this->parseFiles(); $this->file = $this->parseFiles($this->originalFile);
} }
} }
/** /**
* @return array * @return array
*/ */
public function parseFiles(): array public function parseFiles(array $files, $parentKey = null): array
{ {
$list = []; $list = [];
foreach ($_FILES as $key => $value) { foreach ($files as $key => $value) {
// Parse multi dept file array
if(isset($value['name']) === false && \is_array($value) === true) {
$list[$key] = $this->parseFiles($value, $key);
continue;
}
// Handle array input // Handle array input
if (\is_array($value['name']) === false) { if (\is_array($value['name']) === false) {
$values['index'] = $key; $values['index'] = $parentKey ?? $key;
try { try {
$list[$key] = InputFile::createFromArray($values + $value); $list[$key] = InputFile::createFromArray($values + $value);
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
@@ -237,11 +244,27 @@ class InputHandler
return $element; return $element;
} }
protected function getValueFromArray(array $array): array
{
$output = [];
/* @var $item InputItem */
foreach ($array as $key => $item) {
if ($item instanceof IInputItem) {
$item = $item->getValue();
}
$output[$key] = \is_array($item) ? $this->getValueFromArray($item) : $item;
}
return $output;
}
/** /**
* Get input element value matching index * Get input element value matching index
* *
* @param string $index * @param string $index
* @param string|object|null $defaultValue * @param string|mixed|null $defaultValue
* @param array ...$methods * @param array ...$methods
* @return string|array * @return string|array
*/ */
@@ -249,18 +272,18 @@ class InputHandler
{ {
$input = $this->find($index, ...$methods); $input = $this->find($index, ...$methods);
if ($input instanceof IInputItem) {
$input = $input->getValue();
}
/* Handle collection */ /* Handle collection */
if (\is_array($input) === true) { if (\is_array($input) === true) {
$output = []; $output = $this->getValueFromArray($input);
/* @var $item InputItem */
foreach ($input as $item) {
$output[] = \is_array($item) ? $item : $item->getValue();
}
return (\count($output) === 0) ? $defaultValue : $output; return (\count($output) === 0) ? $defaultValue : $output;
} }
return ($input === null || (\is_string($input->getValue()) && trim($input->getValue()) === '')) ? $defaultValue : $input->getValue(); return ($input === null || (\is_string($input) && trim($input) === '')) ? $defaultValue : $input;
} }
/** /**
@@ -319,7 +342,7 @@ class InputHandler
public function all(array $filter = []): array public function all(array $filter = []): array
{ {
$output = $this->originalParams + $this->originalPost + $this->originalFile; $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) { foreach ($filter as $filterKey) {
if (array_key_exists($filterKey, $output) === false) { if (array_key_exists($filterKey, $output) === false) {
@@ -380,6 +403,7 @@ class InputHandler
public function setOriginalPost(array $post): self public function setOriginalPost(array $post): self
{ {
$this->originalPost = $post; $this->originalPost = $post;
return $this; return $this;
} }
@@ -400,6 +424,7 @@ class InputHandler
public function setOriginalParams(array $params): self public function setOriginalParams(array $params): self
{ {
$this->originalParams = $params; $this->originalParams = $params;
return $this; return $this;
} }
@@ -420,6 +445,7 @@ class InputHandler
public function setOriginalFile(array $file): self public function setOriginalFile(array $file): self
{ {
$this->originalFile = $file; $this->originalFile = $file;
return $this; return $this;
} }
+2 -2
View File
@@ -62,10 +62,10 @@ class InputItem implements IInputItem, \IteratorAggregate
/** /**
* Set input value * Set input value
* @param string $value * @param mixed $value
* @return static * @return static
*/ */
public function setValue(string $value): IInputItem public function setValue($value): IInputItem
{ {
$this->value = $value; $this->value = $value;
+58 -5
View File
@@ -19,6 +19,10 @@ class Request
public const REQUEST_TYPE_DELETE = 'delete'; public const REQUEST_TYPE_DELETE = 'delete';
public const REQUEST_TYPE_HEAD = 'head'; public const REQUEST_TYPE_HEAD = 'head';
public const CONTENT_TYPE_JSON = 'application/json';
public const CONTENT_TYPE_FORM_DATA = 'multipart/form-data';
public const CONTENT_TYPE_X_FORM_ENCODED = 'application/x-www-form-urlencoded';
/** /**
* All request-types * All request-types
* @var string[] * @var string[]
@@ -57,6 +61,12 @@ class Request
*/ */
protected $headers = []; protected $headers = [];
/**
* Request ContentType
* @var string
*/
protected $contentType;
/** /**
* Request host * Request host
* @var string * @var string
@@ -117,7 +127,9 @@ class Request
$this->setHost($this->getHeader('http-host')); $this->setHost($this->getHeader('http-host'));
// Check if special IIS header exist, otherwise use default. // Check if special IIS header exist, otherwise use default.
$this->setUrl(new Url($this->getHeader('unencoded-url', $this->getHeader('request-uri')))); $this->setUrl(new Url($this->getFirstHeader(['unencoded-url', 'request-uri',])));
$this->setContentType(strtolower($this->getHeader('content-type')));
$this->method = strtolower($this->getHeader('request-method')); $this->method = strtolower($this->getHeader('request-method'));
$this->inputHandler = new InputHandler($this); $this->inputHandler = new InputHandler($this);
@@ -282,6 +294,50 @@ class Request
return $header ?? $defaultValue; return $header ?? $defaultValue;
} }
/**
* Will try to find first header from list of headers.
*
* @param array $headers
* @param null $defaultValue
* @return mixed|null
*/
public function getFirstHeader(array $headers, $defaultValue = null)
{
foreach($headers as $header) {
$header = $this->getHeader($header);
if($header !== null) {
return $header;
}
}
return $defaultValue;
}
/**
* Get request content-type
* @return string|null
*/
public function getContentType(): ?string
{
return $this->contentType;
}
/**
* Set request content-type
* @param string $contentType
* @return $this
*/
protected function setContentType(string $contentType): self
{
if(strpos($contentType, ';') > 0) {
$this->contentType = substr($contentType, 0, strpos($contentType, ';'));
} else {
$this->contentType = $contentType;
}
return $this;
}
/** /**
* Get input class * Get input class
* @return InputHandler * @return InputHandler
@@ -298,7 +354,7 @@ class Request
* *
* @return bool * @return bool
*/ */
public function isFormatAccepted($format): bool public function isFormatAccepted(string $format): bool
{ {
return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) !== false); return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) !== false);
} }
@@ -438,7 +494,6 @@ class Request
public function setLoadedRoutes(array $routes): self public function setLoadedRoutes(array $routes): self
{ {
$this->loadedRoutes = $routes; $this->loadedRoutes = $routes;
return $this; return $this;
} }
@@ -451,7 +506,6 @@ class Request
public function addLoadedRoute(ILoadableRoute $route): self public function addLoadedRoute(ILoadableRoute $route): self
{ {
$this->loadedRoutes[] = $route; $this->loadedRoutes[] = $route;
return $this; return $this;
} }
@@ -474,7 +528,6 @@ class Request
public function setHasPendingRewrite(bool $boolean): self public function setHasPendingRewrite(bool $boolean): self
{ {
$this->hasPendingRewrite = $boolean; $this->hasPendingRewrite = $boolean;
return $this; return $this;
} }
+1 -1
View File
@@ -371,7 +371,7 @@ class Url implements \JsonSerializable
*/ */
public function getParam(string $name, ?string $defaultValue = null): ?string public function getParam(string $name, ?string $defaultValue = null): ?string
{ {
return isset($this->getParams()[$name]) ?? $defaultValue; return (isset($this->getParams()[$name]) === true) ? $this->getParams()[$name] : $defaultValue;
} }
/** /**
@@ -2,44 +2,21 @@
namespace Pecee\SimpleRouter\ClassLoader; namespace Pecee\SimpleRouter\ClassLoader;
use DI\Container; use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
class ClassLoader implements IClassLoader class ClassLoader implements IClassLoader
{ {
/**
* Dependency injection enabled
* @var bool
*/
protected $useDependencyInjection = false;
/**
* @var Container|null
*/
protected $container;
/** /**
* Load class * Load class
* *
* @param string $class * @param string $class
* @return mixed * @return object
* @throws NotFoundHttpException * @throws ClassNotFoundHttpException
*/ */
public function loadClass(string $class) public function loadClass(string $class)
{ {
if (class_exists($class) === false) { if (class_exists($class) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404); throw new ClassNotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404, null, $class);
}
if ($this->useDependencyInjection === true) {
$container = $this->getContainer();
if ($container !== null) {
try {
return $container->get($class);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
} }
return new $class(); return new $class();
@@ -51,68 +28,10 @@ class ClassLoader implements IClassLoader
* @param Callable $closure * @param Callable $closure
* @param array $parameters * @param array $parameters
* @return mixed * @return mixed
* @throws NotFoundHttpException
*/ */
public function loadClosure(Callable $closure, array $parameters) public function loadClosure(Callable $closure, array $parameters)
{ {
if ($this->useDependencyInjection === true) {
$container = $this->getContainer();
if ($container !== null) {
try {
return $container->call($closure, $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
}
return \call_user_func_array($closure, $parameters); return \call_user_func_array($closure, $parameters);
} }
/**
* Get dependency injector container.
*
* @return Container|null
*/
public function getContainer(): ?Container
{
return $this->container;
}
/**
* Set the dependency-injector container.
*
* @param Container $container
* @return ClassLoader
*/
public function setContainer(Container $container): self
{
$this->container = $container;
return $this;
}
/**
* Enable or disable dependency injection.
*
* @param bool $enabled
* @return static
*/
public function useDependencyInjection(bool $enabled): self
{
$this->useDependencyInjection = $enabled;
return $this;
}
/**
* Return true if dependency injection is enabled.
*
* @return bool
*/
public function isDependencyInjectionEnabled(): bool
{
return $this->useDependencyInjection;
}
} }
@@ -5,8 +5,20 @@ namespace Pecee\SimpleRouter\ClassLoader;
interface IClassLoader interface IClassLoader
{ {
/**
* Called when loading class
* @param string $class
* @return object
*/
public function loadClass(string $class); public function loadClass(string $class);
/**
* Called when loading method
*
* @param callable $closure
* @param array $parameters
* @return mixed
*/
public function loadClosure(Callable $closure, array $parameters); public function loadClosure(Callable $closure, array $parameters);
} }
@@ -0,0 +1,38 @@
<?php
namespace Pecee\SimpleRouter\Exceptions;
use Throwable;
class ClassNotFoundHttpException extends NotFoundHttpException
{
protected $class;
protected $method;
public function __construct($message = "", $code = 0, Throwable $previous = null, string $class, ?string $method = null)
{
parent::__construct($message, $code, $previous);
$this->class = $class;
$this->method = $method;
}
/**
* Get class name
* @return string
*/
public function getClass(): string
{
return $this->class;
}
/**
* Get method
* @return string|null
*/
public function getMethod(): ?string
{
return $this->method;
}
}
@@ -143,7 +143,7 @@ class EventHandler implements IEventHandler
* Get events. * Get events.
* *
* @param string|null $name Filter events by name. * @param string|null $name Filter events by name.
* @param array ...$names Add multiple names... * @param array|string ...$names Add multiple names...
* @return array * @return array
*/ */
public function getEvents(?string $name, ...$names): array public function getEvents(?string $name, ...$names): array
+3 -3
View File
@@ -4,6 +4,7 @@ namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Middleware\IMiddleware; use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request; use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException; use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Router; use Pecee\SimpleRouter\Router;
@@ -77,7 +78,6 @@ abstract class Route implements IRoute
$router->debug('Executing callback'); $router->debug('Executing callback');
/* When the callback is a function */ /* When the callback is a function */
return $router->getClassLoader()->loadClosure($callback, $parameters); return $router->getClassLoader()->loadClosure($callback, $parameters);
} }
@@ -95,7 +95,7 @@ abstract class Route implements IRoute
} }
if (method_exists($class, $method) === false) { if (method_exists($class, $method) === false) {
throw new NotFoundHttpException(sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404); throw new ClassNotFoundHttpException(sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404, null, $className, $method);
} }
$router->debug('Executing callback'); $router->debug('Executing callback');
@@ -250,7 +250,7 @@ abstract class Route implements IRoute
/** /**
* Set callback * Set callback
* *
* @param string|array\Closure $callback * @param string|array|\Closure $callback
* @return static * @return static
*/ */
public function setCallback($callback): IRoute public function setCallback($callback): IRoute
+51 -15
View File
@@ -110,6 +110,13 @@ class Router
*/ */
protected $classLoader; protected $classLoader;
/**
* When enabled the router will render all routes that matches.
* When disabled the router will stop execution when first route is found.
* @var bool
*/
protected $renderMultipleRoutes = true;
/** /**
* Router constructor. * Router constructor.
*/ */
@@ -262,13 +269,20 @@ class Router
/** /**
* Load routes * Load routes
* @throws NotFoundHttpException
* @return void * @return void
* @throws NotFoundHttpException
*/ */
public function loadRoutes(): void public function loadRoutes(): void
{ {
$this->debug('Loading routes'); $this->debug('Loading routes');
$this->fireEvents(EventHandler::EVENT_LOAD_ROUTES, [
'routes' => $this->routes,
]);
/* Loop through each route-request */
$this->processRoutes($this->routes);
$this->fireEvents(EventHandler::EVENT_BOOT, [ $this->fireEvents(EventHandler::EVENT_BOOT, [
'bootmanagers' => $this->bootManagers, 'bootmanagers' => $this->bootManagers,
]); ]);
@@ -291,13 +305,6 @@ class Router
$this->debug('Finished rendering bootmanager "%s"', $className); $this->debug('Finished rendering bootmanager "%s"', $className);
} }
$this->fireEvents(EventHandler::EVENT_LOAD_ROUTES, [
'routes' => $this->routes,
]);
/* Loop through each route-request */
$this->processRoutes($this->routes);
$this->debug('Finished loading routes'); $this->debug('Finished loading routes');
} }
@@ -306,7 +313,7 @@ class Router
* *
* @return string|null * @return string|null
* @throws NotFoundHttpException * @throws NotFoundHttpException
* @throws TokenMismatchException * @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException
* @throws HttpException * @throws HttpException
* @throws \Exception * @throws \Exception
*/ */
@@ -350,7 +357,7 @@ class Router
{ {
$this->debug('Routing request'); $this->debug('Routing request');
$methodNotAllowed = false; $methodNotAllowed = null;
try { try {
$url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath(); $url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath();
@@ -370,7 +377,12 @@ class Router
/* Check if request method matches */ /* 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()); $this->debug('Method "%s" not allowed', $this->request->getMethod());
// Only set method not allowed is not already set
if ($methodNotAllowed === null) {
$methodNotAllowed = true; $methodNotAllowed = true;
}
continue; continue;
} }
@@ -394,15 +406,22 @@ class Router
'route' => $route, 'route' => $route,
]); ]);
$output = $route->renderRoute($this->request, $this); $routeOutput = $route->renderRoute($this->request, $this);
if ($output !== null) {
return $output; if ($this->renderMultipleRoutes === true) {
if ($routeOutput !== null) {
return $routeOutput;
} }
$output = $this->handleRouteRewrite($key, $url); $output = $this->handleRouteRewrite($key, $url);
if ($output !== null) { if ($output !== null) {
return $output; return $output;
} }
} else {
$output = $this->handleRouteRewrite($key, $url);
return $output ?? $routeOutput;
}
} }
} }
@@ -475,9 +494,9 @@ class Router
/** /**
* @param \Exception $e * @param \Exception $e
* @throws HttpException
* @throws \Exception
* @return string|null * @return string|null
* @throws \Exception
* @throws HttpException
*/ */
protected function handleException(\Exception $e): ?string protected function handleException(\Exception $e): ?string
{ {
@@ -664,6 +683,7 @@ class Router
->setParams($getParams); ->setParams($getParams);
} }
if($name !== null) {
/* We try to find a match on the given name */ /* We try to find a match on the given name */
$route = $this->findRoute($name); $route = $this->findRoute($name);
@@ -673,6 +693,7 @@ class Router
->setPath($route->findUrl($route->getMethod(), $parameters, $name)) ->setPath($route->findUrl($route->getMethod(), $parameters, $name))
->setParams($getParams); ->setParams($getParams);
} }
}
/* Using @ is most definitely a controller@method or alias@method */ /* 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) {
@@ -907,4 +928,19 @@ class Router
return $this->debugList; return $this->debugList;
} }
/**
* Changes the rendering behavior of the router.
* When enabled the router will render all routes that matches.
* When disabled the router will stop rendering at the first route that matches.
*
* @param bool $bool
* @return $this
*/
public function setRenderMultipleRoutes(bool $bool): self
{
$this->renderMultipleRoutes = $bool;
return $this;
}
} }
+33 -29
View File
@@ -10,7 +10,6 @@
namespace Pecee\SimpleRouter; namespace Pecee\SimpleRouter;
use DI\Container;
use Pecee\Exceptions\InvalidArgumentException; use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Middleware\BaseCsrfVerifier; use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Request; use Pecee\Http\Request;
@@ -59,6 +58,11 @@ class SimpleRouter
*/ */
public static function start(): void public static function start(): void
{ {
// Set default namespaces
foreach (static::router()->getRoutes() as $route) {
static::addDefaultNamespace($route);
}
echo static::router()->start(); echo static::router()->start();
} }
@@ -307,8 +311,8 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|\Closure $callback * @param string|array|\Closure $callback
* @param array|null $settings * @param array|null $settings
* @see SimpleRouter::form
* @return RouteUrl * @return RouteUrl
* @see SimpleRouter::form
*/ */
public static function basic(string $url, $callback, array $settings = null): IRoute public static function basic(string $url, $callback, array $settings = null): IRoute
{ {
@@ -322,14 +326,14 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|\Closure $callback * @param string|array|\Closure $callback
* @param array|null $settings * @param array|null $settings
* @see SimpleRouter::form
* @return RouteUrl * @return RouteUrl
* @see SimpleRouter::form
*/ */
public static function form(string $url, $callback, array $settings = null): IRoute public static function form(string $url, $callback, array $settings = null): IRoute
{ {
return static::match([ return static::match([
Request::REQUEST_TYPE_GET, Request::REQUEST_TYPE_GET,
Request::REQUEST_TYPE_POST Request::REQUEST_TYPE_POST,
], $url, $callback, $settings); ], $url, $callback, $settings);
} }
@@ -346,7 +350,6 @@ class SimpleRouter
{ {
$route = new RouteUrl($url, $callback); $route = new RouteUrl($url, $callback);
$route->setRequestMethods($requestMethods); $route->setRequestMethods($requestMethods);
$route = static::addDefaultNamespace($route);
if ($settings !== null) { if ($settings !== null) {
$route->setSettings($settings); $route->setSettings($settings);
@@ -366,7 +369,6 @@ class SimpleRouter
public static function all(string $url, $callback, array $settings = null) public static function all(string $url, $callback, array $settings = null)
{ {
$route = new RouteUrl($url, $callback); $route = new RouteUrl($url, $callback);
$route = static::addDefaultNamespace($route);
if ($settings !== null) { if ($settings !== null) {
$route->setSettings($settings); $route->setSettings($settings);
@@ -386,7 +388,6 @@ class SimpleRouter
public static function controller(string $url, string $controller, array $settings = null) public static function controller(string $url, string $controller, array $settings = null)
{ {
$route = new RouteController($url, $controller); $route = new RouteController($url, $controller);
$route = static::addDefaultNamespace($route);
if ($settings !== null) { if ($settings !== null) {
$route->setSettings($settings); $route->setSettings($settings);
@@ -406,7 +407,6 @@ class SimpleRouter
public static function resource(string $url, string $controller, array $settings = null) public static function resource(string $url, string $controller, array $settings = null)
{ {
$route = new RouteResource($url, $controller); $route = new RouteResource($url, $controller);
$route = static::addDefaultNamespace($route);
if ($settings !== null) { if ($settings !== null) {
$route->setSettings($settings); $route->setSettings($settings);
@@ -511,39 +511,43 @@ class SimpleRouter
{ {
if (static::$defaultNamespace !== null) { if (static::$defaultNamespace !== null) {
$callback = $route->getCallback(); $ns = static::$defaultNamespace;
$namespace = $route->getNamespace();
/* Only add default namespace on relative callbacks */ if ($namespace !== null) {
if ($callback === null || (\is_string($callback) === true && $callback[0] !== '\\')) { // Don't overwrite namespaces that starts with \
if ($namespace[0] !== '\\') {
$namespace = static::$defaultNamespace; $ns .= '\\' . $namespace;
} else {
$currentNamespace = $route->getNamespace(); $ns = $namespace;
}
if ($currentNamespace !== null) {
$namespace .= '\\' . $currentNamespace;
} }
$route->setDefaultNamespace($namespace); $route->setNamespace($ns);
}
} }
return $route; return $route;
} }
/** /**
* Enable or disable dependency injection * Changes the rendering behavior of the router.
* When enabled the router will render all routes that matches.
* When disabled the router will stop rendering at the first route that matches.
* *
* @param Container $container * @param bool $bool
* @return IClassLoader
*/ */
public static function enableDependencyInjection(Container $container): IClassLoader public static function enableMultiRouteRendering(bool $bool): void
{ {
return static::router() static::router()->setRenderMultipleRoutes($bool);
->getClassLoader() }
->useDependencyInjection(true)
->setContainer($container); /**
* Set custom class-loader class used.
* @param IClassLoader $classLoader
*/
public static function setCustomClassLoader(IClassLoader $classLoader): void
{
static::router()->setClassLoader($classLoader);
} }
/** /**
@@ -0,0 +1,49 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
require_once 'Dummy/Managers/TestBootManager.php';
require_once 'Dummy/Managers/FindUrlBootManager.php';
class BootManagerTest extends \PHPUnit\Framework\TestCase
{
public function testBootManagerRoutes()
{
$result = false;
TestRouter::get('/', function () use (&$result) {
$result = true;
});
TestRouter::get('/about', 'DummyController@method2');
TestRouter::get('/contact', 'DummyController@method3');
// Add boot-manager
TestRouter::addBootManager(new TestBootManager([
'/con' => '/about',
'/contact' => '/',
]));
TestRouter::debug('/contact');
$this->assertTrue($result);
}
public function testFindUrlFromBootManager()
{
TestRouter::get('/', 'DummyController@method1');
TestRouter::get('/about', 'DummyController@method2')->name('about');
TestRouter::get('/contact', 'DummyController@method3')->name('contact');
$result = false;
// Add boot-manager
TestRouter::addBootManager(new FindUrlBootManager($result));
TestRouter::debug('/');
$this->assertTrue($result);
}
}
@@ -0,0 +1,30 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/ClassLoader/CustomClassLoader.php';
class ClassLoaderTest extends \PHPUnit\Framework\TestCase
{
public function testCustomClassLoader()
{
$result = false;
TestRouter::setCustomClassLoader(new CustomClassLoader());
TestRouter::get('/', 'NonExistingClass@method3');
TestRouter::get('/test-closure', function($status) use(&$result) {
$result = $status;
});
$classLoaderClass = TestRouter::debugOutput('/', 'get', false);
TestRouter::debugOutput('/test-closure');
$this->assertEquals('method3', $classLoaderClass);
$this->assertTrue($result);
TestRouter::router()->reset();
}
}
@@ -1,53 +0,0 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
class DependencyInjectionTest extends \PHPUnit\Framework\TestCase
{
public function testDependencyInjectionDevelopment()
{
$builder = new \DI\ContainerBuilder();
$container = $builder
->useAutowiring(true)
->ignorePhpDocErrors(true)
->build();
TestRouter::enableDependencyInjection($container);
$className = null;
TestRouter::get('/', function (DummyMiddleware $url) use (&$className) {
$className = \get_class($url);
});
TestRouter::debug('/');
$this->assertEquals(DummyMiddleware::class, $className);
}
public function testDependencyInjectionProduction()
{
$cacheDir = dirname(__DIR__, 2) . '/tmp';
$builder = new \DI\ContainerBuilder();
$builder
->enableCompilation($cacheDir)
->writeProxiesToFile(true, $cacheDir . '/proxies')
->ignorePhpDocErrors(true)
->useAutowiring(true);
$container = $builder->build();
TestRouter::enableDependencyInjection($container);
$className = null;
TestRouter::get('/', function (DummyMiddleware $url) use (&$className) {
$className = \get_class($url);
});
TestRouter::debug('/');
$this->assertEquals(DummyMiddleware::class, $className);
}
}
@@ -0,0 +1,14 @@
<?php
class CustomClassLoader implements \Pecee\SimpleRouter\ClassLoader\IClassLoader
{
public function loadClass(string $class)
{
return new DummyController();
}
public function loadClosure(callable $closure, array $parameters)
{
return \call_user_func_array($closure, ['result' => true]);
}
}
@@ -16,6 +16,11 @@ class DummyController
public function method2() public function method2()
{ {
}
public function method3()
{
return 'method3';
} }
public function param($params = null) public function param($params = null)
@@ -0,0 +1,26 @@
<?php
class FindUrlBootManager implements \Pecee\SimpleRouter\IRouterBootManager
{
protected $result;
public function __construct(&$result)
{
$this->result = &$result;
}
/**
* Called when router loads it's routes
*
* @param \Pecee\SimpleRouter\Router $router
* @param \Pecee\Http\Request $request
*/
public function boot(\Pecee\SimpleRouter\Router $router, \Pecee\Http\Request $request): void
{
$contact = $router->findRoute('contact');
if($contact !== null) {
$this->result = true;
}
}
}
@@ -3,13 +3,11 @@
class TestBootManager implements \Pecee\SimpleRouter\IRouterBootManager class TestBootManager implements \Pecee\SimpleRouter\IRouterBootManager
{ {
protected $routes; protected $rewrite;
protected $aliasUrl;
public function __construct(array $routes, string $aliasUrl) public function __construct(array $rewrite)
{ {
$this->routes = $routes; $this->rewrite = $rewrite;
$this->aliasUrl = $aliasUrl;
} }
/** /**
@@ -20,11 +18,11 @@ class TestBootManager implements \Pecee\SimpleRouter\IRouterBootManager
*/ */
public function boot(\Pecee\SimpleRouter\Router $router, \Pecee\Http\Request $request): void public function boot(\Pecee\SimpleRouter\Router $router, \Pecee\Http\Request $request): void
{ {
foreach ($this->routes as $url) { foreach ($this->rewrite as $url => $rewrite) {
// If the current url matches the rewrite url, we use our custom route // If the current url matches the rewrite url, we use our custom route
if ($request->getUrl()->contains($url) === true) { if ($request->getUrl()->contains($url) === true) {
$request->setRewriteUrl($this->aliasUrl); $request->setRewriteUrl($rewrite);
} }
} }
@@ -50,8 +50,8 @@ class EventHandlerTest extends \PHPUnit\Framework\TestCase
// Add boot-manager // Add boot-manager
TestRouter::addBootManager(new TestBootManager([ TestRouter::addBootManager(new TestBootManager([
'/test', '/test' => '/',
], '/')); ]));
// Start router // Start router
TestRouter::debug('/non-existing'); TestRouter::debug('/non-existing');
@@ -61,7 +61,6 @@ class EventHandlerTest extends \PHPUnit\Framework\TestCase
public function testAllEvent() public function testAllEvent()
{ {
$status = false; $status = false;
$eventHandler = new EventHandler(); $eventHandler = new EventHandler();
+103 -5
View File
@@ -1,5 +1,7 @@
<?php <?php
use Pecee\Http\Input\InputFile;
require_once 'Dummy/DummyMiddleware.php'; require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php'; require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php'; require_once 'Dummy/Handler/ExceptionHandler.php';
@@ -20,6 +22,13 @@ class InputHandlerTest extends \PHPUnit\Framework\TestCase
'Canon', 'Canon',
]; ];
protected $sodas = [
0 => 'Pepsi',
1 => 'Coca Cola',
2 => 'Harboe',
3 => 'Mountain Dew',
];
protected $day = 'monday'; protected $day = 'monday';
public function testPost() public function testPost()
@@ -29,6 +38,7 @@ class InputHandlerTest extends \PHPUnit\Framework\TestCase
$_POST = [ $_POST = [
'names' => $this->names, 'names' => $this->names,
'day' => $this->day, 'day' => $this->day,
'sodas' => $this->sodas,
]; ];
$router = TestRouter::router(); $router = TestRouter::router();
@@ -51,6 +61,7 @@ class InputHandlerTest extends \PHPUnit\Framework\TestCase
$this->assertNull($handler->find('non-existing')); $this->assertNull($handler->find('non-existing'));
$this->assertNull($handler->value('names', null, 'get')); $this->assertNull($handler->value('names', null, 'get'));
$this->assertNull($handler->find('names', 'get')); $this->assertNull($handler->find('names', 'get'));
$this->assertEquals($this->sodas, $handler->value('sodas'));
$objects = $handler->find('names'); $objects = $handler->find('names');
@@ -111,16 +122,87 @@ class InputHandlerTest extends \PHPUnit\Framework\TestCase
$_GET = []; $_GET = [];
} }
public function testFile() public function testFile()
{ {
// TODO: implement test-file global $_FILES;
$this->assertEquals(true, true);
$testFile = $this->generateFile();
$_FILES = [
'test_input' => $testFile,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$inputHandler = TestRouter::request()->getInputHandler();
$testFileContent = md5(uniqid('test', false));
$file = $inputHandler->file('test_input');
$this->assertInstanceOf(InputFile::class, $file);
$this->assertEquals($testFile['name'], $file->getFilename());
$this->assertEquals($testFile['type'], $file->getType());
$this->assertEquals($testFile['tmp_name'], $file->getTmpName());
$this->assertEquals($testFile['error'], $file->getError());
$this->assertEquals($testFile['size'], $file->getSize());
$this->assertEquals(pathinfo($testFile['name'], PATHINFO_EXTENSION), $file->getExtension());
file_put_contents($testFile['tmp_name'], $testFileContent);
$this->assertEquals($testFileContent, $file->getContents());
// Cleanup
unlink($testFile['tmp_name']);
} }
public function testFiles() public function testFilesArray()
{ {
// TODO: implement test-files global $_FILES;
$this->assertEquals(true, true);
$testFiles = [
$file = $this->generateFile(),
$file = $this->generateFile(),
$file = $this->generateFile(),
$file = $this->generateFile(),
$file = $this->generateFile(),
];
$_FILES = [
'my_files' => $testFiles,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$inputHandler = TestRouter::request()->getInputHandler();
$files = $inputHandler->file('my_files');
$this->assertCount(5, $files);
/* @var $file InputFile */
foreach ($files as $key => $file) {
$testFileContent = md5(uniqid('test', false));
$this->assertInstanceOf(InputFile::class, $file);
$this->assertEquals($testFiles[$key]['name'], $file->getFilename());
$this->assertEquals($testFiles[$key]['type'], $file->getType());
$this->assertEquals($testFiles[$key]['tmp_name'], $file->getTmpName());
$this->assertEquals($testFiles[$key]['error'], $file->getError());
$this->assertEquals($testFiles[$key]['size'], $file->getSize());
$this->assertEquals(pathinfo($testFiles[$key]['name'], PATHINFO_EXTENSION), $file->getExtension());
file_put_contents($testFiles[$key]['tmp_name'], $testFileContent);
$this->assertEquals($testFileContent, $file->getContents());
// Cleanup
unlink($testFiles[$key]['tmp_name']);
}
} }
public function testAll() public function testAll()
@@ -173,4 +255,20 @@ class InputHandlerTest extends \PHPUnit\Framework\TestCase
$_POST = []; $_POST = [];
} }
protected function generateFile()
{
return [
'name' => uniqid('', false) . '.txt',
'type' => 'text/plain',
'tmp_name' => sys_get_temp_dir() . '/phpYfWUiw',
'error' => 0,
'size' => rand(3, 40),
];
}
protected function generateFileContent()
{
return md5(uniqid('', false));
}
} }
+38
View File
@@ -0,0 +1,38 @@
<?php
use Pecee\Http\Input\InputFile;
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
class RequestTest extends \PHPUnit\Framework\TestCase
{
public function testContentTypeParse()
{
global $_SERVER;
$contentType = 'application/x-www-form-urlencoded';
$_SERVER['content_type'] = $contentType;
$router = TestRouter::router();
$router->reset();
$request = $router->getRequest();
$this->assertEquals($contentType, $request->getContentType());
// Test special content-types
$router->reset();
$_SERVER['content_type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
$this->assertEquals($contentType, $request->getContentType());
$router->reset();
}
// TODO: implement more test-cases
}
@@ -6,7 +6,7 @@ require_once 'Dummy/Handler/ExceptionHandlerSecond.php';
require_once 'Dummy/Handler/ExceptionHandlerThird.php'; require_once 'Dummy/Handler/ExceptionHandlerThird.php';
require_once 'Dummy/Middleware/RewriteMiddleware.php'; require_once 'Dummy/Middleware/RewriteMiddleware.php';
class RouteRewriteTest extends \PHPUnit\Framework\TestCase class RouterRewriteTest extends \PHPUnit\Framework\TestCase
{ {
/** /**
@@ -210,4 +210,15 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
$this->assertTrue(true); $this->assertTrue(true);
} }
public function testSameRoutes()
{
TestRouter::get('/recipe', 'DummyController@method1')->name('add');
TestRouter::post('/recipe', 'DummyController@method2')->name('edit');
TestRouter::debugNoReset('/recipe', 'post');
TestRouter::debug('/recipe', 'get');
$this->assertTrue(true);
}
} }
@@ -183,4 +183,92 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('match', $output); $this->assertEquals('match', $output);
} }
public function testRenderMultipleRoutesDisabled()
{
TestRouter::router()->setRenderMultipleRoutes(false);
$result = false;
TestRouter::get('/', function () use (&$result) {
$result = true;
});
TestRouter::get('/', function () use (&$result) {
$result = false;
});
TestRouter::debug('/');
$this->assertTrue($result);
}
public function testRenderMultipleRoutesEnabled()
{
TestRouter::router()->setRenderMultipleRoutes(true);
$result = [];
TestRouter::get('/', function () use (&$result) {
$result[] = 'route1';
});
TestRouter::get('/', function () use (&$result) {
$result[] = 'route2';
});
TestRouter::debug('/');
$this->assertCount(2, $result);
}
public function testDefaultNamespace()
{
TestRouter::setDefaultNamespace('\\Default\\Namespace');
TestRouter::get('/', 'DummyController@method1', ['as' => 'home']);
TestRouter::group([
'namespace' => 'Appended\Namespace',
'prefix' => '/horses',
], function () {
TestRouter::get('/', 'DummyController@method1');
TestRouter::group([
'namespace' => '\\New\\Namespace',
'prefix' => '/race',
], function () {
TestRouter::get('/', 'DummyController@method1');
});
});
// Test appended namespace
$class = null;
try {
TestRouter::debugNoReset('/horses/');
} catch (\Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException $e) {
$class = $e->getClass();
}
$this->assertEquals('\\Default\\Namespace\\Appended\Namespace\\DummyController', $class);
// Test overwritten namespace
$class = null;
try {
TestRouter::debugNoReset('/horses/race');
} catch (\Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException $e) {
$class = $e->getClass();
}
$this->assertEquals('\\New\\Namespace\\DummyController', $class);
TestRouter::router()->reset();
}
} }