From 1188670042710499395d7148b56ec16b43e6c4e3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Tue, 7 Jun 2022 16:45:33 +0300 Subject: [PATCH] Implement friendly exception for `InvalidMiddlewareDefinitionException` (#50) --- CHANGELOG.md | 3 +- composer.json | 3 +- src/InvalidMiddlewareDefinitionException.php | 121 +++++++++++++++++- ...validMiddlewareDefinitionExceptionTest.php | 26 +++- 4 files changed, 143 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81e738d..16a3400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,7 @@ ## 2.0.2 under development -- no changes in this release. - +- Enh #45: Implement friendly exception for `InvalidMiddlewareDefinitionException` (vjik) ## 2.0.1 February 14, 2022 diff --git a/composer.json b/composer.json index 505d41d..9447858 100644 --- a/composer.json +++ b/composer.json @@ -18,11 +18,12 @@ }, "require": { "php": "^7.4|^8.0", + "psr/container": "^1.0|^2.0", "psr/event-dispatcher": "^1.0", "psr/http-message": "^1.0", "psr/http-server-handler": "^1.0", "psr/http-server-middleware": "^1.0", - "psr/container": "^1.0|^2.0", + "yiisoft/friendly-exception": "^1.1", "yiisoft/injector": "^1.0" }, "require-dev": { diff --git a/src/InvalidMiddlewareDefinitionException.php b/src/InvalidMiddlewareDefinitionException.php index d6d0739..3bc4548 100644 --- a/src/InvalidMiddlewareDefinitionException.php +++ b/src/InvalidMiddlewareDefinitionException.php @@ -5,29 +5,140 @@ namespace Yiisoft\Middleware\Dispatcher; use InvalidArgumentException; +use Psr\Http\Server\MiddlewareInterface; +use Yiisoft\FriendlyException\FriendlyExceptionInterface; use function get_class; use function is_array; use function is_object; use function is_string; -final class InvalidMiddlewareDefinitionException extends InvalidArgumentException +final class InvalidMiddlewareDefinitionException extends InvalidArgumentException implements FriendlyExceptionInterface { /** - * @param array|callable|string $middlewareDefinition + * @var mixed + */ + private $definition; + private ?string $definitionString; + + /** + * @param mixed $middlewareDefinition */ public function __construct($middlewareDefinition) { + $this->definition = $middlewareDefinition; + $this->definitionString = $this->convertDefinitionToString($middlewareDefinition); + $message = 'Parameter should be either PSR middleware class name or a callable.'; - $definitionString = $this->convertDefinitionToString($middlewareDefinition); - if ($definitionString !== null) { - $message .= ' Got ' . $definitionString . '.'; + if ($this->definitionString !== null) { + $message .= ' Got ' . $this->definitionString . '.'; } parent::__construct($message); } + public function getName(): string + { + return 'Invalid middleware definition'; + } + + public function getSolution(): ?string + { + $solution = []; + + if ($this->definitionString !== null) { + $solution[] = <<definitionString}` + SOLUTION; + } + + $suggestion = $this->generateSuggestion(); + if ($suggestion !== null) { + $solution[] = '## Suggestion'; + $solution[] = $suggestion; + } + + $solution[] = <<isControllerWithNonExistAction()) { + return <<definition[0]}` exists, but does not contain method `{$this->definition[1]}()`. + + Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller: + + ```php + public function {$this->definition[1]}(): ResponseInterface + { + // TODO: Implement your action + } + ``` + SOLUTION; + } + + if ($this->isNotMiddlewareClassName()) { + return sprintf( + 'Class `%s` exists, but does not implement `%s`.', + $this->definition, + MiddlewareInterface::class + ); + } + + if ($this->isStringNotClassName()) { + return sprintf( + 'Class `%s` not found. It may be needed to install a package with this middleware.', + $this->definition + ); + } + + return null; + } + + /** + * @psalm-assert-if-true string $this->definition + */ + private function isStringNotClassName(): bool + { + return is_string($this->definition) + && !class_exists($this->definition); + } + + /** + * @psalm-assert-if-true class-string $this->definition + */ + private function isNotMiddlewareClassName(): bool + { + return is_string($this->definition) + && class_exists($this->definition); + } + + /** + * @psalm-assert-if-true array{0:class-string,1:string} $this->definition + */ + private function isControllerWithNonExistAction(): bool + { + return is_array($this->definition) + && array_keys($this->definition) === [0, 1] + && is_string($this->definition[0]) + && class_exists($this->definition[0]); + } + /** * @param mixed $middlewareDefinition */ diff --git a/tests/InvalidMiddlewareDefinitionExceptionTest.php b/tests/InvalidMiddlewareDefinitionExceptionTest.php index 27127ba..3fe70c9 100644 --- a/tests/InvalidMiddlewareDefinitionExceptionTest.php +++ b/tests/InvalidMiddlewareDefinitionExceptionTest.php @@ -17,18 +17,27 @@ public function dataBase(): array [ 'test', '"test"', + 'Class `test` not found', + ], + [ + TestController::class, + '"Yiisoft\Middleware\Dispatcher\Tests\Support\TestController"', + 'Class `Yiisoft\Middleware\Dispatcher\Tests\Support\TestController` exists, but does not implement', ], [ new TestController(), 'an instance of "Yiisoft\Middleware\Dispatcher\Tests\Support\TestController"', + 'Related links', ], [ [TestController::class, 'notExistsAction'], '["Yiisoft\Middleware\Dispatcher\Tests\Support\TestController", "notExistsAction"]', + 'Try adding `notExistsAction()` action to `Yiisoft\Middleware\Dispatcher\Tests\Support\TestController` controller:', ], [ ['class' => TestController::class, 'index'], '["class" => "Yiisoft\Middleware\Dispatcher\Tests\Support\TestController", "index"]', + null, ], ]; } @@ -38,10 +47,13 @@ public function dataBase(): array * * @param mixed $definition */ - public function testBase($definition, string $expected): void + public function testBase($definition, string $expectedMessage, ?string $expectedSolution): void { $exception = new InvalidMiddlewareDefinitionException($definition); - self::assertStringEndsWith('. Got ' . $expected . '.', $exception->getMessage()); + self::assertStringEndsWith('. Got ' . $expectedMessage . '.', $exception->getMessage()); + if ($expectedSolution !== null) { + self::assertStringContainsString($expectedSolution, $exception->getSolution()); + } } public function dataUnknownDefinition(): array @@ -65,4 +77,14 @@ public function testUnknownDefinition($definition): void $exception->getMessage() ); } + + public function testName(): void + { + $exception = new InvalidMiddlewareDefinitionException('test'); + + self::assertSame( + 'Invalid middleware definition', + $exception->getName() + ); + } }