Passed
Push — main ( 073c01...b49c3d )
by Dimitri
03:21
created

Middleware::getMiddlewareAndOptions()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 11
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\Services;
15
use BlitzPHP\Middlewares\BaseMiddleware;
16
use BlitzPHP\Middlewares\BodyParser;
17
use BlitzPHP\Middlewares\Cors;
18
use LogicException;
19
use Psr\Http\Message\ResponseInterface;
20
use Psr\Http\Message\ServerRequestInterface;
21
use Psr\Http\Server\MiddlewareInterface;
22
use Psr\Http\Server\RequestHandlerInterface;
23
24
class Middleware implements RequestHandlerInterface
25
{
26
    /**
27
     * Middlewares a executer pour la requete courante
28
     */
29
    protected array $middlewares = [];
30
31
    /**
32
     * Index du middleware actuellement executer
33
     */
34
    protected int $index = 0;
35
36
    /**
37
     * Aliases des middlewares
38
     */
39
    protected array $aliases = [
40
        'body-parser' => BodyParser::class,
41
        'cors'        => Cors::class,
42
    ];
43
44
    /**
45
     * Contructor
46
     */
47
    public function __construct(protected Response $response, protected string $path)
48
    {
49
    }
50
51
    /**
52
     * Ajoute un alias de middleware
53
     */
54
    public function alias(string $alias, callable|object|string $middleware): self
55
    {
56
		return $this->aliases([$alias => $middleware]);
57
    }
58
59
    /**
60
     * Ajoute des alias de middlewares
61
     */
62
    public function aliases(array $aliases): self
63
    {
64
        $this->aliases = array_merge($this->aliases, $aliases);
65
66
        return $this;
67
    }
68
69
    /**
70
     * Ajoute un middleware a la chaine d'execution
71
     *
72
     * @param array|callable|object|string $middlewares
73
     */
74
    public function add($middlewares, array $options = []): self
75
    {
76
        if (! is_array($middlewares)) {
77
            $middlewares = [$middlewares];
78
        }
79
80
        foreach ($middlewares as $middleware) {
81
            $this->append($middleware, $options);
82
        }
83
84
        return $this;
85
    }
86
87
    /**
88
     * Ajoute un middleware en bout de chaine
89
     *
90
     * @param callable|object|string $middleware
91
     */
92
    public function append($middleware, array $options = []): self
93
    {
94
        [$middleware, $options] = $this->getMiddlewareAndOptions($middleware, $options);
95
96
        $middleware          = $this->makeMiddleware($middleware);
97
        $this->middlewares[] = compact('middleware', 'options');
98
99
        return $this;
100
    }
101
102
    /**
103
     * Ajoute un middleware en debut de chaine
104
     *
105
     * @param callable|object|string $middleware
106
     */
107
    public function prepend($middleware, array $options = []): self
108
    {
109
        [$middleware, $options] = $this->getMiddlewareAndOptions($middleware, $options);
110
111
        $middleware = $this->makeMiddleware($middleware);
112
        array_unshift($this->middlewares, compact('middleware', 'options'));
113
114
        return $this;
115
    }
116
117
    /**
118
     * insert un middleware a une position donnee
119
     *
120
     * @param callable|object|string $middleware
121
     *
122
     * @alias insertAt
123
     */
124
    public function insert(int $index, $middleware, array $options = []): self
125
    {
126
        return $this->insertAt($index, $middleware, $options);
127
    }
128
129
    /**
130
     * Insérez un middleware appelable à un index spécifique.
131
     *
132
     * Si l'index existe déjà, le nouvel appelable sera inséré,
133
     * et l'élément existant sera décalé d'un indice supérieur.
134
     *
135
     * @param int                    $index      La position où le middleware doit être insérer.
136
     * @param callable|object|string $middleware Le middleware à inserer.
137
     */
138
    public function insertAt(int $index, $middleware, array $options = []): self
139
    {
140
        [$middleware, $options] = $this->getMiddlewareAndOptions($middleware, $options);
141
142
        $middleware = $this->makeMiddleware($middleware);
143
        array_splice($this->middlewares, $index, 0, compact('middleware', 'options'));
144
145
        return $this;
146
    }
147
148
    /**
149
     * Insérez un objet middleware avant la première classe correspondante.
150
     *
151
     * Trouve l'index du premier middleware qui correspond à la classe fournie,
152
     * et insère l'appelable fourni avant.
153
     *
154
     * @param string                 $class      Le nom de classe pour insérer le middleware avant.
155
     * @param callable|object|string $middleware Le middleware à inserer.
156
     *
157
     * @throws LogicException Si le middleware à insérer avant n'est pas trouvé.
158
     */
159
    public function insertBefore(string $class, $middleware, array $options = []): self
160
    {
161
        $found = false;
162
        $i     = 0;
163
164
        if (array_key_exists($class, $this->aliases)) {
165
            $class = $this->aliases[$class];
166
        }
167
168
        foreach ($this->middlewares as $i => $object) {
169
            if ((is_string($object) && $object === $class) || is_a($object, $class)) {
170
                $found = true;
171
                break;
172
            }
173
        }
174
175
        if ($found) {
176
            return $this->insertAt($i, $middleware, $options);
177
        }
178
179
        throw new LogicException(sprintf("No middleware matching '%s' could be found.", $class));
180
    }
181
182
    /**
183
     * Insérez un objet middleware après la première classe correspondante.
184
     *
185
     * Trouve l'index du premier middleware qui correspond à la classe fournie,
186
     * et insère le callback fourni après celui-ci. Si la classe n'est pas trouvée,
187
     * cette méthode se comportera comme add().
188
     *
189
     * @param string                 $class      Le nom de classe pour insérer le middleware après.
190
     * @param callable|object|string $middleware Le middleware à inserer.
191
     */
192
    public function insertAfter(string $class, $middleware, array $options = []): self
193
    {
194
        $found = false;
195
        $i     = 0;
196
197
        if (array_key_exists($class, $this->aliases)) {
198
            $class = $this->aliases[$class];
199
        }
200
201
        foreach ($this->middlewares as $i => $object) {
202
            if ((is_string($object) && $object === $class) || is_a($object, $class)) {
203
                $found = true;
204
                break;
205
            }
206
        }
207
208
        if ($found) {
209
            return $this->insertAt($i + 1, $middleware, $options);
210
        }
211
212
        return $this->add($middleware, $options);
213
    }
214
215
    /**
216
     * Execution du middleware
217
     */
218
    public function handle(ServerRequestInterface $request): ResponseInterface
219
    {
220
		if (empty($processing = $this->getMiddleware())) {
221
			return $this->response;
222
		}
223
224
        ['middleware' => $middleware, 'options' => $options] = $processing;
225
226
        if (empty($middleware)) {
227
            return $this->response;
228
        }
229
230
        if (isset($options['except']) && $this->pathApplies($this->path, $options['except'])) {
231
            return $this->handle($request);
232
        }
233
234
        unset($options['except']);
235
236
        if (is_callable($middleware)) {
237
            return $middleware($request, $this->response, [$this, 'handle']);
238
        }
239
240
        if ($middleware instanceof MiddlewareInterface) {
241
            if ($middleware instanceof BaseMiddleware) {
242
                $middleware = $middleware->init($options + ['path' => $this->path])->fill($options);
243
            }
244
245
            return $middleware->process($request, $this);
246
        }
247
248
        return $this->response;
249
    }
250
251
    /**
252
     * Fabrique un middleware
253
     *
254
     * @param callable|object|string $middleware
255
     *
256
     * @return callable|object
257
     */
258
    private function makeMiddleware($middleware)
259
    {
260
        if (is_string($middleware) && array_key_exists($middleware, $this->aliases)) {
261
			$middleware = $this->aliases[$middleware];
262
        }
263
		
264
		return is_string($middleware)
265
			? Services::container()->get($middleware)
266
			: $middleware;
267
    }
268
269
    /**
270
     * Recuperation du middleware actuel
271
     */
272
    private function getMiddleware(): array
273
    {
274
        $middleware = [];
275
276
        if (isset($this->middlewares[$this->index])) {
277
            $middleware = $this->middlewares[$this->index];
278
        }
279
280
        $this->index++;
281
282
        return $middleware;
283
    }
284
285
    /**
286
     * Recupere les options d'un middlewares de type string
287
     * 
288
     * @param callable|object|string $middleware
289
     */
290
    private function getMiddlewareAndOptions($middleware, array $options = []): array 
291
    {
292
        if (is_string($middleware)) {
293
            $parts = explode(':', $middleware);
294
            $middleware = array_shift($parts);
295
            if (isset($parts[0]) && is_string($parts[0])) {
296
                $options = array_merge($options, explode(',', $parts[0]));
297
            }
298
        }
299
        
300
        return [$middleware, $options];
301
    }
302
303
    /**
304
     * Check paths for match for URI
305
     */
306
    private function pathApplies(string $uri, array|string $paths): bool
307
    {
308
        // empty path matches all
309
        if (empty($paths)) {
310
            return true;
311
        }
312
313
        // make sure the paths are iterable
314
        if (is_string($paths)) {
0 ignored issues
show
introduced by
The condition is_string($paths) is always false.
Loading history...
315
            $paths = [$paths];
316
        }
317
318
        // treat each paths as pseudo-regex
319
        foreach ($paths as $path) {
320
            // need to escape path separators
321
            $path = str_replace('/', '\/', trim($path, '/ '));
322
            // need to make pseudo wildcard real
323
            $path = strtolower(str_replace('*', '.*', $path));
324
            // Does this rule apply here?
325
            if (preg_match('#^' . $path . '$#', $uri, $match) === 1) {
326
                return true;
327
            }
328
        }
329
330
        return false;
331
    }
332
}
333