Passed
Push — master ( b202e3...14e011 )
by Divine Niiquaye
02:35 queued 13s
created

MiddlewareDispatcher   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 192
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 39
dl 0
loc 192
ccs 0
cts 71
cp 0
rs 10
c 2
b 0
f 0
wmc 25

8 Methods

Rating   Name   Duplication   Size   Complexity  
B resolve() 0 22 9
A __construct() 0 3 1
A add() 0 10 3
A getMiddlewareStack() 0 3 1
A pipeline() 0 14 4
A prepare() 0 19 5
A addHandler() 0 3 1
A addCallable() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.1 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Flight\Routing\Middlewares;
19
20
use Flight\Routing\Exceptions\InvalidMiddlewareException;
21
use Laminas\Stratigility\Middleware\CallableMiddlewareDecorator;
22
use Laminas\Stratigility\Middleware\RequestHandlerMiddleware;
23
use Laminas\Stratigility\MiddlewarePipe;
24
use Psr\Container\ContainerInterface;
25
use Psr\Http\Server\MiddlewareInterface;
26
use Psr\Http\Server\RequestHandlerInterface;
27
28
/**
29
 * Marshal middleware for use in the application.
30
 *
31
 * This class provides a number of methods for preparing and returning
32
 * middleware for use within an application.
33
 *
34
 * If any middleware provided is already a MiddlewareInterface, it can be used
35
 * verbatim or decorated as-is. Other middleware types acceptable are:
36
 *
37
 * - PSR-15 RequestHandlerInterface instances; these will be decorated as
38
 *   RequestHandlerMiddleware instances.
39
 * - string service names resolving to middleware
40
 * - arrays of service names and/or MiddlewareInterface instances
41
 * - PHP callable that follow the PSR-15 signature
42
 *
43
 * Additionally, the class provides the following decorator/utility methods:
44
 *
45
 * - addCallable() will decorate the callable middleware passed to it using
46
 *   CallableMiddlewareDecorator.
47
 * - addHandler() will decorate the request handler passed to it using
48
 *   RequestHandlerMiddleware.
49
 * - pipeline() will create a MiddlewarePipe instance from the array of
50
 *   middleware passed to it, after passing each first to prepare().
51
 */
