Completed
Push — master ( 6520e3...79c244 )
by Oscar
02:52
created

Dispatcher::__invoke()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
declare(strict_types = 1);
3
4
namespace Middleland;
5
6
use Closure;
7
use Interop\Http\Server\MiddlewareInterface;
8
use Interop\Http\Server\RequestHandlerInterface;
9
use InvalidArgumentException;
10
use LogicException;
11
use Psr\Container\ContainerInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
15
class Dispatcher implements MiddlewareInterface, RequestHandlerInterface
16
{
17
    /**
18
     * @var MiddlewareInterface[]
19
     */
20
    private $middleware;
21
22
    /**
23
     * @var ContainerInterface|null
24
     */
25
    private $container;
26
27
    /**
28
     * @var RequestHandlerInterface|null
29
     */
30
    private $next;
31
32
    /**
33
     * @param MiddlewareInterface[] $middleware
34
     */
35
    public function __construct(array $middleware, ContainerInterface $container = null)
36
    {
37
        if (empty($middleware)) {
38
            throw new LogicException('Empty middleware queue');
39
        }
40
41
        $this->middleware = $middleware;
42
        $this->container = $container;
43
    }
44
45
    /**
46
     * Magic method to execute the dispatcher as a callable
47
     */
48
    public function __invoke(ServerRequestInterface $request): ResponseInterface
49
    {
50
        return $this->dispatch($request);
51
    }
52
53
    /**
54
     * Return the current or next available middleware frame in the middleware.
55
     *
56
     * @return MiddlewareInterface|false
57
     */
58
    private function get(ServerRequestInterface $request, bool $next = false)
59
    {
60
        $frame = $next ? next($this->middleware) : current($this->middleware);
61
62
        if ($frame === false) {
63
            return $frame;
64
        }
65
66
        if (is_array($frame)) {
67
            $conditions = $frame;
68
            $frame = array_pop($conditions);
69
70
            foreach ($conditions as $condition) {
71
                if ($condition === true) {
72
                    continue;
73
                }
74
75
                if ($condition === false) {
76
                    return $this->get($request, true);
77
                }
78
79
                if (is_string($condition)) {
80
                    $condition = new Matchers\Path($condition);
81
                } elseif (!is_callable($condition)) {
82
                    throw new InvalidArgumentException('Invalid matcher. Must be a boolean, string or a callable');
83
                }
84
85
                if (!$condition($request)) {
86
                    return $this->get($request, true);
87
                }
88
            }
89
        }
90
91
        if (is_string($frame)) {
92
            if ($this->container === null) {
93
                throw new InvalidArgumentException(sprintf('No valid middleware provided (%s)', $frame));
94
            }
95
96
            $frame = $this->container->get($frame);
97
        }
98
99
        if ($frame instanceof Closure) {
100
            return self::createMiddlewareFromClosure($frame);
101
        }
102
103
        if ($frame instanceof MiddlewareInterface) {
104
            return $frame;
105
        }
106
107
        throw new InvalidArgumentException(sprintf('No valid middleware provided (%s)', is_object($frame) ? get_class($frame) : gettype($frame)));
108
    }
109
110
    /**
111
     * Dispatch the request, return a response.
112
     */
113
    public function dispatch(ServerRequestInterface $request): ResponseInterface
114
    {
115
        reset($this->middleware);
116
117
        return $this->get($request)->process($request, $this);
118
    }
119
120
    /**
121
     * @see RequestHandlerInterface
122
     */
123
    public function handle(ServerRequestInterface $request): ResponseInterface
124
    {
125
        $frame = $this->get($request, true);
126
127
        if ($frame === false) {
128
            if ($this->next !== null) {
129
                return $this->next->handle($request);
130
            }
131
132
            throw new LogicException('Middleware queue exhausted');
133
        }
134
135
        return $frame->process($request, $this);
136
    }
137
138
    /**
139
     * @see MiddlewareInterface
140
     */
141
    public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
142
    {
143
        $this->next = $next;
144
145
        return $this->dispatch($request);
146
    }
147
148
    /**
149
     * Create a middleware from a closure
150
     */
151
    private static function createMiddlewareFromClosure(Closure $handler): MiddlewareInterface
152
    {
153
        return new class($handler) implements MiddlewareInterface {
154
            private $handler;
155
156
            public function __construct(Closure $handler)
157
            {
158
                $this->handler = $handler;
159
            }
160
161
            public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
162
            {
163
                return call_user_func($this->handler, $request, $next);
164
            }
165
        };
166
    }
167
}
168