Passed
Push — master ( 118867...5c417f )
by Sergei
02:29
created

convertToString()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 8

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 23
ccs 12
cts 12
cp 1
rs 8.4444
cc 8
nc 7
nop 1
crap 8
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\Definitions\Exception\InvalidConfigException;
10
use Yiisoft\Definitions\Helpers\DefinitionValidator;
11
use Yiisoft\FriendlyException\FriendlyExceptionInterface;
12
13
use function array_slice;
14
use function count;
15
use function get_class;
16
use function gettype;
17
use function is_array;
18
use function is_bool;
19
use function is_float;
20
use function is_int;
21
use function is_object;
22
use function is_string;
23
24
final class InvalidMiddlewareDefinitionException extends InvalidArgumentException implements FriendlyExceptionInterface
25
{
26
    /**
27
     * @var mixed
28
     */
29
    private $definition;
30
    private string $definitionString;
31
32
    /**
33
     * @param mixed $middlewareDefinition
34
     */
35 23
    public function __construct($middlewareDefinition)
36
    {
37 23
        $this->definition = $middlewareDefinition;
38 23
        $this->definitionString = $this->convertDefinitionToString($middlewareDefinition);
39
40 23
        parent::__construct(
41 23
            'Parameter should be either PSR middleware class name or a callable. Got ' . $this->definitionString . '.'
42
        );
43
    }
44
45 1
    public function getName(): string
46
    {
47 1
        return 'Invalid middleware definition';
48
    }
49
50 6
    public function getSolution(): ?string
51
    {
52 6
        $solution = [
53
            <<<SOLUTION
54
            ## Got definition value
55
56 6
            `{$this->definitionString}`
57
            SOLUTION
58
        ];
59
60 6
        $suggestion = $this->generateSuggestion();
61 6
        if ($suggestion !== null) {
62 5
            $solution[] = '## Suggestion';
63 5
            $solution[] = $suggestion;
64
        }
65
66 6
        $solution[] = <<<SOLUTION
67
        ## Middleware definition examples
68
69
        PSR middleware class name:
70
71
        ```php
72
        Yiisoft\Session\SessionMiddleware::class
73
        ```
74
75
        PSR middleware array definition:
76
77
        ```php
78
        [
79
            'class' => MyMiddleware::class,
80
            '__construct()' => [
81
                'someVar' => 42,
82
            ],
83
        ]
84
        ```
85
86
        Closure that returns `ResponseInterface`:
87
88
        ```php
89
        static function (): ResponseInterface {
90
            return new Response(418);
91
        },
92
        ```
93
94
        Closure that returns `MiddlewareInterface`:
95
96
        ```php
97
        static function (): MiddlewareInterface {
98
            return new TestMiddleware();
99
        }
100
        ```
101
102
        Action in controller:
103
104
        ```php
105
        [App\Backend\UserController::class, 'index']
106
        ```
107
108
        ## Related links
109
110
        - [Array definition syntax](https://github.com/yiisoft/definitions#arraydefinition)
111
        - [Callable PHP documentation](https://www.php.net/manual/language.types.callable.php)
112
        SOLUTION;
113
114 6
        return implode("\n\n", $solution);
115
    }
116
117 6
    private function generateSuggestion(): ?string
118
    {
119 6
        if ($this->isControllerWithNonExistAction()) {
120
            return <<<SOLUTION
121 1
            Class `{$this->definition[0]}` exists, but does not contain method `{$this->definition[1]}()`.
122
123 1
            Try adding `{$this->definition[1]}()` action to `{$this->definition[0]}` controller:
124
125
            ```php
126 1
            public function {$this->definition[1]}(): ResponseInterface
127
            {
128
                // TODO: Implement your action
129
            }
130
            ```
131
            SOLUTION;
132
        }
133
134 5
        if ($this->isNotMiddlewareClassName()) {
135 1
            return sprintf(
136
                'Class `%s` exists, but does not implement `%s`.',
137 1
                $this->definition,
138
                MiddlewareInterface::class
139
            );
140
        }
141
142 4
        if ($this->isStringNotClassName()) {
143 1
            return sprintf(
144
                'Class `%s` not found. It may be needed to install a package with this middleware.',
145 1
                $this->definition
146
            );
147
        }
148
149 3
        if (is_array($this->definition)) {
150
            try {
151 2
                DefinitionValidator::validateArrayDefinition($this->definition);
152 1
            } catch (InvalidConfigException $e) {
153
                return <<<SOLUTION
154
                You may have an error in array definition. Array definition validation result:
155
156
                ```
157 1
                {$e->getMessage()}
158
                ```
159
                SOLUTION;
160
            }
161
162
            /** @psalm-suppress MixedArgument In valid array definition element "class" always is string */
163 1
            return sprintf(
164
                'Array definition is valid, class `%s` exists, but does not implement `%s`.',
165 1
                $this->definition['class'],
166
                MiddlewareInterface::class
167
            );
168
        }
169
170 1
        return null;
171
    }
172
173
    /**
174
     * @psalm-assert-if-true string $this->definition
175
     */
176 4
    private function isStringNotClassName(): bool
177
    {
178 4
        return is_string($this->definition)
179 4
            && !class_exists($this->definition);
180
    }
181
182
    /**
183
     * @psalm-assert-if-true class-string $this->definition
184
     */
185 5
    private function isNotMiddlewareClassName(): bool
186
    {
187 5
        return is_string($this->definition)
188 5
            && class_exists($this->definition);
189
    }
190
191
    /**
192
     * @psalm-assert-if-true array{0:class-string,1:string} $this->definition
193
     */
194 6
    private function isControllerWithNonExistAction(): bool
195
    {
196 6
        return is_array($this->definition)
197 6
            && array_keys($this->definition) === [0, 1]
198 6
            && is_string($this->definition[0])
199 6
            && class_exists($this->definition[0]);
200
    }
201
202
    /**
203
     * @param mixed $middlewareDefinition
204
     */
205 23
    private function convertDefinitionToString($middlewareDefinition): string
206
    {
207 23
        if (is_object($middlewareDefinition)) {
208 3
            return 'an instance of "' . get_class($middlewareDefinition) . '"';
209
        }
210
211 20
        if (is_string($middlewareDefinition)) {
212 5
            return '"' . $middlewareDefinition . '"';
213
        }
214
215 15
        if (is_array($middlewareDefinition)) {
216 12
            $items = [];
217
            /** @var mixed $value */
218 12
            foreach (array_slice($middlewareDefinition, 0, 2) as $key => $value) {
219 12
                $items[] = (is_string($key) ? '"' . $key . '" => ' : '') . $this->convertToString($value);
220
            }
221 12
            return '[' . implode(', ', $items) . (count($middlewareDefinition) > 2 ? ', ...' : '') . ']';
222
        }
223
224 3
        return $this->convertToString($middlewareDefinition);
225
    }
226
227
    /**
228
     * @param mixed $value
229
     */
230 15
    private function convertToString($value): string
231
    {
232 15
        if (is_string($value)) {
233 9
            return '"' . $value . '"';
234
        }
235
236 7
        if (is_int($value) || is_float($value)) {
237 2
            return (string) $value;
238
        }
239
240 5
        if (is_bool($value)) {
241 2
            return $value ? 'true' : 'false';
242
        }
243
244 3
        if ($value === null) {
245 1
            return 'null';
246
        }
247
248 3
        if (is_object($value)) {
249 2
            return get_class($value);
250
        }
251
252 1
        return gettype($value);
253
    }
254
}
255