Passed
Pull Request — master (#68)
by Dmitriy
12:29
created

Dispatcher.php$0 ➔ __construct()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use InvalidArgumentException;
8
use Psr\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
use Yiisoft\Injector\Injector;
14
15
final class Dispatcher implements DispatcherInterface
16
{
17
    /**
18
     * Contains a stack of middleware wrapped in handlers.
19
     * Each handler points to the handler of middleware that will be processed next.
20
     * @var RequestHandlerInterface|null stack of middleware
21
     */
22
    private ?RequestHandlerInterface $stack = null;
23
24
    private ContainerInterface $container;
25
    /**
26
     * @var callable[]|string[]|array[]
27
     */
28
    private array $middlewares = [];
29
30
    public function __construct(ContainerInterface $container)
31
    {
32
        $this->container = $container;
33
    }
34
35
    public function dispatch(ServerRequestInterface $request, RequestHandlerInterface $fallbackHandler): ResponseInterface
36
    {
37
        $handler = $fallbackHandler;
38
        if ($this->stack === null) {
39
            foreach ($this->middlewares as $middleware) {
40
                $handler = $this->wrap($this->prepareMiddleware($middleware), $handler);
41
            }
42
            $this->stack = $handler;
43
        }
44
45
        return $this->stack->handle($request);
0 ignored issues
show
Bug introduced by
The method handle() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

45
        return $this->stack->/** @scrutinizer ignore-call */ handle($request);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
46
    }
47
48
    public function withMiddlewares(array $middlewares): DispatcherInterface
49
    {
50
        $stack = $this->stack;
51
        $this->stack = null;
52
        $clone = clone $this;
53
        $clone->middlewares = $middlewares;
54
        $this->stack = $stack;
55
56
        return $clone;
57
    }
58
59
    public function hasMiddlewares(): bool
60
    {
61
        return $this->middlewares !== [];
62
    }
63
64
    /**
65
     * Wraps handler by middlewares
66
     */
67
    private function wrap(MiddlewareInterface $middleware, RequestHandlerInterface $handler): RequestHandlerInterface
68
    {
69
        return new class($middleware, $handler) implements RequestHandlerInterface {
70
            private MiddlewareInterface $middleware;
71
            private RequestHandlerInterface $handler;
72
73
            public function __construct(MiddlewareInterface $middleware, RequestHandlerInterface $handler)
74
            {
75
                $this->middleware = $middleware;
76
                $this->handler = $handler;
77
            }
78
79
            public function handle(ServerRequestInterface $request): ResponseInterface
80
            {
81
                return $this->middleware->process($request, $this->handler);
82
            }
83
        };
84
    }
85
86
    private function wrapCallable($callback): MiddlewareInterface
87
    {
88
        if (is_array($callback) && !is_object($callback[0])) {
89
            [$controller, $action] = $callback;
90
            return new class($controller, $action, $this->container) implements MiddlewareInterface {
91
                private string $class;
92
                private string $method;
93
                private ContainerInterface $container;
94
95
                public function __construct(string $class, string $method, ContainerInterface $container)
96
                {
97
                    $this->class = $class;
98
                    $this->method = $method;
99
                    $this->container = $container;
100
                }
101
102
                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
103
                {
104
                    $controller = $this->container->get($this->class);
105
                    return (new Injector($this->container))->invoke([$controller, $this->method], [$request, $handler]);
106
                }
107
            };
108
        }
109
110
        return new class($callback, $this->container) implements MiddlewareInterface {
111
            private ContainerInterface $container;
112
            private $callback;
113
114
            public function __construct(callable $callback, ContainerInterface $container)
115
            {
116
                $this->callback = $callback;
117
                $this->container = $container;
118
            }
119
120
            public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
121
            {
122
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
123
                return $response instanceof MiddlewareInterface ? $response->process($request, $handler) : $response;
124
            }
125
        };
126
    }
127
128
    /**
129
     * @param callable|string|array $middleware
130
     * @return MiddlewareInterface|string|array
131
     */
132
    private function prepareMiddleware($middleware)
133
    {
134
        if (is_string($middleware)) {
135
            if ($this->container === null) {
136
                throw new InvalidArgumentException('Route container must not be null for lazy loaded middleware.');
137
            }
138
            return $this->container->get($middleware);
139
        }
140
141
        if (is_array($middleware) && !is_object($middleware[0])) {
142
            if ($this->container === null) {
143
                throw new InvalidArgumentException('Route container must not be null for handler action.');
144
            }
145
            return $this->wrapCallable($middleware);
146
        }
147
148
        if ($this->isCallable($middleware)) {
149
            if ($this->container === null) {
150
                throw new InvalidArgumentException('Route container must not be null for callable.');
151
            }
152
            return $this->wrapCallable($middleware);
153
        }
154
155
        return $middleware;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $middleware returns the type callable which is incompatible with the documented return type Psr\Http\Server\MiddlewareInterface|array|string.
Loading history...
156
    }
157
158
    private function isCallable($definition): bool
159
    {
160
        if (is_callable($definition)) {
161
            return true;
162
        }
163
164
        return is_array($definition) && array_keys($definition) === [0, 1] && in_array($definition[1], get_class_methods($definition[0]) ?? [], true);
165
    }
166
}
167