diff --git a/composer.json b/composer.json index 854db54..c4ed5d7 100644 --- a/composer.json +++ b/composer.json @@ -16,14 +16,13 @@ } ], "require": { - "php": ">= 7.1", - "contributte/psr7-http-message": "~0.5" + "php": "^7.2", + "contributte/di": "^0.4.0", + "contributte/psr7-http-message": "^0.6.0" }, "require-dev": { - "nette/application": "~2.4.12", - "nette/di": "~2.4.12", - "nette/http": "~2.4.9", - "nette/utils": "~2.5.2", + "nette/application": "~3.0.0", + "nette/http": "~3.0.1", "ninjify/nunjuck": "~0.2", "ninjify/qa": "~0.8.0", "phpstan/extension-installer": "^1.0", @@ -34,16 +33,8 @@ "psr/log": "^1.0", "tracy/tracy": "~2.6.1" }, - "conflict": { - "nette/application": "<2.4.12", - "nette/di": "<2.4.12", - "nette/http": "<2.4.9", - "nette/utils": "<2.5.2", - "tracy/tracy": "<2.5.4" - }, "suggest": { "tracy/tracy": "to use TracyMiddleware", - "nette/di": "to use CompilerExtension(NetteMiddlewareExtension | StandaloneMiddlewareExtension)", "nette/http": "to use NetteMiddlewareExtension & NetteMiddlewareApplication", "nette/application": "to use PresenterMiddleware" }, @@ -64,7 +55,7 @@ }, "extra": { "branch-alias": { - "dev-master": "0.8.x-dev" + "dev-master": "0.9.x-dev" } } } diff --git a/phpstan.neon b/phpstan.neon index 8c231c0..e9e4b8e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,3 +3,7 @@ parameters: - '#Only booleans are allowed in an if condition, mixed given.#' - '#Only booleans are allowed in a negated boolean, mixed given.#' - '#^Parameter \#2 \$parameters of function call_user_func_array expects array\, array\ given\.$#' + - '#^Parameter \#1 \$name of method Nette\\DI\\ContainerBuilder\:\:getDefinition\(\) expects string, int\|string given\.$#' + - '#^Only booleans are allowed in a negated boolean, int given\.$#' + # Cannot happen + - '#^Parameter \#1 \$request of method Contributte\\Middlewares\\PresenterMiddleware\:\:processRequest\(\) expects Nette\\Application\\Request, Nette\\Application\\Request\|null given\.$#' diff --git a/src/DI/AbstractMiddlewaresExtension.php b/src/DI/AbstractMiddlewaresExtension.php index 3c05caf..4d7ed41 100644 --- a/src/DI/AbstractMiddlewaresExtension.php +++ b/src/DI/AbstractMiddlewaresExtension.php @@ -2,42 +2,47 @@ namespace Contributte\Middlewares\DI; +use Contributte\DI\Helper\ExtensionDefinitionsHelper; use Contributte\Middlewares\Exception\InvalidStateException; use Contributte\Middlewares\Tracy\DebugChainBuilder; use Contributte\Middlewares\Tracy\MiddlewaresPanel; use Contributte\Middlewares\Utils\ChainBuilder; -use Nette\DI\Compiler; use Nette\DI\CompilerExtension; -use Nette\DI\Statement; +use Nette\DI\Definitions\ServiceDefinition; +use Nette\DI\Definitions\Statement; use Nette\PhpGenerator\ClassType; -use Nette\Utils\Validators; +use Nette\Schema\Expect; +use Nette\Schema\Schema; +use stdClass; +/** + * @property-read stdClass $config + */ abstract class AbstractMiddlewaresExtension extends CompilerExtension { public const MIDDLEWARE_TAG = 'middleware'; - /** @var mixed[] */ - protected $defaults = [ - 'middlewares' => [], - 'debug' => false, - ]; + public function getConfigSchema(): Schema + { + return Expect::structure([ + 'middlewares' => Expect::arrayOf( + Expect::anyOf(Expect::string(), Expect::array(), Expect::type(Statement::class)) + ), + 'debug' => Expect::bool(false), + ]); + } - /** - * Register services - */ public function loadConfiguration(): void { $builder = $this->getContainerBuilder(); - $config = $this->validateConfig($this->defaults); - - Validators::assertField($config, 'middlewares', 'array'); + $config = $this->config; // Register middleware chain builder $chain = $builder->addDefinition($this->prefix('chain')) ->setAutowired(false); - if ($config['debug'] !== true) { + if (!$config->debug) { $chain->setFactory(ChainBuilder::class); } else { $chain->setFactory(DebugChainBuilder::class); @@ -47,16 +52,13 @@ public function loadConfiguration(): void } } - /** - * Decorate services - */ public function beforeCompile(): void { $builder = $this->getContainerBuilder(); - $config = $this->getConfig(); + $config = $this->config; // Compile defined middlewares - if ($config['middlewares'] !== []) { + if ($config->middlewares !== []) { $this->compileDefinedMiddlewares(); return; @@ -75,26 +77,18 @@ public function beforeCompile(): void private function compileDefinedMiddlewares(): void { $builder = $this->getContainerBuilder(); - $config = $this->getConfig(); + $config = $this->config; + $definitionsHelper = new ExtensionDefinitionsHelper($this->compiler); // Obtain middleware chain builder $chain = $builder->getDefinition($this->prefix('chain')); + assert($chain instanceof ServiceDefinition); // Add middleware services to chain $counter = 0; - foreach ($config['middlewares'] as $service) { - + foreach ($config->middlewares as $service) { // Create middleware as service - if ( - is_array($service) - || $service instanceof Statement - || (is_string($service) && strncmp($service, '@', 1) !== 0) - ) { - $def = $builder->addDefinition($this->prefix('middleware' . ($counter++))); - Compiler::loadDefinition($def, $service); - } else { - $def = $builder->getDefinition(ltrim($service, '@')); - } + $def = $definitionsHelper->getDefinitionFromConfig($service, $this->prefix('middleware' . ($counter++))); // Append to chain of middlewares $chain->addSetup('add', [$def]); @@ -114,7 +108,7 @@ private function compileTaggedMiddlewares(): void } // Sort by priority - uasort($definitions, function ($a, $b) { + uasort($definitions, function (array $a, array $b) { $p1 = $a['priority'] ?? 10; $p2 = $b['priority'] ?? 10; @@ -127,6 +121,7 @@ private function compileTaggedMiddlewares(): void // Obtain middleware chain builder $chain = $builder->getDefinition($this->prefix('chain')); + assert($chain instanceof ServiceDefinition); // Add middleware services to chain foreach ($definitions as $name => $tag) { @@ -137,9 +132,9 @@ private function compileTaggedMiddlewares(): void public function afterCompile(ClassType $class): void { - $config = $this->validateConfig($this->defaults); + $config = $this->config; - if ($config['debug'] === true) { + if ($config->debug) { $initialize = $class->getMethod('initialize'); $initialize->addBody( '$this->getService(?)->addPanel($this->getService(?));', diff --git a/src/DI/MiddlewaresExtension.php b/src/DI/MiddlewaresExtension.php index 4b6b428..3646c6a 100644 --- a/src/DI/MiddlewaresExtension.php +++ b/src/DI/MiddlewaresExtension.php @@ -3,7 +3,7 @@ namespace Contributte\Middlewares\DI; use Contributte\Middlewares\Application\MiddlewareApplication; -use Nette\DI\Statement; +use Nette\DI\Definitions\Statement; class MiddlewaresExtension extends AbstractMiddlewaresExtension { diff --git a/src/DI/NetteMiddlewaresExtension.php b/src/DI/NetteMiddlewaresExtension.php index 2b57010..bdecc87 100644 --- a/src/DI/NetteMiddlewaresExtension.php +++ b/src/DI/NetteMiddlewaresExtension.php @@ -3,8 +3,8 @@ namespace Contributte\Middlewares\DI; use Contributte\Middlewares\Application\NetteMiddlewareApplication; +use Nette\DI\Definitions\Statement; use Nette\DI\ServiceCreationException; -use Nette\DI\Statement; use Nette\Http\Request; use Nette\Http\Response; diff --git a/src/PresenterMiddleware.php b/src/PresenterMiddleware.php index 23573ca..4f53746 100644 --- a/src/PresenterMiddleware.php +++ b/src/PresenterMiddleware.php @@ -3,22 +3,19 @@ namespace Contributte\Middlewares; use Contributte\Middlewares\Exception\InvalidStateException; -use Contributte\Psr7\Psr7Request; use Contributte\Psr7\Psr7Response; use Contributte\Psr7\Psr7ServerRequest; -use Exception; use Nette\Application\AbortException; use Nette\Application\ApplicationException; use Nette\Application\BadRequestException; use Nette\Application\InvalidPresenterException; use Nette\Application\IPresenter; use Nette\Application\IPresenterFactory; -use Nette\Application\IResponse; use Nette\Application\IResponse as IApplicationResponse; -use Nette\Application\IRouter; use Nette\Application\Request as ApplicationRequest; use Nette\Application\Responses\ForwardResponse; use Nette\Application\UI\Presenter; +use Nette\Routing\Router; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Throwable; @@ -32,7 +29,7 @@ class PresenterMiddleware implements IMiddleware /** @var IPresenterFactory */ protected $presenterFactory; - /** @var IRouter */ + /** @var Router */ protected $router; /** @var ApplicationRequest[] */ @@ -47,7 +44,7 @@ class PresenterMiddleware implements IMiddleware /** @var bool */ protected $catchExceptions = true; - public function __construct(IPresenterFactory $presenterFactory, IRouter $router) + public function __construct(IPresenterFactory $presenterFactory, Router $router) { $this->presenterFactory = $presenterFactory; $this->router = $router; @@ -97,11 +94,8 @@ public function __invoke(ServerRequestInterface $psr7Request, ResponseInterface try { $applicationResponse = $this->processRequest($this->createInitialRequest($psr7Request)); } catch (Throwable $e) { - // Handle is followed - } - - if (isset($e)) { - if (!$this->catchExceptions || $this->errorPresenter === null) { + $errorPresenter = $this->errorPresenter; + if (!$this->catchExceptions || $errorPresenter === null) { throw $e; } @@ -109,44 +103,36 @@ public function __invoke(ServerRequestInterface $psr7Request, ResponseInterface // Create a new response with given code $psr7Response = $psr7Response->withStatus($e instanceof BadRequestException ? ($e->getCode() ?: 404) : 500); // Try resolve exception via forward or redirect - $applicationResponse = $this->processException($e); + $applicationResponse = $this->processException($e, $errorPresenter); } catch (Throwable $e) { // No fallback needed } + throw $e; } - // Convert to Psr7Response - if ($applicationResponse instanceof Psr7Response) { - // If response is Psr7Response type, just use it - $psr7Response = $applicationResponse; - } elseif ($applicationResponse instanceof IApplicationResponse) { - // If response is IApplicationResponse, wrap to Psr7Response - $psr7Response = $psr7Response->withApplicationResponse($applicationResponse); - } else { - throw new InvalidStateException(); - } + $psr7Response = $psr7Response->withApplicationResponse($applicationResponse); - // Pass to next middleware - $psr7Response = $next($psr7Request, $psr7Response); - - // Return response - return $psr7Response; + return $next($psr7Request, $psr7Response); } - public function processRequest(?ApplicationRequest $request): IApplicationResponse + public function processRequest(ApplicationRequest $request): IApplicationResponse { - if ($request === null) { - throw new InvalidStateException('This should not happen. Please report issue at https://github.com/contributte/middlewares/issues'); - } - process: if (count($this->requests) > self::$maxLoop) { throw new ApplicationException('Too many loops detected in application life cycle.'); } $this->requests[] = $request; - $this->presenter = $this->presenterFactory->createPresenter($request->getPresenterName()); - /** @var IResponse|null $response */ + + if (!$request->isMethod($request::FORWARD) && !strcasecmp($request->getPresenterName(), (string) $this->errorPresenter)) { + throw new BadRequestException('Invalid request. Presenter is not achievable.'); + } + + try { + $this->presenter = $this->presenterFactory->createPresenter($request->getPresenterName()); + } catch (InvalidPresenterException $e) { + throw count($this->requests) > 1 ? $e : new BadRequestException($e->getMessage(), 0, $e); + } $response = $this->presenter->run(clone $request); if ($response instanceof ForwardResponse) { @@ -154,19 +140,14 @@ public function processRequest(?ApplicationRequest $request): IApplicationRespon goto process; } - if ($response === null) { - throw new BadRequestException('Invalid response. Nullable.'); - } - return $response; } /** - * @param Exception|Throwable $e * @throws ApplicationException * @throws BadRequestException */ - public function processException($e): IApplicationResponse + public function processException(Throwable $e, string $errorPresenter): IApplicationResponse { $args = [ 'exception' => $e, @@ -175,40 +156,47 @@ public function processException($e): IApplicationResponse if ($this->presenter instanceof Presenter) { try { - $this->presenter->forward(':' . $this->errorPresenter . ':', $args); + $this->presenter->forward(':' . $errorPresenter . ':', $args); } catch (AbortException $foo) { return $this->processRequest($this->presenter->getLastCreatedRequest()); } } - return $this->processRequest(new ApplicationRequest($this->errorPresenter, ApplicationRequest::FORWARD, $args)); + return $this->processRequest(new ApplicationRequest($errorPresenter, ApplicationRequest::FORWARD, $args)); } /** - * @param Psr7ServerRequest|Psr7Request $psr7Request * @throws BadRequestException */ - protected function createInitialRequest($psr7Request): ApplicationRequest + protected function createInitialRequest(Psr7ServerRequest $psr7Request): ApplicationRequest { - $request = $this->router->match($psr7Request->getHttpRequest()); - - if (!$request instanceof ApplicationRequest) { - throw new BadRequestException('No route for HTTP request.'); + $netteRequest = $psr7Request->getHttpRequest(); + $parameters = $this->router->match($netteRequest); + $presenter = $parameters[Presenter::PRESENTER_KEY] ?? null; + if ($presenter === null) { + throw new InvalidStateException('Missing presenter in route definition.'); } - if (strcasecmp($request->getPresenterName(), (string) $this->errorPresenter) === 0) { - throw new BadRequestException('Invalid request. Presenter is not achievable.'); + if ($parameters === null || !is_string($presenter)) { + throw new BadRequestException('No route for HTTP request.'); } try { - $name = $request->getPresenterName(); - $this->presenterFactory->getPresenterClass($name); + $this->presenterFactory->getPresenterClass($presenter); } catch (InvalidPresenterException $e) { throw new BadRequestException($e->getMessage(), 0, $e); } - return $request; + unset($parameters[Presenter::PRESENTER_KEY]); + return new ApplicationRequest( + $presenter, + $netteRequest->getMethod(), + $parameters, + $netteRequest->getPost(), + $netteRequest->getFiles(), + [ApplicationRequest::SECURED => $netteRequest->isSecured()] + ); } }