Issues (536)

src/Http/MiddlewareQueue.php (5 issues)

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
     * Index du middleware actuellement executer
29
     */
30
    protected int $position = 0;
31
32
    /**
33
     * Aliases des middlewares
34
     */
35
    protected array $aliases = [];
36
37
    /**
38
     * Groupes de middlewares
39
     *
40
     * @var array<string, array>
41
     */
42
    protected array $groups = [];
43
44
    /**
45
     * Constructor
46
     *
47
     * @param array $queue Liste des middlewares a executer pour la requete courante
48
     */
49
    public function __construct(protected Container $container, protected array $queue = [], protected ?Request $request = null, protected ?Response $response = null)
50
    {
51 14
        $this->request  = $request ?: $this->container->get(Request::class);
52 14
        $this->response = $response ?: $this->container->get(Response::class);
53
    }
54
55
    /**
56
     * Ajoute un alias de middleware
57
     */
58
    public function alias(string $alias, Closure|MiddlewareInterface|string $middleware): static
59
    {
60 2
        return $this->aliases([$alias => $middleware]);
61
    }
62
63
    /**
64
     * Ajoute des alias de middlewares
65
     *
66
     * @param array<string, Closure|MiddlewareInterface|string> $aliases
67
     */
68
    public function aliases(array $aliases): static
69
    {
70 2
        $this->aliases = array_merge($this->aliases, $aliases);
71
72 2
        return $this;
73
    }
74
75
    /**
76
     * Ajoute des groupes de middlewares
77
     *
78
     * @param array<string, array> $groups
79
     */
80
    public function groups(array $groups): static
81
    {
82 4
        $this->groups = array_merge($this->groups, $groups);
83
84 4
        return $this;
85
    }
86
87
    /**
88
     * Ajoute un middleware a la chaine d'execution
89
     */
90
    public function add(array|Closure|MiddlewareInterface|string $middleware): static
91
    {
92
        if (is_array($middleware)) {
0 ignored issues
show
The condition is_array($middleware) is always true.
Loading history...
93 4
            $this->queue = array_merge($this->queue, $middleware);
94
95 4
            return $this;
96
        }
97 12
        $this->queue[] = $middleware;
98
99 12
        return $this;
100
    }
101
102
    /**
103
     * Alias pour MiddlewareQueue::add().
104
     *
105
     * @see MiddlewareQueue::add()
106
     */
107
    public function push(array|Closure|MiddlewareInterface|string $middleware): static
108
    {
109 4
        return $this->add($middleware);
110
    }
111
112
    /**
113
     * Ajoute un middleware en bout de chaine
114
     *
115
     * Alias pour MiddlewareQueue::add().
116
     *
117
     * @see MiddlewareQueue::add()
118
     */
119
    public function append(array|Closure|MiddlewareInterface|string $middleware): static
120
    {
121 2
        return $this->add($middleware);
122
    }
123
124
    /**
125
     * Ajoute un middleware en debut de chaine
126
     */
127
    public function prepend(array|Closure|MiddlewareInterface|string $middleware): static
128
    {
129
        if (is_array($middleware)) {
0 ignored issues
show
The condition is_array($middleware) is always true.
Loading history...
130 2
            $this->queue = array_merge($middleware, $this->queue);
131
132 2
            return $this;
133
        }
134 2
        array_unshift($this->queue, $middleware);
135
136 2
        return $this;
137
    }
138
139
    /**
140
     * insert un middleware a une position donnee.
141
     *
142
     * Alias pour MiddlewareQueue::add().
143
     *
144
     * @param int $index La position où le middleware doit être insérer.
145
     *
146
     * @see MiddlewareQueue::add()
147
     */
148
    public function insert(int $index, Closure|MiddlewareInterface|string $middleware): static
149
    {
150
        return $this->insertAt($index, $middleware);
151
    }
152
153
    /**
154
     * Insérez un middleware appelable à un index spécifique.
155
     *
156
     * Si l'index existe déjà, le nouvel appelable sera inséré,
157
     * et l'élément existant sera décalé d'un indice supérieur.
158
     *
159
     * @param int $index La position où le middleware doit être insérer.
160
     */
161
    public function insertAt(int $index, Closure|MiddlewareInterface|string $middleware): static
162
    {
163 6
        array_splice($this->queue, $index, 0, [$middleware]);
164
165 6
        return $this;
166
    }
167
168
    /**
169
     * Insérez un objet middleware avant la première classe correspondante.
170
     *
171
     * Trouve l'index du premier middleware qui correspond à la classe fournie,
172
     * et insère le middleware fourni avant.
173
     *
174
     * @param string $class Le nom de classe pour insérer le middleware avant.
175
     *
176
     * @throws LogicException Si le middleware à insérer avant n'est pas trouvé.
177
     */
178
    public function insertBefore(string $class, Closure|MiddlewareInterface|string $middleware): static
179
    {
180 4
        $found = false;
181 4
        $i     = 0;
182
183
        if (array_key_exists($class, $this->aliases) && is_string($this->aliases[$class])) {
184 2
            $class = $this->aliases[$class];
185
        }
186
187
        foreach ($this->queue as $i => $object) {
188
            if ((is_string($object) && $object === $class) || is_a($object, $class)) {
189 4
                $found = true;
190 4
                break;
191
            }
192
        }
193
194
        if ($found) {
195 4
            return $this->insertAt($i, $middleware);
196
        }
197
198 2
        throw new LogicException(sprintf("No middleware matching '%s' could be found.", $class));
199
    }
