Passed
Push — master ( 1f8125...118867 )
by Sergei
02:25
created

isStringNotClassName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Middleware\Dispatcher;
6
7
use InvalidArgumentException;
8
use Psr\Http\Server\MiddlewareInterface;
9
use Yiisoft\FriendlyException\FriendlyExceptionInterface;
10
11
use function get_class;
12
use function is_array;
13
use function is_object;
14
use function is_string;
15
16
final class InvalidMiddlewareDefinitionException extends InvalidArgumentException implements FriendlyExceptionInterface
17
{
18
    /**
19
     * @var mixed
20
     */
21
    private $definition;
22
    private ?string $definitionString;
23
24
    /**
25
     * @param mixed $middlewareDefinition
26
     */
27 18
    public function __construct($middlewareDefinition)
28
    {
29 18
        $this->definition = $middlewareDefinition;
30 18
        $this->definitionString = $this->convertDefinitionToString($middlewareDefinition);
31
32 18
        $message = 'Parameter should be either PSR middleware class name or a callable.';
33
34 18
        if ($this->definitionString !== null) {
35 14
            $message .= ' Got ' . $this->definitionString . '.';
36
        }
37
38 18
        parent::__construct($message);
39
    }
40
41 1
    public function getName(): string
42
    {
43 1
        return 'Invalid middleware definition';
44
    }
45
46 4
    public function getSolution(): ?string
47
    {
48 4
        $solution = [];
49
50 4
        if ($this->definitionString !== null) {
51 4
            $solution[] = <<<SOLUTION
52
            ## Got definition value
53
54 4
            `{$this->definitionString}`
55
            SOLUTION;
56
        }
57
58 4
        $suggestion = $this->generateSuggestion();
59 4
        if ($suggestion !== null) {
60 3
            $solution[] = '## Suggestion';
61 3
            $solution[] = $suggestion;
62
        }
63
64 4
        $solution[] = <<<SOLUTION
65
        ## Middleware definition examples
66
67
        - PSR middleware class name: `Yiisoft\Session\SessionMiddleware::class`.
68
        - Action in controller: `[App\Backend\UserController::class, 'index']`.
69
70
        ## Related links
71
72
        - [Callable PHP documentation](https://www.php.net/manual/language.types.callable.php)
73
        SOLUTION;
74
75 4
        return implode("\n\n", $solution);
76
    }
77
78 4
    private function generateSuggestion(): ?string
79
    {
80 4
        if ($this->isControllerWithNonExistAction()) {
81
            return <<<SOLUTION
82 1
            Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[1]}()`.
83
84 1
            Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller:
85
86
            ```php
87 1
            public function {$this->definition[1]}(): ResponseInterface
88
            {
89
                // TODO: Implement your action
90
            }
91
            ```
92
            SOLUTION;
93
        }
94
95 3
        if ($this->isNotMiddlewareClassName()) {
96 1
            return sprintf(
97
                'Class `%s` exists, but does not implement `%s`.',
98 1
                $this->definition,
99
                MiddlewareInterface::class
100
            );
101
        }
102
103 2
        if ($this->isStringNotClassName()) {
104 1
            return sprintf(
105
                'Class `%s` not found. It may be needed to install a package with this middleware.',
106 1
                $this->definition
107
            );
108
        }
109
110 1
        return null;
111
    }
112
113
    /**
114
     * @psalm-assert-if-true string $this->definition
115
     */
116 2
    private function isStringNotClassName(): bool
117
    {
118 2
        return is_string($this->definition)
119 2
            && !class_exists($this->definition);
120
    }
121
122
    /**
123
     * @psalm-assert-if-true class-string $this->definition
124
     */
125 3
    private function isNotMiddlewareClassName(): bool
126
    {
127 3
        return is_string($this->definition)
128 3
            && class_exists($this->definition);
129
    }
130
131
    /**
132
     * @psalm-assert-if-true array{0:class-string,1:string} $this->definition
133
     */
134 4
    private function isControllerWithNonExistAction(): bool
135
    {
136 4
        return is_array($this->definition)
137 4
            && array_keys($this->definition) === [0, 1]
138 4
            && is_string($this->definition[0])
139 4
            && class_exists($this->definition[0]);
140
    }
141
142
    /**
143
     * @param mixed $middlewareDefinition
144
     */
145 18
    private function convertDefinitionToString($middlewareDefinition): ?string
146
    {
147 18
        if (is_object($middlewareDefinition)) {
148 3
            return 'an instance of "' . get_class($middlewareDefinition) . '"';
149
        }
150
151 15
        if (is_string($middlewareDefinition)) {
152 5
            return '"' . $middlewareDefinition . '"';
153
        }
154
155 10
        if (is_array($middlewareDefinition)) {
156 9
            $items = $middlewareDefinition;
157 9
            foreach ($middlewareDefinition as $item) {
158 9
                if (!is_string($item)) {
159 3
                    return null;
160
                }
161
            }
162 6
            array_walk(
163
                $items,
164
                /**
165
                 * @param mixed $item
166
                 * @psalm-param array-key $key
167
                 */
168 6
                static function (&$item, $key) {
169 6
                    $item = (string)$item;
170 6
                    $item = '"' . $item . '"';
171 6
                    if (is_string($key)) {
172 2
                        $item = '"' . $key . '" => ' . $item;
173
                    }
174
                }
175
            );
176
            /** @var string[] $items */
177 6
            return '[' . implode(', ', $items) . ']';
178
        }
179
180 1
        return null;
181
    }
182
}
183