Completed
Push — master ( 3399bc...8e197a )
by Gabriel
02:04
created

Dispatcher.php$0 ➔ handle()   A

Complexity

Conditions 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 6
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
crap 12
1
<?php
2
3
namespace Nip\Http\ServerMiddleware;
4
5
use Closure;
6
use InvalidArgumentException;
7
use LogicException;
8
use Nip\Container\ContainerInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
14
/**
15
 * Class Dispatcher
16
 * @package Nip\Http\ServerMiddleware
17
 *
18
 * @based on https://github.com/oscarotero/middleland/blob/master/src/Dispatcher.php
19
 */
20
class Dispatcher implements DispatcherInterface
21
{
22
23
    /**
24
     * @var MiddlewareInterface[]
25
     */
26
    private $middleware;
27
    /**
28
     * @var ContainerInterface|null
29
     */
30
    private $container;
31
32
33
    /**
34
     * @param MiddlewareInterface[] $middleware
35
     * @param ContainerInterface $container
36
     */
37
    public function __construct(array $middleware, ContainerInterface $container = null)
38
    {
39
        if (empty($middleware)) {
40
            throw new LogicException('Empty middleware queue');
41
        }
42
        $this->middleware = $middleware;
43
        $this->container = $container;
44
    }
45
46
    /**
47
     * Return a new dispatcher containing the given middleware.
48
     *
49
     * @param MiddlewareInterface $middleware
50
     * @return DispatcherInterface
51
     */
52
    public function with(MiddlewareInterface $middleware): DispatcherInterface
53
    {
54
        return $this->withElement($middleware);
55
    }
56
57
    /**
58
     * Return a new dispatcher containing the given element.
59
     *
60
     * @param MiddlewareInterface $element
61
     * @return DispatcherInterface
62
     */
63
    public function withElement($element): DispatcherInterface
64
    {
65
        $this->middleware = array_merge($this->middleware, [$element]);
66
        return $this;
67
    }
68
69
    /**
70
     * Dispatch the request, return a response.
71
     *
72
     * @param ServerRequestInterface $request
73
     *
74
     * @return ResponseInterface
75
     */
76
    public function dispatch(ServerRequestInterface $request): ResponseInterface
77
    {
78
        reset($this->middleware);
79
        return $this->get($request)->process($request, $this->createDelegate());
80
    }
81
82
    /**
83
     * Create a delegate for the current stack
84
     *
85
     * @param RequestHandlerInterface $delegate
86
     *
87
     * @return RequestHandlerInterface
88
     */
89
    private function createDelegate(RequestHandlerInterface $delegate = null): RequestHandlerInterface
90
    {
91
        return new class($this, $delegate) implements RequestHandlerInterface {
92
            private $dispatcher;
93
            private $delegate;
94
95
            /**
96
             * @param Dispatcher $dispatcher
97
             * @param RequestHandlerInterface|null $delegate
98
             */
99
            public function __construct(Dispatcher $dispatcher, RequestHandlerInterface $delegate = null)
100
            {
101
                $this->dispatcher = $dispatcher;
102
                $this->delegate = $delegate;
103
            }
104
105
            /**
106
             * {@inheritdoc}
107
             */
108
            public function handle(ServerRequestInterface $request): ResponseInterface
109
            {
110
                $frame = $this->dispatcher->next($request);
111
                if ($frame === false) {
112
                    if ($this->delegate !== null) {
113
                        return $this->delegate->handle($request);
114
                    }
115
                    throw new LogicException('Middleware queue exhausted');
116
                }
117
                return $frame->process($request, $this);
118
            }
119
        };
120
    }
121
122
    /**
123
     * Return the next available middleware frame in the queue.
124
     *
125
     * @param ServerRequestInterface $request
126
     * @return false|MiddlewareInterface
127
     */
128
    public function next(ServerRequestInterface $request)
129
    {
130
        next($this->middleware);
131
        return $this->get($request);
132
    }
133
134
    /**
135
     * Return the next available middleware frame in the middleware.
136
     *
137
     * @param ServerRequestInterface $request
138
     *
139
     * @return MiddlewareInterface|false
140
     */
141
    private function get(ServerRequestInterface $request)
142
    {
143
        $frame = current($this->middleware);
144
        if ($frame === false) {
145
            return $frame;
146
        }
147
        if (is_array($frame)) {
148
            $conditions = $frame;
149
            $frame = array_pop($conditions);
150
            foreach ($conditions as $condition) {
151
                if ($condition === true) {
152
                    continue;
153
                }
154
                if ($condition === false) {
155
                    return $this->next($request);
156
                }
157
                if (is_string($condition)) {
158
                    $condition = new Matchers\Path($condition);
159
                } elseif (!($condition instanceof Matchers\MatcherInterface)) {
160
                    throw new InvalidArgumentException(
161
                        'Invalid matcher. Must be a boolean, string or an instance of MatcherInterface'
162
                    );
163
                }
164
                if (!$condition->match($request)) {
165
                    return $this->next($request);
166
                }
167
            }
168
        }
169
        if (is_string($frame)) {
170
            if ($this->container === null) {
171
                throw new InvalidArgumentException(sprintf('No valid middleware provided (%s)', $frame));
172
            }
173
            $frame = $this->container->get($frame);
174
        }
175
        if ($frame instanceof Closure) {
176
            return $this->createMiddlewareFromClosure($frame);
177
        }
178
        if ($frame instanceof MiddlewareInterface) {
179
            return $frame;
180
        }
181
        throw new InvalidArgumentException(
182
            sprintf('No valid middleware provided (%s)', is_object($frame) ? get_class($frame) : gettype($frame))
183
        );
184
    }
185
186
    /**
187
     * Create a middleware from a closure
188
     *
189
     * @param Closure $handler
190
     *
191
     * @return MiddlewareInterface
192
     */
193
    private function createMiddlewareFromClosure(Closure $handler): MiddlewareInterface
194
    {
195
        return new class($handler) implements MiddlewareInterface {
196
            private $handler;
197
198
            /**
199
             * @param Closure $handler
200
             */
201
            public function __construct(Closure $handler)
202
            {
203
                $this->handler = $handler;
204
            }
205
206
            /**
207
             * {@inheritdoc}
208
             */
209
            public function process(ServerRequestInterface $request, RequestHandlerInterface $delegate)
210
            {
211
                $response = call_user_func($this->handler, $request, $delegate);
212
                if (!($response instanceof ResponseInterface)) {
213
                    throw new LogicException('The middleware must return a ResponseInterface');
214
                }
215
                return $response;
216
            }
217
        };
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
224
    {
225
        reset($this->middleware);
226
        return $this->get($request)->process($request, $this->createDelegate($handler));
227
    }
228
}
229