Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify return type error #94

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
153 changes: 153 additions & 0 deletions src/Exception/AbstractInvalidMiddlewareException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher\Exception;

use InvalidArgumentException;
use Psr\Http\Server\MiddlewareInterface;
use Throwable;
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Helpers\DefinitionValidator;
use Yiisoft\FriendlyException\FriendlyExceptionInterface;
use Yiisoft\Middleware\Dispatcher\Helper\DefinitionHelper;

use function is_array;

abstract class AbstractInvalidMiddlewareException extends InvalidArgumentException implements FriendlyExceptionInterface
{
protected string $definitionString;

public function __construct(
protected mixed $definition,
string $message,
?Throwable $previous = null,
) {
$this->definitionString = DefinitionHelper::convertDefinitionToString($definition);

parent::__construct($message, 0, $previous);

Check warning on line 28 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ public function __construct(protected mixed $definition, string $message, ?Throwable $previous = null) { $this->definitionString = DefinitionHelper::convertDefinitionToString($definition); - parent::__construct($message, 0, $previous); + parent::__construct($message, -1, $previous); } public function getSolution() : ?string {

Check warning on line 28 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ public function __construct(protected mixed $definition, string $message, ?Throwable $previous = null) { $this->definitionString = DefinitionHelper::convertDefinitionToString($definition); - parent::__construct($message, 0, $previous); + parent::__construct($message, 1, $previous); } public function getSolution() : ?string {

Check warning on line 28 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ public function __construct(protected mixed $definition, string $message, ?Throwable $previous = null) { $this->definitionString = DefinitionHelper::convertDefinitionToString($definition); - parent::__construct($message, 0, $previous); + parent::__construct($message, -1, $previous); } public function getSolution() : ?string {

Check warning on line 28 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ public function __construct(protected mixed $definition, string $message, ?Throwable $previous = null) { $this->definitionString = DefinitionHelper::convertDefinitionToString($definition); - parent::__construct($message, 0, $previous); + parent::__construct($message, 1, $previous); } public function getSolution() : ?string {
}

public function getSolution(): ?string
{
$solution = [

Check warning on line 33 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ } public function getSolution() : ?string { - $solution = [<<<SOLUTION -## Got definition value - -`{$this->definitionString}` -SOLUTION -]; + $solution = []; $suggestion = $this->generateSuggestion(); if ($suggestion !== null) { $solution[] = '## Suggestion';

Check warning on line 33 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ } public function getSolution() : ?string { - $solution = [<<<SOLUTION -## Got definition value - -`{$this->definitionString}` -SOLUTION -]; + $solution = []; $suggestion = $this->generateSuggestion(); if ($suggestion !== null) { $solution[] = '## Suggestion';
<<<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:

```php
Yiisoft\Session\SessionMiddleware::class
```

PSR middleware array definition:

```php
[
'class' => MyMiddleware::class,
'__construct()' => [
'someVar' => 42,
],
]
```

Closure that returns `ResponseInterface`:

```php
static function (): ResponseInterface {
return new Response(418);
},
```

Closure that returns `MiddlewareInterface`:

```php
static function (): MiddlewareInterface {
return new TestMiddleware();
}
```

Action in controller:

```php
[App\Backend\UserController::class, 'index']
```

## Related links

- [Array definition syntax](https://github.com/yiisoft/definitions#arraydefinition)
- [Callable PHP documentation](https://www.php.net/manual/language.types.callable.php)
SOLUTION;

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

private function generateSuggestion(): ?string
{
if (DefinitionHelper::isControllerWithNonExistAction($this->definition)) {
return <<<SOLUTION
Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[1]}()`.

Check warning on line 102 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ { if (DefinitionHelper::isControllerWithNonExistAction($this->definition)) { return <<<SOLUTION -Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[1]}()`. +Class `{$this->definition[1]}` exists, but does not contain method `{$this->definition[1]}()`. Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller:

Check warning on line 102 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ { if (DefinitionHelper::isControllerWithNonExistAction($this->definition)) { return <<<SOLUTION -Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[1]}()`. +Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[0]}()`. Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller:

Check warning on line 102 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ { if (DefinitionHelper::isControllerWithNonExistAction($this->definition)) { return <<<SOLUTION -Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[1]}()`. +Class `{$this->definition[1]}` exists, but does not contain method `{$this->definition[1]}()`. Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller:

Check warning on line 102 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ { if (DefinitionHelper::isControllerWithNonExistAction($this->definition)) { return <<<SOLUTION -Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[1]}()`. +Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[0]}()`. Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller:

Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller:

```php
public function {$this->definition[1]}(): ResponseInterface

Check warning on line 107 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller: ```php -public function {$this->definition[1]}(): ResponseInterface +public function {$this->definition[0]}(): ResponseInterface { // TODO: Implement your action }

Check warning on line 107 in src/Exception/AbstractInvalidMiddlewareException.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller: ```php -public function {$this->definition[1]}(): ResponseInterface +public function {$this->definition[0]}(): ResponseInterface { // TODO: Implement your action }
{
// TODO: Implement your action
}
```
SOLUTION;
}

if (DefinitionHelper::isNotMiddlewareClassName($this->definition)) {
return sprintf(
'Class `%s` exists, but does not implement `%s`.',
$this->definition,
MiddlewareInterface::class
);
}

if (DefinitionHelper::isStringNotClassName($this->definition)) {
return sprintf(
'Class `%s` not found. It may be needed to install a package with this middleware.',
$this->definition
);
}

if (is_array($this->definition)) {
try {
DefinitionValidator::validateArrayDefinition($this->definition);
} catch (InvalidConfigException $e) {
return <<<SOLUTION
You may have an error in array definition. Array definition validation result:

```
{$e->getMessage()}
```
SOLUTION;
}

/** @psalm-suppress MixedArgument In valid array definition element "class" always is string */
return sprintf(
'Array definition is valid, class `%s` exists, but does not implement `%s`.',
$this->definition['class'],
MiddlewareInterface::class
);
}

return null;
}
}
39 changes: 39 additions & 0 deletions src/Exception/InvalidMiddlewareReturnTypeException.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need add solution for friendly exception also. Solution from AbstractInvalidMiddlewareException does not fit here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher\Exception;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Throwable;
use Yiisoft\Middleware\Dispatcher\Helper\DefinitionHelper;
use Yiisoft\Middleware\Dispatcher\Helper\ResponseHelper;

final class InvalidMiddlewareReturnTypeException extends AbstractInvalidMiddlewareException
{
public function __construct(
mixed $definition,
private readonly mixed $result,
?Throwable $previous = null,
) {
$this->definitionString = DefinitionHelper::convertDefinitionToString($definition);

parent::__construct(
$definition,
sprintf(
'Middleware %s must return an instance of `%s` or `%s`, %s returned.',
$this->definitionString,
MiddlewareInterface::class,
ResponseInterface::class,
ResponseHelper::convertToString($this->result),
),
$previous,
);
}

public function getName(): string
{
return sprintf('Invalid middleware result type %s', get_debug_type($this->result));
}
}
94 changes: 94 additions & 0 deletions src/Helper/DefinitionHelper.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This methods need for excpetion only. May be keep thier in AbstractInvalidMiddlewareException but change visibility from private to protected?

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher\Helper;

use function array_slice;
use function count;
use function gettype;
use function is_array;
use function is_bool;
use function is_float;
use function is_int;
use function is_object;
use function is_string;

final class DefinitionHelper
{
/**
* @psalm-assert-if-true string $definition
*/
public static function isStringNotClassName(mixed $definition): bool
{
return is_string($definition)
&& !class_exists($definition);
}

/**
* @psalm-assert-if-true class-string $definition
*/
public static function isNotMiddlewareClassName(mixed $definition): bool
{
return is_string($definition)
&& class_exists($definition);
}

/**
* @psalm-assert-if-true array{0:class-string,1:string} $definition
*/
public static function isControllerWithNonExistAction(mixed $definition): bool
{
return is_array($definition)
&& array_keys($definition) === [0, 1]
&& is_string($definition[0])
&& class_exists($definition[0]);
}

public static function convertDefinitionToString(mixed $middlewareDefinition): string
{
if (is_object($middlewareDefinition)) {
return 'an instance of `' . $middlewareDefinition::class . '`';
}

if (is_string($middlewareDefinition)) {
return '"' . $middlewareDefinition . '"';
}

if (is_array($middlewareDefinition)) {
$items = [];
/** @var mixed $value */
foreach (array_slice($middlewareDefinition, 0, 2) as $key => $value) {
$items[] = (is_string($key) ? '"' . $key . '" => ' : '') . self::convertToString($value);
}
return '[' . implode(', ', $items) . (count($middlewareDefinition) > 2 ? ', ...' : '') . ']';
}

return self::convertToString($middlewareDefinition);
}

private static function convertToString(mixed $value): string
{
if (is_string($value)) {
return '"' . $value . '"';
}

if (is_int($value) || is_float($value)) {
return (string) $value;
}

if (is_bool($value)) {
return $value ? 'true' : 'false';
}

if ($value === null) {
return '"null"';
}

if (is_object($value)) {
return sprintf('"%s"', $value::class);
}

return sprintf('"%s"', gettype($value));
}
}
57 changes: 57 additions & 0 deletions src/Helper/ResponseHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Middleware\Dispatcher\Helper;

use function gettype;
use function is_array;
use function is_bool;
use function is_object;

final class ResponseHelper
{
public static function convertToString(mixed $response): string
{
if (is_object($response)) {
return sprintf('"%s" (object)', $response::class);
}

if (is_array($response)) {
$items = [];

if (!array_is_list($response)) {
/** @var mixed $value */
foreach (array_keys($response) as $key) {
$items[] = sprintf('"%s" => ...', $key);
}
} else {
$items[] = '...';
}
return sprintf(
'"[%s]" (array of %d%s)',
implode(', ', $items),
$count = count($response),
$count === 1 ? ' element' : ' elements'
);
}

if (is_bool($response)) {
return sprintf('"%s" (bool)', $response ? 'true' : 'false');
}

if (is_scalar($response)) {
return sprintf(
'"%s" (%s)',
(string)$response,
match (true) {
is_int($response) => 'int',
is_float($response) => 'float',
default => gettype($response)
}
);
}

return sprintf('"%s" (%s)', gettype($response), get_debug_type($response));
}
}
Loading
Loading