52
class MiddlewareDispatcher
53
{
54
    /**
55
     * Set of middleware to be applied for every request.
56
     *
57
     * @var MiddlewareInterface[]|string[]
58
     */
59
    protected $middlewares = [];
60
61
    /**
62
     * Set of route middleware to be used in $middlewares
63
     * Stack, if string name is equal to a given middleware.
64
     *
65
     * @var array<string,mixed>
66
     */
67
    protected $routeMiddlewares = [];
68
69
    /**
70
     * @var null|ContainerInterface
71
     */
72
    protected $container;
73
74
    /**
75
     * @param null|ContainerInterface $container
76
     */
77
    public function __construct(ContainerInterface $container = null)
78
    {
79
        $this->container = $container;
80
    }
81
82
    /**
83
     * Add new middleware to the the stack.
84
     *
85
     * NOTE: This method adds middleware at the top of chain,
86
     * if it's an array else pushes middleware to the end of chain.
87
     *
88
     * Middleware are organized as a stack. That means middleware
89
     * that have been added before will be executed after the newly
90
     * added one (last in, first out).
91
     *
92
     * Example (in implementation):
93
     * $this->add(new ProxyMiddleware());
94
     *
95
     * or
96
     *
97
     * $this->add(ProxyMiddleware::class);
98
     *
99
     * @param mixed ...$middleware
100
     */
101
    public function add(...$middlewares): void
102
    {
103
        foreach ($middlewares as $middleware) {
104
            if (\is_array($middleware)) {
105
                $this->routeMiddlewares = \array_merge($middleware, $this->routeMiddlewares);
106
107
                continue;
108
            }
109
110
            $this->middlewares[] = $middleware;
111
        }
112
    }
113
114
    /**
115
     * Get all middlewares stack.
116
     *
117
     * @return MiddlewareInterface[]|string[]
118
     */
119
    public function getMiddlewareStack(): array
120
    {
121
        return \array_filter($this->middlewares);
122
    }
123
124
    /**
125
     * Resolve a middleware so it can be used flexibly.
126
     *
127
     * @param callable|MiddlewareInterface|string $middleware
128
     *
129
     * @return callable|MiddlewareInterface|RequestHandlerInterface
130
     */
131
    public function resolve($middleware)
132
    {
133
        if (\is_string($middleware)) {
134
            if (\array_key_exists($middleware, $this->routeMiddlewares)) {
135
                $middleware = $this->routeMiddlewares[$middleware];
136
            }
137
138
            if (
139
                (null !== $this->container && \is_string($middleware)) &&
140
                $this->container->has($middleware)
141
            ) {
142
                $middleware = $this->container->get($middleware);
143
            }
144
145
            if (\is_string($middleware) && !\class_exists($middleware)) {
146
                throw InvalidMiddlewareException::forMiddleware($middleware);
147
            }
148
149
            return \is_object($middleware) ? $middleware : new $middleware();
150
        }
151
152
        return $middleware;
153
    }
154
155
    /**
156
     * Add a new middleware to the stack.
157
     *
158
     * Middleware are organized as a stack. That means middleware
159
     * that have been added before will be executed after the newly
160
     * added one (last in, first out).
161
     *
162
     * @param callable|MiddlewareInterface|RequestHandlerInterface|string|string[] $middleware
163
     *
164
     * @throws InvalidMiddlewareException if argument is not one of
165
     *                                    the specified types
166
     *
167
     * @return MiddlewareInterface
168
     */
169
    public function prepare($middleware): MiddlewareInterface
170
    {
171
        if ($middleware instanceof MiddlewareInterface) {
172
            return $middleware;
173
        }
174
175
        if ($middleware instanceof RequestHandlerInterface) {
176
            return $this->addHandler($middleware);
177
        }
178
179
        if (\is_callable($middleware)) {
180
            return $this->addCallable($middleware);
181
        }
182
183
        if (\is_array($middleware)) {
184
            return $this->pipeline(...$middleware);
185
        }
186
187
        throw InvalidMiddlewareException::forMiddleware($middleware);
188
    }
189
190
    /**
191
     * Decorate callable standards-signature middleware via a CallableMiddlewareDecorator.
192
     *
193
     * @param callable $middleware
194
     *
195
     * @return CallableMiddlewareDecorator
196
     */
197
    public function addCallable(callable $middleware): CallableMiddlewareDecorator
198
    {
199
        return new CallableMiddlewareDecorator($middleware);
200
    }
201
202
    /**
203
     * Decorate a RequestHandlerInterface as middleware via RequestHandlerMiddleware.
204
     *
205
     * @param RequestHandlerInterface $handler
206
     *
207
     * @return RequestHandlerMiddleware
208
     */
209
    public function addHandler(RequestHandlerInterface $handler): RequestHandlerMiddleware
210
    {
211
        return new RequestHandlerMiddleware($handler);
212
    }
213
214
    /**
215
     * Create a middleware pipeline from an array of middleware.
216
     *
217
     * This method allows passing an array of middleware as either:
218
     *
219
     * - discrete arguments
220
     * - an array of middleware, using the splat operator: pipeline(...$array)
221
     * - an array of middleware as the sole argument: pipeline($array)
222
     *
223
     * Each item is passed to prepare() before being passed to the
224
     * MiddlewarePipe instance the method returns.
225
     *
226
     * @param callable|MiddlewareInterface|RequestHandlerInterface|string ...$middleware
227
     *
228
     * @return MiddlewarePipe
229
     */
230
    public function pipeline(...$middleware): MiddlewarePipe
231
    {
232
        // Allow passing arrays of middleware or individual lists of middleware
233
        if (\is_array($middleware[0]) && \count($middleware) === 1) {
234
            $middleware = \array_shift($middleware);
235
        }
236
237
        $pipeline = new MiddlewarePipe();
238
239
        foreach (\array_map([$this, 'resolve'], (array) $middleware) as $m) {
240
            $pipeline->pipe($this->prepare($m));
241
        }
242
243
        return $pipeline;
244
    }
245
}
246