Development

- Optimised Input-classes.
- `get` and `getObject` methods on `Input` now supports filtering on multiple method-types when using the `$method` parameter.
- Input classes now know how to parse that stupid nested $_FILES array.
- It's now possible to change method-names on ResourceControllers.
- Removed `getValue` and `setValue` from `InputFile` classes.
- Ensured that request-method are only parsed from $_POST or $_SERVER.
- Fixed minor parameter-issues with subdomain routing.
- Added PHPDocs.
- Added even more unit-tests.
- Many small optimisations tweaks.
This commit is contained in:
Simon Sessingø
2016-11-26 04:30:00 +01:00
parent 68fc6b76c0
commit 6213f2fb75
27 changed files with 685 additions and 417 deletions
+4 -4
View File
@@ -756,7 +756,7 @@ If items is grouped in the html, it will return an array of items.
**Note:** `get` will automatically trim the value and ensure that it's not empty. If it's empty the `$defaultValue` will be returned. **Note:** `get` will automatically trim the value and ensure that it's not empty. If it's empty the `$defaultValue` will be returned.
```php ```php
$value = input()->get($index, $defaultValue); $value = input()->get($index, $defaultValue, $methods);
``` ```
**Return parameter object (matches both GET, POST, FILE):** **Return parameter object (matches both GET, POST, FILE):**
@@ -772,7 +772,7 @@ If items is grouped in the html, it will return an array of items.
**Note:** `getObject` will only return `$defaultValue` if the item doesn't exist. If you want `$defaultValue` to be returned if the item is empty, please use `input()->get()` instead. **Note:** `getObject` will only return `$defaultValue` if the item doesn't exist. If you want `$defaultValue` to be returned if the item is empty, please use `input()->get()` instead.
```php ```php
$object = input()->getObject($index); $object = input()->getObject($index, $defaultValue = null, $methods = null);
``` ```
**Return specific GET parameter (where name is the name of your parameter):** **Return specific GET parameter (where name is the name of your parameter):**
@@ -853,8 +853,8 @@ All object implements the `IInputItem` interface and will always contain these m
Below example requires you to have the helper functions added. Please refer to the helper functions section in the documentation. Below example requires you to have the helper functions added. Please refer to the helper functions section in the documentation.
```php ```php
// Get parameter site_id or default-value 2 /* Get parameter site_id or default-value 2 from either post-value or query-string */
$siteId = input()->get('site_id', 2); $siteId = input()->get('site_id', 2, ['post', 'get']);
``` ```
--- ---
+7 -7
View File
@@ -7,41 +7,41 @@ interface IRestController
/** /**
* @return void * @return void
*/ */
function index(); public function index();
/** /**
* @param mixed $id * @param mixed $id
* @return void * @return void
*/ */
function show($id); public function show($id);
/** /**
* @return void * @return void
*/ */
function store(); public function store();
/** /**
* @return void * @return void
*/ */
function create(); public function create();
/** /**
* View * View
* @param mixed $id * @param mixed $id
* @return void * @return void
*/ */
function edit($id); public function edit($id);
/** /**
* @param mixed $id * @param mixed $id
* @return void * @return void
*/ */
function update($id); public function update($id);
/** /**
* @param mixed $id * @param mixed $id
* @return void * @return void
*/ */
function destroy($id); public function destroy($id);
} }
-4
View File
@@ -12,10 +12,6 @@ interface IInputItem
public function setName($name); public function setName($name);
public function getValue();
public function setValue($value);
public function __toString(); public function __toString();
} }
+122 -56
View File
@@ -32,7 +32,7 @@ class Input
$this->parseInputs(); $this->parseInputs();
} }
protected function parseInputs() public function parseInputs()
{ {
/* Parse get requests */ /* Parse get requests */
if (count($_GET) > 0) { if (count($_GET) > 0) {
@@ -42,7 +42,7 @@ class Input
/* Parse post requests */ /* Parse post requests */
$postVars = $_POST; $postVars = $_POST;
if (in_array($this->request->getMethod(), ['put', 'patch', 'delete']) === true) { if (in_array($this->request->getMethod(), ['put', 'patch', 'delete'], false) === true) {
parse_str(file_get_contents('php://input'), $postVars); parse_str(file_get_contents('php://input'), $postVars);
} }
@@ -51,49 +51,86 @@ class Input
} }
/* Parse get requests */ /* Parse get requests */
$this->parseFiles();
}
if (count($_FILES) > 0) { public function parseFiles()
{
if (count($_FILES) === 0) {
return [];
}
$max = count($_FILES) - 1; $list = [];
$keys = array_keys($_FILES);
for ($i = $max; $i >= 0; $i--) { foreach ($_FILES as $key => $value) {
$key = $keys[$i];
$value = $_FILES[$key];
// Handle array input // Handle array input
if (is_array($value['name']) === false) { if (is_array($value['name']) === false) {
$values['index'] = $key; $values['index'] = $key;
$this->file[$key] = InputFile::createFromArray(array_merge($value, $values)); $list[$key] = InputFile::createFromArray(array_merge($value, $values));
continue; continue;
} }
$subMax = count($value['name']) - 1; $keys = [];
$keys = array_keys($value['name']);
$list = array_merge_recursive($list, [$key => $this->rearrangeFiles($value['name'], $keys, $value)]);
}
return $list;
}
protected function rearrangeFiles(array $values, &$index, $original)
{
$output = []; $output = [];
for ($i = $subMax; $i >= 0; $i--) { $getItem = function ($key, $property = 'name') use ($original, $index) {
$output[$keys[$i]] = InputFile::createFromArray([ $path = $original[$property];
foreach (array_values($index) as $i) {
$path = $path[$i];
}
return $path[$key];
};
foreach ($values as $key => $value) {
if (is_array($getItem($key)) === false) {
$file = InputFile::createFromArray([
'index' => $key, 'index' => $key,
'error' => $value['error'][$keys[$i]], 'error' => $getItem($key, 'error'),
'tmp_name' => $value['tmp_name'][$keys[$i]], 'tmp_name' => $getItem($key, 'tmp_name'),
'type' => $value['type'][$keys[$i]], 'type' => $getItem($key, 'type'),
'size' => $value['size'][$keys[$i]], 'size' => $getItem($key, 'size'),
'name' => $value['name'][$keys[$i]], 'filename' => $getItem($key, 'name'),
]); ]);
$output = array_merge_recursive($output, [$key => $file]);
if (isset($output[$key])) {
$output[$key][] = $file;
} else {
$output[$key] = $file;
} }
$this->file[$key] = $output; continue;
}
}
} }
protected function handleGetPost($array) $index[] = $key;
$output = array_merge_recursive($output, [$key => $this->rearrangeFiles($value, $index, $original)]);
}
return $output;
}
protected function handleGetPost(array $array)
{ {
$tmp = []; $list = [];
$max = count($array) - 1; $max = count($array) - 1;
$keys = array_keys($array); $keys = array_keys($array);
@@ -105,77 +142,110 @@ class Input
// Handle array input // Handle array input
if (is_array($value) === false) { if (is_array($value) === false) {
$tmp[$key] = new InputItem($key, $value); $list[$key] = new InputItem($key, $value);
continue; continue;
} }
$subMax = count($value) - 1; $output = $this->handleGetPost($value);
$keys = array_keys($value);
$output = [];
for ($i = $subMax; $i >= 0; $i--) { $list[$key] = $output;
$output[$keys[$i]] = new InputItem($key, $value[$keys[$i]]);
} }
$tmp[$key] = $output; return $list;
} }
return $tmp; /**
} * Find post-value by index or return default value.
*
public function findPost($index, $default = null) * @param string $index
* @param string|null $defaultValue
* @return InputItem|string
*/
public function findPost($index, $defaultValue = null)
{ {
return isset($this->post[$index]) ? $this->post[$index] : $default; return $this->post[$index] ?? $defaultValue;
} }
public function findFile($index, $default = null) /**
* Find file by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputFile|string
*/
public function findFile($index, $defaultValue = null)
{ {
return isset($this->file[$index]) ? $this->file[$index] : $default; return $this->file[$index] ?? $defaultValue;
} }
public function findGet($index, $default = null) /**
* Find parameter/query-string by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputItem|string
*/
public function findGet($index, $defaultValue = null)
{ {
return isset($this->get[$index]) ? $this->get[$index] : $default; return $this->get[$index] ?? $defaultValue;
} }
public function getObject($index, $default = null, $method = null) /**
* Get input object
*
* @param string $index
* @param string|null $defaultValue
* @param array|string|null $methods
* @return IInputItem|string
*/
public function getObject($index, $defaultValue = null, $methods = null)
{ {
if ($methods !== null && is_string($methods) === true) {
$methods = [$methods];
}
$element = null; $element = null;
if ($method === null || strtolower($method) === 'get') { if ($methods === null || in_array('get', $methods)) {
$element = $this->findGet($index); $element = $this->findGet($index);
} }
if ($element === null && $method === null || strtolower($method) === 'post') { if (($element === null && $methods === null) || ($methods !== null && in_array('post', $methods))) {
$element = $this->findPost($index); $element = $this->findPost($index);
} }
if ($element === null && $method === null || strtolower($method) === 'file') { if (($element === null && $methods === null) || ($methods !== null && in_array('file', $methods))) {
$element = $this->findFile($index); $element = $this->findFile($index);
} }
return ($element === null) ? $default : $element; return ($element !== null) ? $element : $defaultValue;
} }
/** /**
* Get input element value matching index * Get input element value matching index
* *
* @param string $index * @param string $index
* @param string|null $default * @param string|null $defaultValue
* @param string|null $method * @param array|string|null $methods
* @return InputItem|string * @return InputItem|string
*/ */
public function get($index, $default = null, $method = null) public function get($index, $defaultValue = null, $methods = null)
{ {
$input = $this->getObject($index, $default, $method); $input = $this->getObject($index, $defaultValue, $methods);
if ($input instanceof InputItem) { if ($input instanceof InputItem) {
return (trim($input->getValue()) === '') ? $default : $input->getValue(); return (trim($input->getValue()) === '') ? $defaultValue : $input->getValue();
} }
return $input; return $input;
} }
/**
* Check if a input-item exist
*
* @param string $index
* @return bool
*/
public function exists($index) public function exists($index)
{ {
return ($this->getObject($index) !== null); return ($this->getObject($index) !== null);
@@ -194,7 +264,7 @@ class Input
$contents = file_get_contents('php://input'); $contents = file_get_contents('php://input');
if (stripos(trim($contents), '{') === 0) { if (strpos(trim($contents), '{') === 0) {
$output = json_decode($contents, true); $output = json_decode($contents, true);
if ($output === false) { if ($output === false) {
$output = []; $output = [];
@@ -206,11 +276,7 @@ class Input
if ($filter !== null) { if ($filter !== null) {
$output = array_filter($output, function ($key) use ($filter) { $output = array_filter($output, function ($key) use ($filter) {
if (in_array($key, $filter)) { return (in_array($key, $filter) === true);
return true;
}
return false;
}, ARRAY_FILTER_USE_KEY); }, ARRAY_FILTER_USE_KEY);
} }
+100 -34
View File
@@ -4,8 +4,8 @@ namespace Pecee\Http\Input;
class InputFile implements IInputItem class InputFile implements IInputItem
{ {
public $index; public $index;
public $value;
public $name; public $name;
public $filename;
public $size; public $size;
public $type; public $type;
public $error; public $error;
@@ -21,7 +21,9 @@ class InputFile implements IInputItem
/** /**
* Create from array * Create from array
*
* @param array $values * @param array $values
* @throws \InvalidArgumentException
* @return static * @return static
*/ */
public static function createFromArray(array $values) public static function createFromArray(array $values)
@@ -30,12 +32,22 @@ class InputFile implements IInputItem
throw new \InvalidArgumentException('Index key is required'); throw new \InvalidArgumentException('Index key is required');
} }
/* Easy way of ensuring that all indexes-are set and not filling the screen with isset() */
$values = array_merge([
'tmp_name' => null,
'type' => null,
'size' => null,
'name' => null,
'error' => null,
], $values);
$input = new static($values['index']); $input = new static($values['index']);
$input->setError(isset($values['error']) ? $values['error'] : null); $input->setError($values['error'])
$input->setName(isset($values['name']) ? $values['name'] : null); ->setSize($values['size'])
$input->setSize(isset($values['size']) ? $values['size'] : null); ->setType($values['type'])
$input->setType(isset($values['type']) ? $values['type'] : null); ->setTmpName($values['tmp_name'])
$input->setTmpName(isset($values['tmp_name']) ? $values['tmp_name'] : null); ->setFilename($values['name']);
return $input; return $input;
} }
@@ -48,6 +60,11 @@ class InputFile implements IInputItem
return $this->index; return $this->index;
} }
/**
* Set input index
* @param string $index
* @return static $this
*/
public function setIndex($index) public function setIndex($index)
{ {
$this->index = $index; $this->index = $index;
@@ -75,6 +92,10 @@ class InputFile implements IInputItem
return $this; return $this;
} }
/**
* Get mime-type of file
* @return string
*/
public function getMime() public function getMime()
{ {
return $this->getType(); return $this->getType();
@@ -100,12 +121,19 @@ class InputFile implements IInputItem
return $this; return $this;
} }
/**
* Returns extension without "."
*
* @return string
*/
public function getExtension() public function getExtension()
{ {
return pathinfo($this->getName(), PATHINFO_EXTENSION); return pathinfo($this->getName(), PATHINFO_EXTENSION);
} }
/** /**
* Get human friendly name
*
* @return string * @return string
*/ */
public function getName() public function getName()
@@ -113,6 +141,13 @@ class InputFile implements IInputItem
return $this->name; return $this->name;
} }
/**
* Set human friendly name.
* Useful for adding validation etc.
*
* @param string $name
* @return static $this
*/
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = $name;
@@ -120,29 +155,63 @@ class InputFile implements IInputItem
return $this; return $this;
} }
/**
* Set filename
*
* @param string $name
* @return static $this
*/
public function setFilename($name)
{
$this->filename = $name;
return $this;
}
/**
* Get filename
*
* @return string mixed
*/
public function getFilename()
{
return $this->filename;
}
/**
* Move the uploaded temporary file to it's new home
*
* @param string $destination
* @return bool
*/
public function move($destination) public function move($destination)
{ {
return move_uploaded_file($this->tmpName, $destination); return move_uploaded_file($this->tmpName, $destination);
} }
/**
* Get file contents
*
* @return string
*/
public function getContents() public function getContents()
{ {
return file_get_contents($this->tmpName); return file_get_contents($this->tmpName);
} }
public function setValue($value) /**
{ * Return true if an upload error occured.
$this->value = $value; *
* @return bool
return $this; */
}
public function hasError() public function hasError()
{ {
return ($this->getError() !== 0); return ($this->getError() !== 0);
} }
/** /**
* Get upload-error code.
*
* @return string * @return string
*/ */
public function getError() public function getError()
@@ -152,6 +221,7 @@ class InputFile implements IInputItem
/** /**
* Set error * Set error
*
* @param int $error * @param int $error
* @return static $this * @return static $this
*/ */
@@ -162,27 +232,6 @@ class InputFile implements IInputItem
return $this; return $this;
} }
public function toArray()
{
return [
'tmp_name' => $this->tmpName,
'type' => $this->type,
'size' => $this->size,
'name' => $this->name,
'error' => $this->error,
];
}
public function __toString()
{
return $this->getValue();
}
public function getValue()
{
return $this->getTmpName();
}
/** /**
* @return string * @return string
*/ */
@@ -202,4 +251,21 @@ class InputFile implements IInputItem
return $this; return $this;
} }
public function __toString()
{
return $this->getTmpName();
}
public function toArray()
{
return [
'tmp_name' => $this->tmpName,
'type' => $this->type,
'size' => $this->size,
'name' => $this->filename,
'error' => $this->error,
];
}
} }
+1
View File
@@ -75,4 +75,5 @@ class InputItem implements IInputItem
{ {
return (string)$this->value; return (string)$this->value;
} }
} }
@@ -58,7 +58,7 @@ class BaseCsrfVerifier implements IMiddleware
public function handle(Request $request, ILoadableRoute &$route = null) public function handle(Request $request, ILoadableRoute &$route = null)
{ {
if (in_array($request->getMethod(), ['post', 'put', 'delete']) === true && $this->skip($request) === false) { if ($this->skip($request) === false && in_array($request->getMethod(), ['post', 'put', 'delete']) === true) {
$token = $request->getInput()->get(static::POST_KEY, null, 'post'); $token = $request->getInput()->get(static::POST_KEY, null, 'post');
@@ -77,7 +77,7 @@ class BaseCsrfVerifier implements IMiddleware
public function generateToken() public function generateToken()
{ {
$token = $this->csrfToken->generateToken(); $token = CsrfToken::generateToken();
$this->csrfToken->setToken($token); $this->csrfToken->setToken($token);
return $token; return $token;
@@ -85,7 +85,7 @@ class BaseCsrfVerifier implements IMiddleware
public function hasToken() public function hasToken()
{ {
if ($this->token != null) { if ($this->token !== null) {
return true; return true;
} }
+11 -10
View File
@@ -15,10 +15,10 @@ class Request
public function __construct() public function __construct()
{ {
$this->parseHeaders(); $this->parseHeaders();
$this->host = $this->getHeader('http-host');; $this->host = $this->getHeader('http-host');
$this->uri = $this->getHeader('request-uri'); $this->uri = $this->getHeader('request-uri');
$this->input = new Input($this); $this->input = new Input($this);
$this->method = strtolower($this->input->get('_method', $this->getHeader('request-method'))); $this->method = strtolower($this->input->get('_method', $this->getHeader('request-method'), 'post'));
} }
protected function parseHeaders() protected function parseHeaders()
@@ -40,11 +40,7 @@ class Request
public function isSecure() public function isSecure()
{ {
if ($this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443) { return $this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443;
return true;
}
return false;
} }
/** /**
@@ -108,7 +104,7 @@ class Request
return $this->getHeader('http-cf-connecting-ip'); return $this->getHeader('http-cf-connecting-ip');
} }
if ($this->getHeader('http-x-forwarded-for') !== null && strlen($this->getHeader('http-x-forwarded-for'))) { if ($this->getHeader('http-x-forwarded-for') !== null) {
return $this->getHeader('http-x-forwarded_for'); return $this->getHeader('http-x-forwarded_for');
} }
@@ -137,7 +133,7 @@ class Request
* Get header value by name * Get header value by name
* *
* @param string $name * @param string $name
* @param object|null $defaultValue * @param string|null $defaultValue
* *
* @return string|null * @return string|null
*/ */
@@ -217,6 +213,11 @@ class Request
$this->method = $method; $this->method = $method;
} }
public function __isset($name)
{
return $this->data[$name] ?? null;
}
public function __set($name, $value = null) public function __set($name, $value = null)
{ {
$this->data[$name] = $value; $this->data[$name] = $value;
@@ -224,7 +225,7 @@ class Request
public function __get($name) public function __get($name)
{ {
return isset($this->data[$name]) ? $this->data[$name] : null; return $this->data[$name] ?? null;
} }
} }
+4 -5
View File
@@ -64,14 +64,14 @@ class Response
$this->headers([ $this->headers([
'Cache-Control: public', 'Cache-Control: public',
'Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModified) . ' GMT', 'Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT',
'Etag: ' . $eTag, 'Etag: ' . $eTag,
]); ]);
$httpModified = $this->request->getHeader('http-if-modified-since'); $httpModified = $this->request->getHeader('http-if-modified-since');
$httpIfNoneMatch = $this->request->getHeader('http-if-none-match'); $httpIfNoneMatch = $this->request->getHeader('http-if-none-match');
if ($httpModified !== null && strtotime($httpModified) == $lastModified || $httpIfNoneMatch !== null && $httpIfNoneMatch === $eTag) { if (($httpIfNoneMatch !== null && $httpIfNoneMatch === $eTag) || ($httpModified !== null && strtotime($httpModified) === $lastModified)) {
$this->header('HTTP/1.1 304 Not Modified'); $this->header('HTTP/1.1 304 Not Modified');
@@ -111,9 +111,8 @@ class Response
*/ */
public function headers(array $headers) public function headers(array $headers)
{ {
$max = count($headers); foreach ($headers as $header) {
for ($i = 0; $i < $max; $i++) { $this->header($header);
$this->header($headers[$i]);
} }
return $this; return $this;
@@ -22,7 +22,7 @@ interface ILoadableRoute extends IRoute
* @param Request $request * @param Request $request
* @param ILoadableRoute $route * @param ILoadableRoute $route
*/ */
public function loadMiddleware(Request $request, ILoadableRoute &$route); public function loadMiddleware(Request $request, ILoadableRoute $route);
public function getUrl(); public function getUrl();
+1 -1
View File
@@ -18,7 +18,7 @@ interface IRoute
* Returns class to be rendered. * Returns class to be rendered.
* *
* @param Request $request * @param Request $request
* @return object * @return \Closure|string
*/ */
public function renderRoute(Request $request); public function renderRoute(Request $request);
@@ -9,7 +9,14 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
{ {
const PARAMETERS_REGEX_MATCH = '%s([\w\-\_]*?)\%s{0,1}%s'; const PARAMETERS_REGEX_MATCH = '%s([\w\-\_]*?)\%s{0,1}%s';
/**
* @var
*/
protected $url; protected $url;
/**
* @var string
*/
protected $name; protected $name;
/** /**
@@ -19,7 +26,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* @param ILoadableRoute $route * @param ILoadableRoute $route
* @throws HttpException * @throws HttpException
*/ */
public function loadMiddleware(Request $request, ILoadableRoute &$route) public function loadMiddleware(Request $request, ILoadableRoute $route)
{ {
if (count($this->getMiddlewares()) > 0) { if (count($this->getMiddlewares()) > 0) {
@@ -114,7 +121,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
$param = $keys[$i]; $param = $keys[$i];
$value = $params[$param]; $value = $params[$param];
$value = (isset($parameters[$param])) ? $parameters[$param] : $value; $value = $parameters[$param] ?? $value;
if (stripos($url, $param1) !== false || stripos($url, $param) !== false) { if (stripos($url, $param1) !== false || stripos($url, $param) !== false) {
$url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $value, $url); $url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $value, $url);
+10 -7
View File
@@ -86,7 +86,7 @@ abstract class Route implements IRoute
if ($character === '{') { if ($character === '{') {
/* Remove "/" and "\" from regex */ /* Remove "/" and "\" from regex */
if (substr($regex, strlen($regex) - 1) === '/') { if (substr($regex, strlen($regex) - 1) === '/') {
$regex = substr($regex, 0, strlen($regex) - 2); $regex = substr($regex, 0, -2);
} }
$isParameter = true; $isParameter = true;
@@ -99,7 +99,7 @@ abstract class Route implements IRoute
} }
if ($lastCharacter === '?') { if ($lastCharacter === '?') {
$parameter = substr($parameter, 0, strlen($parameter) - 1); $parameter = substr($parameter, 0, -1);
$regex .= '(?:\/?(?P<' . $parameter . '>' . $parameterRegex . ')[^\/]?)?'; $regex .= '(?:\/?(?P<' . $parameter . '>' . $parameterRegex . ')[^\/]?)?';
$required = false; $required = false;
} else { } else {
@@ -136,13 +136,13 @@ abstract class Route implements IRoute
$name = $parameterNames[$i]; $name = $parameterNames[$i];
$parameterValue = isset($parameterValues[$name['name']]) ? $parameterValues[$name['name']] : null; $parameterValue = $parameterValues[$name['name']] ?? null;
if ($name['required'] && $parameterValue === null) { if ($parameterValue === null && $name['required']) {
throw new HttpException('Missing required parameter ' . $name['name'], 404); throw new HttpException('Missing required parameter ' . $name['name'], 404);
} }
if ($name['required'] === false && $parameterValue === null) { if ($parameterValue === null && $name['required'] === false) {
continue; continue;
} }
@@ -384,10 +384,13 @@ abstract class Route implements IRoute
} }
if (count($this->parameters) > 0) { if (count($this->parameters) > 0) {
/* Ensure the right order + values */ /* Ensure the right order + values */
$parameters = ($values['parameters'] + $this->parameters); $parameters = ($values['parameters'] ?? []) + $this->parameters;
$parameters = array_merge($parameters, $this->parameters); $parameters = array_merge($parameters, $this->parameters);
$this->setParameters($parameters); $this->setParameters($parameters);
$values['parameters'] = $parameters;
} }
if (count($this->middlewares) > 0) { if (count($this->middlewares) > 0) {
@@ -406,7 +409,7 @@ abstract class Route implements IRoute
*/ */
public function setSettings(array $values, $merge = false) public function setSettings(array $values, $merge = false)
{ {
if (isset($values['namespace']) && $this->namespace === null) { if ($this->namespace === null && isset($values['namespace'])) {
$this->setNamespace($values['namespace']); $this->setNamespace($values['namespace']);
} }
@@ -31,11 +31,11 @@ class RouteController extends LoadableRoute implements IControllerRoute
} }
/* Remove method/type */ /* Remove method/type */
if (stripos($name, '.') !== false) { if (strpos($name, '.') !== false) {
$method = substr($name, strrpos($name, '.') + 1); $method = substr($name, strrpos($name, '.') + 1);
$newName = substr($name, 0, strrpos($name, '.')); $newName = substr($name, 0, strrpos($name, '.'));
if (strtolower($this->name) === strtolower($newName) && in_array($method, $this->names)) { if (in_array($method, $this->names) === true && strtolower($this->name) === strtolower($newName)) {
return true; return true;
} }
} }
@@ -43,10 +43,15 @@ class RouteController extends LoadableRoute implements IControllerRoute
return parent::hasName($name); return parent::hasName($name);
} }
/**
* @param string|null $method
* @param string|array|null $parameters
* @param string|null $name
* @return string
*/
public function findUrl($method = null, $parameters = null, $name = null) public function findUrl($method = null, $parameters = null, $name = null)
{ {
if (strpos($name, '.') !== false) {
if (stripos($name, '.') !== false) {
$found = array_search(substr($name, strrpos($name, '.') + 1), $this->names); $found = array_search(substr($name, strrpos($name, '.') + 1), $this->names);
if ($found !== false) { if ($found !== false) {
$method = $found; $method = $found;
@@ -54,7 +59,6 @@ class RouteController extends LoadableRoute implements IControllerRoute
} }
$url = ''; $url = '';
$parameters = (array)$parameters; $parameters = (array)$parameters;
/* Remove requestType from method-name, if it exists */ /* Remove requestType from method-name, if it exists */
@@ -115,7 +119,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH); $url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/'; $url = rtrim($url, '/') . '/';
if (strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) { if (stripos($url, $this->url) === 0 && strtolower($url) === strtolower($this->url)) {
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/'); $strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
@@ -127,6 +131,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$this->method = $method; $this->method = $method;
array_shift($path); array_shift($path);
$this->parameters = $path; $this->parameters = $path;
// Set callback // Set callback
+2 -2
View File
@@ -27,8 +27,8 @@ class RouteGroup extends Route implements IGroupRoute
$domain = $this->domains[$i]; $domain = $this->domains[$i];
$parameters = $this->parseParameters($domain, $request->getHost(), '.*'); $parameters = $this->parseParameters($domain, $request->getHost(), '.*');
if ($parameters !== null) { if ($parameters !== null && count($parameters) > 0) {
$this->parameters = $parameters; $this->parameters = array_merge($this->parameters, $parameters);
return true; return true;
} }
+52 -14
View File
@@ -15,6 +15,17 @@ class RouteResource extends LoadableRoute implements IControllerRoute
'update' => '', 'update' => '',
'destroy' => '', 'destroy' => '',
]; ];
protected $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
'destroy' => 'destroy',
];
protected $names = []; protected $names = [];
protected $controller; protected $controller;
@@ -42,7 +53,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
} }
/* Remove method/type */ /* Remove method/type */
if (stripos($name, '.') !== false) { if (strpos($name, '.') !== false) {
$name = substr($name, 0, strrpos($name, '.')); $name = substr($name, 0, strrpos($name, '.'));
} }
@@ -51,7 +62,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
public function findUrl($method = null, $parameters = null, $name = null) public function findUrl($method = null, $parameters = null, $name = null)
{ {
$method = array_search($name, $this->names); $method = array_search($name, $this->names, false);
if ($method !== false) { if ($method !== false) {
return rtrim($this->url . $this->urls[$method], '/') . '/'; return rtrim($this->url . $this->urls[$method], '/') . '/';
} }
@@ -104,43 +115,43 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$parameters = array_merge($this->parameters, (array)$parameters); $parameters = array_merge($this->parameters, (array)$parameters);
$action = isset($parameters['action']) ? $parameters['action'] : null; $action = $parameters['action'] ?? null;
unset($parameters['action']); unset($parameters['action']);
$method = $request->getMethod(); $method = $request->getMethod();
// Delete // Delete
if (isset($parameters['id']) && $method === static::REQUEST_TYPE_DELETE) { if ($method === static::REQUEST_TYPE_DELETE && isset($parameters['id'])) {
return $this->call('destroy', $parameters); return $this->call($this->methodNames['destroy'], $parameters);
} }
// Update // Update
if (isset($parameters['id']) && in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT])) { if (isset($parameters['id']) && in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT])) {
return $this->call('update', $parameters); return $this->call($this->methodNames['update'], $parameters);
} }
// Edit // Edit
if (isset($parameters['id']) && strtolower($action) === 'edit' && $method === static::REQUEST_TYPE_GET) { if ($method === static::REQUEST_TYPE_GET && isset($parameters['id']) && strtolower($action) === 'edit') {
return $this->call('edit', $parameters); return $this->call($this->methodNames['edit'], $parameters);
} }
// Create // Create
if (strtolower($action) === 'create' && $method === static::REQUEST_TYPE_GET) { if ($method === static::REQUEST_TYPE_GET && strtolower($action) === 'create') {
return $this->call('create', $parameters); return $this->call($this->methodNames['create'], $parameters);
} }
// Save // Save
if ($method === static::REQUEST_TYPE_POST) { if ($method === static::REQUEST_TYPE_POST) {
return $this->call('store', $parameters); return $this->call($this->methodNames['store'], $parameters);
} }
// Show // Show
if (isset($parameters['id']) && $method === static::REQUEST_TYPE_GET) { if ($method === static::REQUEST_TYPE_GET && isset($parameters['id'])) {
return $this->call('show', $parameters); return $this->call($this->methodNames['show'], $parameters);
} }
// Index // Index
return $this->call('index', $parameters); return $this->call($this->methodNames['index'], $parameters);
} }
return null; return null;
@@ -182,6 +193,29 @@ class RouteResource extends LoadableRoute implements IControllerRoute
return $this; return $this;
} }
/**
* Define custom method name for resource controller
*
* @param array $names
* @return static $this
*/
public function setMethodNames(array $names)
{
$this->methodNames = $names;
return $this;
}
/**
* Get method names
*
* @return array $this
*/
public function getMethodNames()
{
return $this->methodNames;
}
/** /**
* Merge with information from another route. * Merge with information from another route.
* *
@@ -195,6 +229,10 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$this->names = $values['names']; $this->names = $values['names'];
} }
if (isset($values['methods'])) {
$this->methodNames = $values['methods'];
}
parent::setSettings($values, $merge); parent::setSettings($values, $merge);
return $this; return $this;
+21 -9
View File
@@ -100,7 +100,7 @@ class Router
return static::$instance; return static::$instance;
} }
public function __construct() protected function __construct()
{ {
$this->reset(); $this->reset();
} }
@@ -137,16 +137,26 @@ class Router
return $route; return $route;
} }
/**
* Process added routes.
*
* @param array $routes
* @param IGroupRoute|null $group
* @param IRoute|null $parent
*/
protected function processRoutes(array $routes, IGroupRoute $group = null, IRoute $parent = null) protected function processRoutes(array $routes, IGroupRoute $group = null, IRoute $parent = null)
{ {
// Loop through each route-request // Loop through each route-request
$max = count($routes) - 1; $max = count($routes) - 1;
$exceptionHandlers = [];
/* @var $route IRoute */ /* @var $route IRoute */
for ($i = $max; $i >= 0; $i--) { for ($i = $max; $i >= 0; $i--) {
$route = $routes[$i]; $route = $routes[$i];
/* @var $route IGroupRoute */
if ($route instanceof IGroupRoute) { if ($route instanceof IGroupRoute) {
$group = $route; $group = $route;
@@ -159,9 +169,9 @@ class Router
if ($route->matchRoute($this->request)) { if ($route->matchRoute($this->request)) {
/* Add exceptionhandlers */ /* Add exception handlers */
if (count($route->getExceptionHandlers()) > 0) { if (count($route->getExceptionHandlers()) > 0) {
$this->exceptionHandlers = array_merge($route->getExceptionHandlers(), $this->exceptionHandlers); $exceptionHandlers += $route->getExceptionHandlers();
} }
} }
@@ -172,7 +182,6 @@ class Router
/* Add the parent group */ /* Add the parent group */
$route->setGroup($group); $route->setGroup($group);
} }
if ($parent !== null) { if ($parent !== null) {
@@ -201,6 +210,8 @@ class Router
$this->processRoutes($stack, $route, $group); $this->processRoutes($stack, $route, $group);
} }
} }
$this->exceptionHandlers = array_unique(array_merge($exceptionHandlers, $this->exceptionHandlers));
} }
public function routeRequest($rewrite = false) public function routeRequest($rewrite = false)
@@ -253,7 +264,7 @@ class Router
if ($route->matchRoute($this->request)) { if ($route->matchRoute($this->request)) {
/* Check if request method matches */ /* Check if request method matches */
if (count($route->getRequestMethods()) > 0 && !in_array($this->request->getMethod(), $route->getRequestMethods())) { if (count($route->getRequestMethods()) > 0 && in_array($this->request->getMethod(), $route->getRequestMethods()) === false) {
$routeNotAllowed = true; $routeNotAllowed = true;
continue; continue;
} }
@@ -262,7 +273,7 @@ class Router
$this->loadedRoute->loadMiddleware($this->request, $this->loadedRoute); $this->loadedRoute->loadMiddleware($this->request, $this->loadedRoute);
/* If the request has changed, we reinitialize the router */ /* If the request has changed, we reinitialize the router */
if ($this->request->getUri() !== $this->originalUrl && !in_array($this->request->getUri(), $this->routeRewrites)) { if ($this->request->getUri() !== $this->originalUrl && in_array($this->request->getUri(), $this->routeRewrites) === false) {
$this->routeRewrites[] = $this->request->getUri(); $this->routeRewrites[] = $this->request->getUri();
$this->routeRequest(true); $this->routeRequest(true);
@@ -309,7 +320,7 @@ class Router
$request = $handler->handleError($this->request, $this->loadedRoute, $e); $request = $handler->handleError($this->request, $this->loadedRoute, $e);
/* If the request has changed */ /* If the request has changed */
if ($request !== null && $this->request->getUri() !== $this->originalUrl && !in_array($request->getUri(), $this->routeRewrites)) { if ($request !== null && $this->request->getUri() !== $this->originalUrl && in_array($request->getUri(), $this->routeRewrites) === false) {
$this->request = $request; $this->request = $request;
$this->routeRewrites[] = $request->getUri(); $this->routeRewrites[] = $request->getUri();
$this->routeRequest(true); $this->routeRequest(true);
@@ -375,7 +386,7 @@ class Router
if (strpos($name, '@') !== false && strpos($route->getCallback(), '@') !== false && !is_callable($route->getCallback())) { if (strpos($name, '@') !== false && strpos($route->getCallback(), '@') !== false && !is_callable($route->getCallback())) {
/* Check if the entire callback is matching */ /* Check if the entire callback is matching */
if (strtolower($route->getCallback()) === strtolower($name) || strpos($route->getCallback(), $name) === 0) { if (strpos($route->getCallback(), $name) === 0 || strtolower($route->getCallback()) === strtolower($name)) {
return $route; return $route;
} }
@@ -404,6 +415,7 @@ class Router
* @param string|null $name * @param string|null $name
* @param string|array|null $parameters * @param string|array|null $parameters
* @param array|null $getParams * @param array|null $getParams
* @throws \InvalidArgumentException
* @return string * @return string
*/ */
public function getUrl($name = null, $parameters = null, $getParams = null) public function getUrl($name = null, $parameters = null, $getParams = null)
@@ -439,7 +451,7 @@ class Router
} }
/* Using @ is most definitely a controller@method or alias@method */ /* Using @ is most definitely a controller@method or alias@method */
if (stripos($name, '@') !== false) { if (strpos($name, '@') !== false) {
list($controller, $method) = explode('@', $name); list($controller, $method) = explode('@', $name);
/* Loop through all the routes to see if we can find a match */ /* Loop through all the routes to see if we can find a match */
+1
View File
@@ -309,6 +309,7 @@ class SimpleRouter
* @param string|null $name * @param string|null $name
* @param string|array|null $parameters * @param string|array|null $parameters
* @param array|null $getParams * @param array|null $getParams
* @throws \Exception
* @return string * @return string
*/ */
public static function getUrl($name = null, $parameters = null, $getParams = null) public static function getUrl($name = null, $parameters = null, $getParams = null)
@@ -0,0 +1,4 @@
<?php
class ExceptionHandlerException extends \Exception
{
}
+1 -1
View File
@@ -4,7 +4,7 @@ class ExceptionHandler implements \Pecee\Handlers\IExceptionHandler
{ {
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error) public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{ {
throw $error; echo $error->getMessage();
} }
} }
@@ -0,0 +1,13 @@
<?php
class TestExceptionHandlerFirst implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{
echo 'ExceptionHandler 1 loaded' . chr(10);
$request->setUri('/');
return $request;
}
}
@@ -0,0 +1,13 @@
<?php
class TestExceptionHandlerSecond implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{
echo 'ExceptionHandler 2 loaded' . chr(10);
$request->setUri('/');
return $request;
}
}
@@ -0,0 +1,12 @@
<?php
class TestExceptionHandlerThird implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{
echo 'ExceptionHandler 3 loaded' . chr(10);
throw new ExceptionHandlerException('All good!', 666);
}
}
+15 -6
View File
@@ -10,6 +10,8 @@ class MiddlewareTest extends PHPUnit_Framework_TestCase
{ {
public function testMiddlewareFound() public function testMiddlewareFound()
{ {
$this->setExpectedException('MiddlewareLoadedException');
SimpleRouter::router()->reset(); SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get'); SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/test/url'); SimpleRouter::request()->setUri('/my/test/url');
@@ -18,15 +20,22 @@ class MiddlewareTest extends PHPUnit_Framework_TestCase
SimpleRouter::get('/my/test/url', 'DummyController@start', ['middleware' => 'DummyMiddleware']); SimpleRouter::get('/my/test/url', 'DummyController@start', ['middleware' => 'DummyMiddleware']);
}); });
$found = false;
try {
SimpleRouter::start(); SimpleRouter::start();
} catch (\Exception $e) {
$found = ($e instanceof MiddlewareLoadedException);
} }
$this->assertTrue($found); public function testNestedMiddlewareLoad()
{
$this->setExpectedException('MiddlewareLoadedException');
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler', 'middleware' => 'DummyMiddleware'], function () {
SimpleRouter::get('/my/test/url', 'DummyController@start');
});
SimpleRouter::start();
} }
} }
+32 -11
View File
@@ -2,34 +2,55 @@
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/Exceptions/ExceptionHandlerException.php';
require_once 'Dummy/Handler/TestExceptionHandlerFirst.php';
require_once 'Dummy/Handler/TestExceptionHandlerSecond.php';
require_once 'Dummy/Handler/TestExceptionHandlerThird.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException as NotFoundHttpException; use Pecee\SimpleRouter\Exceptions\NotFoundHttpException as NotFoundHttpException;
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class RouterRouteTest extends PHPUnit_Framework_TestCase class RouterRouteTest extends PHPUnit_Framework_TestCase
{ {
protected $result = false; protected $result = false;
/**
* Redirects to another route through 3 exception handlers.
*
* You will see "ExceptionHandler 1 loaded" 2 times. This happen because
* the exceptionhandler is asking the router to reload.
*
* That means that the exceptionhandler is loaded again, but this time
* the router ignores the same rewrite-route to avoid loop - loads
* the second which have same behavior and is also ignored before
* throwing the final Exception in ExceptionHandler 3.
*
* So this tests:
* 1. If ExceptionHandlers loads
* 2. If ExceptionHandlers load in the correct order
* 3. If ExceptionHandlers can rewrite the page on error
* 4. If the router can avoid redirect-loop due to developer has started loop.
* 5. And finally if we reaches the last exception-handler and that the correct
* exception-type is being thrown.
*/
public function testNotFound() public function testNotFound()
{ {
$this->setExpectedException('ExceptionHandlerException');
SimpleRouter::router()->reset(); SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get'); SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2'); SimpleRouter::request()->setUri('/test-param1-param2');
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () { SimpleRouter::group(['exceptionHandler' => ['TestExceptionHandlerFirst', 'TestExceptionHandlerSecond']], function () {
SimpleRouter::group(['exceptionHandler' => 'TestExceptionHandlerThird'], function () {
SimpleRouter::get('/non-existing-path', 'DummyController@start'); SimpleRouter::get('/non-existing-path', 'DummyController@start');
});
}); });
$found = false;
try {
SimpleRouter::start(); SimpleRouter::start();
} catch (\Exception $e) {
$found = ($e instanceof NotFoundHttpException && $e->getCode() == 404);
}
$this->assertTrue($found);
} }
public function testGet() public function testGet()
+4 -3
View File
@@ -10,7 +10,8 @@ class RouterUrlTest extends PHPUnit_Framework_TestCase
{ {
protected $result = false; protected $result = false;
protected function getUrl($name = null, $parameters = null, array $getParams = []) { protected function getUrl($name = null, $parameters = null, array $getParams = [])
{
return SimpleRouter::getUrl($name, $parameters, $getParams); return SimpleRouter::getUrl($name, $parameters, $getParams);
} }
@@ -25,7 +26,7 @@ class RouterUrlTest extends PHPUnit_Framework_TestCase
SimpleRouter::get('/about', 'DummyController@about'); SimpleRouter::get('/about', 'DummyController@about');
SimpleRouter::group(['prefix' => '/admin', 'as' => 'admin'], function() { SimpleRouter::group(['prefix' => '/admin', 'as' => 'admin'], function () {
// Match route with prefix on alias // Match route with prefix on alias
SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'home']); SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'home']);
@@ -38,7 +39,7 @@ class RouterUrlTest extends PHPUnit_Framework_TestCase
}); });
SimpleRouter::group(['prefix' => 'api', 'as' => 'api'], function() { SimpleRouter::group(['prefix' => 'api', 'as' => 'api'], function () {
// Match resource controller // Match resource controller
SimpleRouter::resource('phones', 'DummyController'); SimpleRouter::resource('phones', 'DummyController');