Test Failed
Push — master ( e90e21...173e5c )
by Divine Niiquaye
02:22
created

MiddlewareDispatcher::getMiddlewareStack()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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[]
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 array                   $routeMiddlewares
76
     * @param null|ContainerInterface $container
77
     */
78
    public function __construct(array $routeMiddlewares, ContainerInterface $container = null)
79
    {
80
        $this->routeMiddlewares = $routeMiddlewares;
81
        $this->container        = $container;
82
    }
83
84
    /**
85
     * Add new middleware to the the stack.
86
     *
87
     * NOTE: This method adds middleware at the top of chain,
88
     * if it's an array else pushes middleware to the end of chain.
89
     *
90
     * Middleware are organized as a stack. That means middleware
91
     * that have been added before will be executed after the newly
92
     * added one (last in, first out).
93
     *
94
     * Example (in implementation):
95
     * $this->add(new ProxyMiddleware());
96
     *
97
     * or
98
     *
99
     * $this->add([ProxyMiddleware::class]);
100
     *
101
     * @param mixed $middleware
102
     */
103
    public function add($middleware): void
104
    {
105
        if (\is_array($middleware)) {
106
            $this->routeMiddlewares = \array_merge($middleware['routing'] ?? [], $this->routeMiddlewares);
107
            unset($middleware['routing']);
108
109
            $this->middlewares = \array_merge($middleware, $this->middlewares);
110
111
            return;
112
        }
113
114
        if (null !== $middleware) {
115
            $this->middlewares[] = $middleware;
116
        }
117
    }
118
119
    /**
120
     * Get all middlewares stack.
121
     *
122
     * @return array<string, MiddlewareInterface>
123
     */
124
    public function getMiddlewareStack(): array
125
    {
126
        return \array_filter($this->middlewares);
127
    }
128
129
    /**
130
     * Resolve a middleware so it can be used flexibly.
131
     *
132
     * @param callable|MiddlewareInterface|string $middleware
133
     *
134
     * @return callable|MiddlewareInterface|RequestHandlerInterface
135
     */
136
    public function resolve($middleware)
137
    {
138
        if (\is_string($middleware)) {
139
            if (\array_key_exists($middleware, $this->routeMiddlewares)) {
140
                $middleware = $this->routeMiddlewares[$middleware];
141
            }
142
143
            if (
144
                (null !== $this->container && \is_string($middleware)) &&
145
                $this->container->has($middleware)
146
            ) {
147
                $middleware = $this->container->get($middleware);
148
            }
149
150
            if (\is_string($middleware) && !\class_exists($middleware)) {
151
                throw InvalidMiddlewareException::forMiddleware($middleware);
152
            }
153
154
            return \is_object($middleware) ? $middleware : new $middleware();
155
        }
156
157
        return $middleware;
158
    }
159
160
    /**
161
     * Add a new middleware to the stack.
162
     *
163
     * Middleware are organized as a stack. That means middleware
164
     * that have been added before will be executed after the newly
165
     * added one (last in, first out).
166
     *
167
     * @param array|callable|MiddlewareInterface|RequestHandlerInterface|string $middleware
168
     *
169
     * @throws InvalidMiddlewareException if argument is not one of
170
     *                                    the specified types
171
     *
172
     * @return MiddlewareInterface
173
     */
174
    public function prepare($middleware): MiddlewareInterface
175
    {
176
        if ($middleware instanceof MiddlewareInterface) {
177
            return $middleware;
178
        }
179
180
        if ($middleware instanceof RequestHandlerInterface) {
181
            return $this->addHandler($middleware);
182
        }
183
184
        if (\is_callable($middleware)) {
185
            return $this->addCallable($middleware);
186
        }
187
188
        if (\is_array($middleware)) {
189
            return $this->pipeline(...$middleware);
190
        }
191
192
        throw InvalidMiddlewareException::forMiddleware($middleware);
193
    }
194
195
    /**
196
     * Decorate callable standards-signature middleware via a CallableMiddlewareDecorator.
197
     *
198
     * @param callable $middleware
199
     *
200
     * @return CallableMiddlewareDecorator
201
     */
202
    public function addCallable(callable $middleware): CallableMiddlewareDecorator
203
    {
204
        return new CallableMiddlewareDecorator($middleware);
205
    }
206
207
    /**
208
     * Decorate a RequestHandlerInterface as middleware via RequestHandlerMiddleware.
209
     *
210
     * @param RequestHandlerInterface $handler
211
     *
212
     * @return RequestHandlerMiddleware
213
     */
214
    public function addHandler(RequestHandlerInterface $handler): RequestHandlerMiddleware
215
    {
216
        return new RequestHandlerMiddleware($handler);
217
    }
218
219
    /**
220
     * Create a middleware pipeline from an array of middleware.
221
     *
222
     * This method allows passing an array of middleware as either:
223
     *
224
     * - discrete arguments
225
     * - an array of middleware, using the splat operator: pipeline(...$array)
226
     * - an array of middleware as the sole argument: pipeline($array)
227
     *
228
     * Each item is passed to prepare() before being passed to the
229
     * MiddlewarePipe instance the method returns.
230
     *
231
     * @param array|MiddlewarePipe|string $middleware
232
     *
233
     * @return MiddlewarePipe
234
     */
235
    public function pipeline(...$middleware): MiddlewarePipe
236
    {
237
        // Allow passing arrays of middleware or individual lists of middleware
238
        if (\is_array($middleware[0]) && \count($middleware) === 1) {
239
            $middleware = \array_shift($middleware);
240
        }
241
242
        $pipeline = new MiddlewarePipe();
243
244
        foreach (\array_map([$this, 'resolve'], $middleware) as $m) {
245
            $pipeline->pipe($this->prepare($m));
246
        }
247
248
        return $pipeline;
249
    }
250
}
251