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
+1 -1
View File
@@ -15,7 +15,7 @@ interface IInputItem
public function getValue();
public function setValue(string $value): self;
public function setValue($value): self;
public function __toString(): string;
+3 -3
View File
@@ -261,16 +261,16 @@ class InputFile implements IInputItem
return $this->getTmpName();
}
public function getValue(): ?string
public function getValue()
{
return $this->getFilename();
}
/**
* @param string $value
* @param mixed $value
* @return static
*/
public function setValue(string $value): IInputItem
public function setValue($value): IInputItem
{
$this->filename = $value;
+38 -12
View File
@@ -92,22 +92,29 @@ class InputHandler
/* Parse get requests */
if (\count($_FILES) !== 0) {
$this->originalFile = $_FILES;
$this->file = $this->parseFiles();
$this->file = $this->parseFiles($this->originalFile);
}
}
/**
* @return array
*/
public function parseFiles(): array
public function parseFiles(array $files, $parentKey = null): array
{
$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
if (\is_array($value['name']) === false) {
$values['index'] = $key;
$values['index'] = $parentKey ?? $key;
try {
$list[$key] = InputFile::createFromArray($values + $value);
} catch (InvalidArgumentException $e) {
@@ -237,11 +244,27 @@ class InputHandler
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
*
* @param string $index
* @param string|object|null $defaultValue
* @param string|mixed|null $defaultValue
* @param array ...$methods
* @return string|array
*/
@@ -249,18 +272,18 @@ class InputHandler
{
$input = $this->find($index, ...$methods);
if ($input instanceof IInputItem) {
$input = $input->getValue();
}
/* Handle collection */
if (\is_array($input) === true) {
$output = [];
/* @var $item InputItem */
foreach ($input as $item) {
$output[] = \is_array($item) ? $item : $item->getValue();
}
$output = $this->getValueFromArray($input);
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
{
$output = $this->originalParams + $this->originalPost + $this->originalFile;
$output = (\count($filter) > 0) ? array_intersect_key($output, array_flip($filter)) : $output;
$output = (\count($filter) > 0) ? \array_intersect_key($output, \array_flip($filter)) : $output;
foreach ($filter as $filterKey) {
if (array_key_exists($filterKey, $output) === false) {
@@ -380,6 +403,7 @@ class InputHandler
public function setOriginalPost(array $post): self
{
$this->originalPost = $post;
return $this;
}
@@ -400,6 +424,7 @@ class InputHandler
public function setOriginalParams(array $params): self
{
$this->originalParams = $params;
return $this;
}
@@ -420,6 +445,7 @@ class InputHandler
public function setOriginalFile(array $file): self
{
$this->originalFile = $file;
return $this;
}
+2 -2
View File
@@ -62,10 +62,10 @@ class InputItem implements IInputItem, \IteratorAggregate
/**
* Set input value
* @param string $value
* @param mixed $value
* @return static
*/
public function setValue(string $value): IInputItem
public function setValue($value): IInputItem
{
$this->value = $value;
+59 -6
View File
@@ -19,6 +19,10 @@ class Request
public const REQUEST_TYPE_DELETE = 'delete';
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
* @var string[]
@@ -57,6 +61,12 @@ class Request
*/
protected $headers = [];
/**
* Request ContentType
* @var string
*/
protected $contentType;
/**
* Request host
* @var string
@@ -117,7 +127,9 @@ class Request
$this->setHost($this->getHeader('http-host'));
// 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->inputHandler = new InputHandler($this);
@@ -282,6 +294,50 @@ class Request
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
* @return InputHandler
@@ -298,7 +354,7 @@ class Request
*
* @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);
}
@@ -438,7 +494,6 @@ class Request
public function setLoadedRoutes(array $routes): self
{
$this->loadedRoutes = $routes;
return $this;
}
@@ -451,7 +506,6 @@ class Request
public function addLoadedRoute(ILoadableRoute $route): self
{
$this->loadedRoutes[] = $route;
return $this;
}
@@ -474,7 +528,6 @@ class Request
public function setHasPendingRewrite(bool $boolean): self
{
$this->hasPendingRewrite = $boolean;
return $this;
}
@@ -493,4 +546,4 @@ class Request
return $this->data[$name] ?? null;
}
}
}
+1 -1
View File
@@ -371,7 +371,7 @@ class Url implements \JsonSerializable
*/
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;
use DI\Container;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
class ClassLoader implements IClassLoader
{
/**
* Dependency injection enabled
* @var bool
*/
protected $useDependencyInjection = false;
/**
* @var Container|null
*/
protected $container;
/**
* Load class
*
* @param string $class
* @return mixed
* @throws NotFoundHttpException
* @return object
* @throws ClassNotFoundHttpException
*/
public function loadClass(string $class)
{
if (class_exists($class) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
}
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());
}
}
throw new ClassNotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404, null, $class);
}
return new $class();
@@ -51,68 +28,10 @@ class ClassLoader implements IClassLoader
* @param Callable $closure
* @param array $parameters
* @return mixed
* @throws NotFoundHttpException
*/
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);
}
/**
* 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
{
/**
* Called when loading class
* @param string $class
* @return object
*/
public function loadClass(string $class);
/**
* Called when loading method
*
* @param callable $closure
* @param array $parameters
* @return mixed
*/
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.
*
* @param string|null $name Filter events by name.
* @param array ...$names Add multiple names...
* @param array|string ...$names Add multiple names...
* @return 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\Request;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Router;
@@ -77,7 +78,6 @@ abstract class Route implements IRoute
$router->debug('Executing callback');
/* When the callback is a function */
return $router->getClassLoader()->loadClosure($callback, $parameters);
}
@@ -95,7 +95,7 @@ abstract class Route implements IRoute
}
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');
@@ -250,7 +250,7 @@ abstract class Route implements IRoute
/**
* Set callback
*
* @param string|array\Closure $callback
* @param string|array|\Closure $callback
* @return static
*/
public function setCallback($callback): IRoute
+63 -27
View File
@@ -110,6 +110,13 @@ class Router
*/
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.
*/
@@ -262,13 +269,20 @@ class Router
/**
* Load routes
* @throws NotFoundHttpException
* @return void
* @throws NotFoundHttpException
*/
public function loadRoutes(): void
{
$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, [
'bootmanagers' => $this->bootManagers,
]);
@@ -291,13 +305,6 @@ class Router
$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');
}
@@ -306,7 +313,7 @@ class Router
*
* @return string|null
* @throws NotFoundHttpException
* @throws TokenMismatchException
* @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException
* @throws HttpException
* @throws \Exception
*/
@@ -350,7 +357,7 @@ class Router
{
$this->debug('Routing request');
$methodNotAllowed = false;
$methodNotAllowed = null;
try {
$url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath();
@@ -370,7 +377,12 @@ class Router
/* Check if request method matches */
if (\count($route->getRequestMethods()) !== 0 && \in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) {
$this->debug('Method "%s" not allowed', $this->request->getMethod());
$methodNotAllowed = true;
// Only set method not allowed is not already set
if ($methodNotAllowed === null) {
$methodNotAllowed = true;
}
continue;
}
@@ -394,14 +406,21 @@ class Router
'route' => $route,
]);
$output = $route->renderRoute($this->request, $this);
if ($output !== null) {
return $output;
}
$routeOutput = $route->renderRoute($this->request, $this);
$output = $this->handleRouteRewrite($key, $url);
if ($output !== null) {
return $output;
if ($this->renderMultipleRoutes === true) {
if ($routeOutput !== null) {
return $routeOutput;
}
$output = $this->handleRouteRewrite($key, $url);
if ($output !== null) {
return $output;
}
} else {
$output = $this->handleRouteRewrite($key, $url);
return $output ?? $routeOutput;
}
}
}
@@ -475,9 +494,9 @@ class Router
/**
* @param \Exception $e
* @throws HttpException
* @throws \Exception
* @return string|null
* @throws \Exception
* @throws HttpException
*/
protected function handleException(\Exception $e): ?string
{
@@ -664,14 +683,16 @@ class Router
->setParams($getParams);
}
/* We try to find a match on the given name */
$route = $this->findRoute($name);
if($name !== null) {
/* We try to find a match on the given name */
$route = $this->findRoute($name);
if ($route !== null) {
return $this->request
->getUrlCopy()
->setPath($route->findUrl($route->getMethod(), $parameters, $name))
->setParams($getParams);
if ($route !== null) {
return $this->request
->getUrlCopy()
->setPath($route->findUrl($route->getMethod(), $parameters, $name))
->setParams($getParams);
}
}
/* Using @ is most definitely a controller@method or alias@method */
@@ -907,4 +928,19 @@ class Router
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;
use DI\Container;
use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Request;
@@ -59,6 +58,11 @@ class SimpleRouter
*/
public static function start(): void
{
// Set default namespaces
foreach (static::router()->getRoutes() as $route) {
static::addDefaultNamespace($route);
}
echo static::router()->start();
}
@@ -307,8 +311,8 @@ class SimpleRouter
* @param string $url
* @param string|array|\Closure $callback
* @param array|null $settings
* @see SimpleRouter::form
* @return RouteUrl
* @see SimpleRouter::form
*/
public static function basic(string $url, $callback, array $settings = null): IRoute
{
@@ -322,14 +326,14 @@ class SimpleRouter
* @param string $url
* @param string|array|\Closure $callback
* @param array|null $settings
* @see SimpleRouter::form
* @return RouteUrl
* @see SimpleRouter::form
*/
public static function form(string $url, $callback, array $settings = null): IRoute
{
return static::match([
Request::REQUEST_TYPE_GET,
Request::REQUEST_TYPE_POST
Request::REQUEST_TYPE_POST,
], $url, $callback, $settings);
}
@@ -346,7 +350,6 @@ class SimpleRouter
{
$route = new RouteUrl($url, $callback);
$route->setRequestMethods($requestMethods);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
@@ -366,7 +369,6 @@ class SimpleRouter
public static function all(string $url, $callback, array $settings = null)
{
$route = new RouteUrl($url, $callback);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
@@ -386,7 +388,6 @@ class SimpleRouter
public static function controller(string $url, string $controller, array $settings = null)
{
$route = new RouteController($url, $controller);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
@@ -406,7 +407,6 @@ class SimpleRouter
public static function resource(string $url, string $controller, array $settings = null)
{
$route = new RouteResource($url, $controller);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
@@ -511,39 +511,43 @@ class SimpleRouter
{
if (static::$defaultNamespace !== null) {
$callback = $route->getCallback();
$ns = static::$defaultNamespace;
$namespace = $route->getNamespace();
/* Only add default namespace on relative callbacks */
if ($callback === null || (\is_string($callback) === true && $callback[0] !== '\\')) {
$namespace = static::$defaultNamespace;
$currentNamespace = $route->getNamespace();
if ($currentNamespace !== null) {
$namespace .= '\\' . $currentNamespace;
if ($namespace !== null) {
// Don't overwrite namespaces that starts with \
if ($namespace[0] !== '\\') {
$ns .= '\\' . $namespace;
} else {
$ns = $namespace;
}
$route->setDefaultNamespace($namespace);
}
$route->setNamespace($ns);
}
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
* @return IClassLoader
* @param bool $bool
*/
public static function enableDependencyInjection(Container $container): IClassLoader
public static function enableMultiRouteRendering(bool $bool): void
{
return static::router()
->getClassLoader()
->useDependencyInjection(true)
->setContainer($container);
static::router()->setRenderMultipleRoutes($bool);
}
/**
* Set custom class-loader class used.
* @param IClassLoader $classLoader
*/
public static function setCustomClassLoader(IClassLoader $classLoader): void
{
static::router()->setClassLoader($classLoader);
}
/**