Skip to content

Commit

Permalink
Implement friendly exception for `InvalidMiddlewareDefinitionExceptio…
Browse files Browse the repository at this point in the history
…n` (#50)
  • Loading branch information
vjik authored Jun 7, 2022
1 parent 1f8125c commit 1188670
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 10 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
121 changes: 116 additions & 5 deletions src/InvalidMiddlewareDefinitionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = <<<SOLUTION
## Got definition value
`{$this->definitionString}`
SOLUTION;
}

$suggestion = $this->generateSuggestion();
if ($suggestion !== null) {
$solution[] = '## Suggestion';
$solution[] = $suggestion;
}

$solution[] = <<<SOLUTION
## Middleware definition examples
- PSR middleware class name: `Yiisoft\Session\SessionMiddleware::class`.
- Action in controller: `[App\Backend\UserController::class, 'index']`.
## Related links
- [Callable PHP documentation](https://www.php.net/manual/language.types.callable.php)
SOLUTION;

return implode("\n\n", $solution);
}

private function generateSuggestion(): ?string
{
if ($this->isControllerWithNonExistAction()) {
return <<<SOLUTION
Class `{$this->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
*/
Expand Down
26 changes: 24 additions & 2 deletions tests/InvalidMiddlewareDefinitionExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
],
];
}
Expand All @@ -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
Expand All @@ -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()
);
}
}

0 comments on commit 1188670

Please sign in to comment.