200
201
    /**
202
     * Insérez un objet middleware après la première classe correspondante.
203
     *
204
     * Trouve l'index du premier middleware qui correspond à la classe fournie,
205
     * et insère le callback fourni après celui-ci. Si la classe n'est pas trouvée,
206
     * cette méthode se comportera comme add().
207
     *
208
     * @param string $class Le nom de classe pour insérer le middleware après.
209
     */
210
    public function insertAfter(string $class, Closure|MiddlewareInterface|string $middleware): static
211
    {
212 4
        $found = false;
213 4
        $i     = 0;
214
215
        if (array_key_exists($class, $this->aliases) && is_string($this->aliases[$class])) {
216 2
            $class = $this->aliases[$class];
217
        }
218
219
        foreach ($this->queue as $i => $object) {
220
            if ((is_string($object) && $object === $class) || is_a($object, $class)) {
221 4
                $found = true;
222 4
                break;
223
            }
224
        }
225
226
        if ($found) {
227 4
            return $this->insertAt($i + 1, $middleware);
228
        }
229
230 2
        return $this->add($middleware);
231
    }
232
233
    /**
234
     * Obtenir le nombre de couches middleware connectés.
235
     */
236
    public function count(): int
237
    {
238 8
        return count($this->queue);
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244
    public function seek(int $position): void
245
    {
246
        if (! isset($this->queue[$position])) {
247 2
            throw new OutOfBoundsException(sprintf('Invalid seek position (%s).', $position));
248
        }
249
250 2
        $this->position = $position;
251
    }
252
253
    /**
254
     * {@inheritDoc}
255
     */
256
    public function rewind(): void
257
    {
258 4
        $this->position = 0;
259
    }
260
261
    /**
262
     *  {@inheritDoc}
263
     */
264
    public function current(): MiddlewareInterface
265
    {
266
        if (! isset($this->queue[$this->position])) {
267 2
            throw new OutOfBoundsException(sprintf('Position actuelle non valide (%s).', $this->position));
268
        }
269
270
        if ($this->queue[$this->position] instanceof MiddlewareInterface) {
271 6
            return $this->queue[$this->position];
272
        }
273
274 14
        return $this->queue[$this->position] = $this->resolve($this->queue[$this->position]);
275
    }
276
277
    /**
278
     * {@inheritDoc}
279
     */
280
    public function key(): int
281
    {
282
        return $this->position;
283
    }
284
285
    /**
286
     * Passe la position actuelle au middleware suivant.
287
     */
288
    public function next(): void
289
    {
290 8
        $this->position++;
291
    }
292
293
    /**
294
     * Vérifie si la position actuelle est valide.
295
     */
296
    public function valid(): bool
297
    {
298 2
        return isset($this->queue[$this->position]);
299
    }
300
301
    /**
302
     * Enregistre les middlewares definis dans le gestionnaire des middlewares
303
     *
304
     * @internal
305
     */
306
    public function register(array $config)
307
    {
308
        $config += [
309
            'aliases' => [],
310
            'globals' => [],
311
            'groups'  => [],
312
            'build'   => static fn () => null,
313 2
        ];
314
315 2
        $this->aliases($config['aliases']);
316 2
        $this->groups($config['groups']);
317
318
        foreach ($config['globals'] as $middleware) {
319 2
            $this->add($middleware);
320
        }
321
322
        if (is_callable($build = $config['build'])) {
323
            $this->container->call($build, [
324
                'request' => $this->request,
325
                'queue'   => $this,
326 2
            ]);
327
        }
328
    }
329
330
    /**
331
     * Resout les groups pour definir les middlewares
332
     *
333
     * @internal
334
     */
335
    public function resolveGroups()
336
    {
337
        foreach ($this->queue as $queue) {
338
            if (is_string($queue) && ! empty($this->groups[$queue])) {
339
                if (! is_array($this->groups[$queue])) {
340
                    continue;
341
                }
342
343 2
                $i = array_search($queue, $this->queue, true);
344 2
                $j = 0;
345
346 2
                unset($this->queue[$i]);
347
348
                foreach ($this->groups[$queue] as $middleware) {
349 2
                    $this->insertAt(($i + $j), $middleware);
350 2
                    $j++;
351
                }
352
            }
353
        }
354
    }
355
356
    /**
357
     * {@internal}
358
     */
359
    public function response(): Response
360
    {
361 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...
362
    }
363
364
    /**
365
     * Résoudre le nom middleware à une instance de middleware compatible PSR 15.
366
     *
367
     * @throws InvalidArgumentException si Middleware introuvable.
368
     */
369
    protected function resolve(Closure|MiddlewareInterface|string $middleware): MiddlewareInterface
370
    {
371
        if (is_string($middleware)) {
372 8
            [$middleware, $options] = explode(':', $middleware) + [1 => null];
373
374
            if (isset($this->aliases[$middleware])) {
375 2
                $middleware = $this->aliases[$middleware];
376
            }
377
378
            if ($this->container->has($middleware)) {
379 8
                $middleware = $this->container->get($middleware);
380
            } else {
381
                throw new InvalidArgumentException(sprintf(
382
                    'Middleware, `%s` n\'a pas été trouvé.',
383
                    $middleware
384 2
                ));
385
            }
386
387
            if ($middleware instanceof BaseMiddleware) {
388
                if (null !== $options) {
389
                    $middleware->fill(explode(',', $options));
390
                }
391
392
                $middleware->init($this->request->getPath());
0 ignored issues
show
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

392
                $middleware->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...
393
            }
394
        }
395
396
        if ($middleware instanceof MiddlewareInterface) {
397 8
            return $middleware;
398
        }
399
400 12
        return new ClosureDecorator($middleware, $this->response);
0 ignored issues
show
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

400
        return new ClosureDecorator(/** @scrutinizer ignore-type */ $middleware, $this->response);
Loading history...
401
    }
402
}
403