Passed
Push — main ( 109ace...7d6b92 )
by Dimitri
17:05
created

MiddlewareQueue::groups()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

400
                $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...
401
            }
402
        }
403
404
        if ($middleware instanceof MiddlewareInterface) {
405 8
            return $middleware;
406
        }
407
408 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

408
        return new ClosureDecorator(/** @scrutinizer ignore-type */ $middleware, $this->response);
Loading history...
409
    }
410
}
411