Passed
Push — main ( 3d6ec5...ba44ab )
by Dimitri
03:04
created

MiddlewareQueue::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Http;
13
14
use BlitzPHP\Container\Container;
15
use BlitzPHP\Middlewares\BaseMiddleware;
16
use BlitzPHP\Middlewares\ClosureDecorator;
17
use Closure;
18
use Countable;
19
use InvalidArgumentException;
20
use LogicException;
21
use OutOfBoundsException;
22
use Psr\Http\Server\MiddlewareInterface;
23
use SeekableIterator;
24
25
class MiddlewareQueue implements Countable, SeekableIterator
26
{
27
    /**
28
     * Middlewares a executer pour la requete courante
29
     *
30
     * @var array<int, mixed>
31
     */
32
    protected array $queue = [];
33
34
    /**
35
     * Index du middleware actuellement executer
36
     */
37
    protected int $position = 0;
38
39
    /**
40
     * Aliases des middlewares
41
     */
42
    protected array $aliases = [];
43
44
    /**
45
     * Constructor
46
     *
47
     * @param array $middleware Liste des middlewares initiaux
48
     */
49
    public function __construct(protected Container $container, array $middleware = [], protected ?Request $request = null, protected ?Response $response = null)
50
    {
51 14
        $this->queue    = $middleware;
52 14
        $this->request  = $request ?: $this->container->get(Request::class);
53 14
        $this->response = $response ?: $this->container->get(Response::class);
54
    }
55
56
    /**
57
     * Ajoute un alias de middleware
58
     */
59
    public function alias(string $alias, Closure|MiddlewareInterface|string $middleware): static
60
    {
61 2
        return $this->aliases([$alias => $middleware]);
62
    }
63
64
    /**
65
     * Ajoute des alias de middlewares
66
     *
67
     * @param array<string, Closure|MiddlewareInterface|string> $aliases
68
     */
69
    public function aliases(array $aliases): static
70
    {
71 2
        $this->aliases = array_merge($this->aliases, $aliases);
72
73 2
        return $this;
74
    }
75
76
    /**
77
     * Ajoute un middleware a la chaine d'execution
78
     */
79
    public function add(array|Closure|MiddlewareInterface|string $middleware): static
80
    {
81
        if (is_array($middleware)) {
0 ignored issues
show
introduced by
The condition is_array($middleware) is always true.
Loading history...
82 2
            $this->queue = array_merge($this->queue, $middleware);
83
84 2
            return $this;
85
        }
86 12
        $this->queue[] = $middleware;
87
88 12
        return $this;
89
    }
90
91
    /**
92
     * Alias pour MiddlewareQueue::add().
93
     *
94
     * @see MiddlewareQueue::add()
95
     */
96
    public function push(array|Closure|MiddlewareInterface|string $middleware): static
97
    {
98 4
        return $this->add($middleware);
99
    }
100
101
    /**
102
     * Ajoute un middleware en bout de chaine
103
     *
104
     * Alias pour MiddlewareQueue::add().
105
     *
106
     * @see MiddlewareQueue::add()
107
     */
108
    public function append(array|Closure|MiddlewareInterface|string $middleware): static
109
    {
110 2
        return $this->add($middleware);
111
    }
112
113
    /**
114
     * Ajoute un middleware en debut de chaine
115
     */
116
    public function prepend(array|Closure|MiddlewareInterface|string $middleware): static
117
    {
118
        if (is_array($middleware)) {
0 ignored issues
show
introduced by
The condition is_array($middleware) is always true.
Loading history...
119 2
            $this->queue = array_merge($middleware, $this->queue);
120
121 2
            return $this;
122
        }
123 2
        array_unshift($this->queue, $middleware);
124
125 2
        return $this;
126
    }
127
128
    /**
129
     * insert un middleware a une position donnee.
130
     *
131
     * Alias pour MiddlewareQueue::add().
132
     *
133
     * @param int $index La position où le middleware doit être insérer.
134
     *
135
     * @see MiddlewareQueue::add()
136
     */
137
    public function insert(int $index, Closure|MiddlewareInterface|string $middleware): static
138
    {
139
        return $this->insertAt($index, $middleware);
140
    }
141
142
    /**
143
     * Insérez un middleware appelable à un index spécifique.
144
     *
145
     * Si l'index existe déjà, le nouvel appelable sera inséré,
146
     * et l'élément existant sera décalé d'un indice supérieur.
147
     *
148
     * @param int $index La position où le middleware doit être insérer.
149
     */
150
    public function insertAt(int $index, Closure|MiddlewareInterface|string $middleware): static
151
    {
152 4
        array_splice($this->queue, $index, 0, [$middleware]);
153
154 4
        return $this;
155
    }
156
157
    /**
158
     * Insérez un objet middleware avant la première classe correspondante.
159
     *
160
     * Trouve l'index du premier middleware qui correspond à la classe fournie,
161
     * et insère le middleware fourni avant.
162
     *
163
     * @param string $class Le nom de classe pour insérer le middleware avant.
164
     *
165
     * @throws LogicException Si le middleware à insérer avant n'est pas trouvé.
166
     */
167
    public function insertBefore(string $class, Closure|MiddlewareInterface|string $middleware): static
168
    {
169 4
        $found = false;
170 4
        $i     = 0;
171
172
        if (array_key_exists($class, $this->aliases) && is_string($this->aliases[$class])) {
173 2
            $class = $this->aliases[$class];
174
        }
175
176
        foreach ($this->queue as $i => $object) {
177
            if ((is_string($object) && $object === $class) || is_a($object, $class)) {
178 4
                $found = true;
179 4
                break;
180
            }
181
        }
182
183
        if ($found) {
184 4
            return $this->insertAt($i, $middleware);
185
        }
186
187 2
        throw new LogicException(sprintf("No middleware matching '%s' could be found.", $class));
188
    }
189
190
    /**
191
     * Insérez un objet middleware après la première classe correspondante.
192
     *
193
     * Trouve l'index du premier middleware qui correspond à la classe fournie,
194
     * et insère le callback fourni après celui-ci. Si la classe n'est pas trouvée,
195
     * cette méthode se comportera comme add().
196
     *
197
     * @param string $class Le nom de classe pour insérer le middleware après.
198
     */
199
    public function insertAfter(string $class, Closure|MiddlewareInterface|string $middleware): static
200
    {
201 4
        $found = false;
202 4
        $i     = 0;
203
204
        if (array_key_exists($class, $this->aliases) && is_string($this->aliases[$class])) {
205 2
            $class = $this->aliases[$class];
206
        }
207
208
        foreach ($this->queue as $i => $object) {
209
            if ((is_string($object) && $object === $class) || is_a($object, $class)) {
210 4
                $found = true;
211 4
                break;
212
            }
213
        }
214
215
        if ($found) {
216 4
            return $this->insertAt($i + 1, $middleware);
217
        }
218
219 2
        return $this->add($middleware);
220
    }
221
222
    /**
223
     * Obtenir le nombre de couches middleware connectés.
224
     */
225
    public function count(): int
226
    {
227 8
        return count($this->queue);
228
    }
229
230
    /**
231
     * {@inheritDoc}
232
     */
233
    public function seek(int $position): void
234
    {
235
        if (! isset($this->queue[$position])) {
236 2
            throw new OutOfBoundsException(sprintf('Invalid seek position (%s).', $position));
237
        }
238
239 2
        $this->position = $position;
240
    }
241
242
    /**
243
     * {@inheritDoc}
244
     */
245
    public function rewind(): void
246
    {
247 4
        $this->position = 0;
248
    }
249
250
    /**
251
     *  {@inheritDoc}
252
     */
253
    public function current(): MiddlewareInterface
254
    {
255
        if (! isset($this->queue[$this->position])) {
256 2
            throw new OutOfBoundsException(sprintf('Position actuelle non valide (%s).', $this->position));
257
        }
258
259
        if ($this->queue[$this->position] instanceof MiddlewareInterface) {
260 6
            return $this->queue[$this->position];
261
        }
262
263 14
        return $this->queue[$this->position] = $this->resolve($this->queue[$this->position]);
264
    }
265
266
    /**
267
     * {@inheritDoc}
268
     */
269
    public function key(): int
270
    {
271
        return $this->position;
272
    }
273
274
    /**
275
     * Passe la position actuelle au middleware suivant.
276
     */
277
    public function next(): void
278
    {
279 8
        $this->position++;
280
    }
281
282
    /**
283
     * Vérifie si la position actuelle est valide.
284
     */
285
    public function valid(): bool
286
    {
287 2
        return isset($this->queue[$this->position]);
288
    }
289
290
    /**
291
     * Enregistre les middlewares definis dans le gestionnaire des middlewares
292
     *
293
     * @internal
294
     */
295
    public function register(array $config)
296
    {
297
        $config += [
298
            'aliases' => [],
299
            'globals' => [],
300
            'build'   => static fn () => null,
301 2
        ];
302
303 2
        $this->aliases($config['aliases']);
304
305
        foreach ($config['globals'] as $middleware) {
306 2
            $this->add($middleware);
307
        }
308
309
        if (is_callable($build = $config['build'])) {
310
            $this->container->call($build, [
311
                'request' => $this->request,
312
                'queue'   => $this,
313 2
            ]);
314
        }
315
    }
316
317
    /**
318
     * {@internal}
319
     */
320
    public function response(): Response
321
    {
322 2
        return $this->response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->response could return the type null which is incompatible with the type-hinted return BlitzPHP\Http\Response. Consider adding an additional type-check to rule them out.
Loading history...
323
    }
324
325
    /**
326
     * Résoudre le nom middleware à une instance de middleware compatible PSR 15.
327
     *
328
     * @throws InvalidArgumentException si Middleware introuvable.
329
     */
330
    protected function resolve(Closure|MiddlewareInterface|string $middleware): MiddlewareInterface
331
    {
332
        if (is_string($middleware)) {
333 8
            [$middleware, $options] = explode(':', $middleware) + [1 => null];
334
335
            if (isset($this->aliases[$middleware])) {
336 2
                $middleware = $this->aliases[$middleware];
337
            }
338
339
            if ($this->container->has($middleware)) {
340 8
                $middleware = $this->container->get($middleware);
341
            } else {
342
                throw new InvalidArgumentException(sprintf(
343
                    'Middleware, `%s` n\'a pas été trouvé.',
344
                    $middleware
345 2
                ));
346
            }
347
348
            if ($middleware instanceof BaseMiddleware) {
349 8
                $middleware->fill(explode(',', $options))->init($this->request->getPath());
0 ignored issues
show
Bug introduced by
The method getPath() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

349
                $middleware->fill(explode(',', $options))->init($this->request->/** @scrutinizer ignore-call */ getPath());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
350
            }
351
        }
352
353
        if ($middleware instanceof MiddlewareInterface) {
354 8
            return $middleware;
355
        }
356
357 12
        return new ClosureDecorator($middleware, $this->response);
0 ignored issues
show
Bug introduced by
It seems like $middleware can also be of type string; however, parameter $callable of BlitzPHP\Middlewares\Clo...ecorator::__construct() does only seem to accept Closure, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

357
        return new ClosureDecorator(/** @scrutinizer ignore-type */ $middleware, $this->response);
Loading history...
358
    }
359
}
360