Passed
Push — master ( f8dde9...fe9361 )
by Alexander
10:36
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
        $clone = clone $this;
51
        $clone->middlewares = $middlewares;
52
53
        return $clone;
54
    }
55
56
    /**
57
     * Wraps handler by middlewares
58
     */
59
    private function wrap(MiddlewareInterface $middleware, RequestHandlerInterface $handler): RequestHandlerInterface
60
    {
61
        return new class($middleware, $handler) implements RequestHandlerInterface {
62
            private MiddlewareInterface $middleware;
63
            private RequestHandlerInterface $handler;
64
65
            public function __construct(MiddlewareInterface $middleware, RequestHandlerInterface $handler)
66
            {
67
                $this->middleware = $middleware;
68
                $this->handler = $handler;
69
            }
70
71
            public function handle(ServerRequestInterface $request): ResponseInterface
72
            {
73
                return $this->middleware->process($request, $this->handler);
74
            }
75
        };
76
    }
77
78
    private function wrapCallable($callback): MiddlewareInterface
79
    {
80
        if (is_array($callback) && !is_object($callback[0])) {
81
            [$controller, $action] = $callback;
82
            return new class($controller, $action, $this->container) implements MiddlewareInterface {
83
                private string $class;
84
                private string $method;
85
                private ContainerInterface $container;
86
87
                public function __construct(string $class, string $method, ContainerInterface $container)
88
                {
89
                    $this->class = $class;
90
                    $this->method = $method;
91
                    $this->container = $container;
92
                }
93
94
                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
95
                {
96
                    $controller = $this->container->get($this->class);
97
                    return (new Injector($this->container))->invoke([$controller, $this->method], [$request, $handler]);
98
                }
99
            };
100
        }
101
102
        return new class($callback, $this->container) implements MiddlewareInterface {
103
            private ContainerInterface $container;
104
            private $callback;
105
106
            public function __construct(callable $callback, ContainerInterface $container)
107
            {
108
                $this->callback = $callback;
109
                $this->container = $container;
110
            }
111
112
            public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
113
            {
114
                $response = (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
115
                return $response instanceof MiddlewareInterface ? $response->process($request, $handler) : $response;
116
            }
117
        };
118
    }
119
120
    /**
121
     * @param callable|string|array $middleware
122
     * @return MiddlewareInterface|string|array
123
     */
124
    private function prepareMiddleware($middleware)
125
    {
126
        if (is_string($middleware)) {
127
            if ($this->container === null) {
128
                throw new InvalidArgumentException('Route container must not be null for lazy loaded middleware.');
129
            }
130
            return $this->container->get($middleware);
131
        }
132
133
        if (is_array($middleware) && !is_object($middleware[0])) {
134
            if ($this->container === null) {
135
                throw new InvalidArgumentException('Route container must not be null for handler action.');
136
            }
137
            return $this->wrapCallable($middleware);
138
        }
139
140
        if ($this->isCallable($middleware)) {
141
            if ($this->container === null) {
142
                throw new InvalidArgumentException('Route container must not be null for callable.');
143
            }
144
            return $this->wrapCallable($middleware);
145
        }
146
147
        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...
148
    }
149
150
    private function isCallable($definition): bool
151
    {
152
        if (is_callable($definition)) {
153
            return true;
154
        }
155
156
        return is_array($definition) && array_keys($definition) === [0, 1] && in_array($definition[1], get_class_methods($definition[0]) ?? [], true);
157
    }
158
}
159