Passed
Push — master ( b86ef6...5fed0d )
by Alexander
02:39
created

MiddlewareDispatcher   A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 42
c 7
b 0
f 0
dl 0
loc 125
ccs 37
cts 37
cp 1
rs 10
wmc 3

12 Methods

Rating   Name   Duplication   Size   Complexity  
A hp$0 ➔ handle() 0 9 1
A hp$1 ➔ __construct() 0 4 1
A addMiddleware() 0 17 3
A hp$1 ➔ getCallbackMiddleware() 0 20 1
wrap() 0 29 ?
A __construct() 0 6 1
A hp$1 ➔ process() 0 5 1
A hp$0 ➔ __construct() 0 8 1
A process() 0 12 3
A hp$0 ➔ wrap() 0 29 1
A dispatch() 0 3 1
getCallbackMiddleware() 0 20 ?
1
<?php
2
3
namespace Yiisoft\Yii\Web;
4
5
use Psr\Container\ContainerInterface;
6
use Psr\EventDispatcher\EventDispatcherInterface;
7
use Psr\Http\Message\ResponseFactoryInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Server\MiddlewareInterface;
11
use Psr\Http\Server\RequestHandlerInterface;
12
use Yiisoft\Injector\Injector;
13
use Yiisoft\Yii\Web\Event\AfterMiddleware;
14
use Yiisoft\Yii\Web\Event\BeforeMiddleware;
15
16
/**
17
 * MiddlewareDispatcher
18
 */
19
final class MiddlewareDispatcher
20
{
21
    /**
22
     * @var MiddlewareInterface[]
23
     */
24
    private array $middlewares = [];
25
26
    private RequestHandlerInterface $nextHandler;
27
    private ContainerInterface $container;
28
29
    /**
30
     * Contains a chain of middleware wrapped in handlers.
31
     * Each handler points to the handler of middleware that will be processed next.
32
     *
33
     * @var RequestHandlerInterface|null stack of middleware
34
     */
35
    private ?RequestHandlerInterface $stack = null;
36
37 2
    public function __construct(
38
        ContainerInterface $container,
39
        RequestHandlerInterface $nextHandler = null
40
    ) {
41 2
        $this->container = $container;
42 2
        $this->nextHandler = $nextHandler ?? new NotFoundHandler($container->get(ResponseFactoryInterface::class));
43 2
    }
44
45
    /**
46
     * @param callable|MiddlewareInterface $middleware
47
     * @return self
48
     */
49 2
    public function addMiddleware($middleware): self
50
    {
51 2
        if (is_callable($middleware)) {
52 1
            $middleware = $this->getCallbackMiddleware($middleware, $this->container);
0 ignored issues
show
Bug introduced by
It seems like $middleware can also be of type Psr\Http\Server\MiddlewareInterface; however, parameter $callback of Yiisoft\Yii\Web\Middlewa...getCallbackMiddleware() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

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

52
            $middleware = $this->getCallbackMiddleware(/** @scrutinizer ignore-type */ $middleware, $this->container);
Loading history...
53
        }
54
55 2
        if (!$middleware instanceof MiddlewareInterface) {
56 1
            throw new \InvalidArgumentException(
57 1
                'Middleware should be either callable or MiddlewareInterface instance. ' . get_class(
58 1
                    $middleware
0 ignored issues
show
Bug introduced by
$middleware of type callable is incompatible with the type object expected by parameter $object of get_class(). ( Ignorable by Annotation )

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

58
                    /** @scrutinizer ignore-type */ $middleware
Loading history...
59 1
                ) . ' given.'
60
            );
61
        }
62
63 1
        $this->middlewares[] = $middleware;
64
65 1
        return $this;
66
    }
67
68 1
    public function dispatch(ServerRequestInterface $request): ResponseInterface
69
    {
70 1
        return $this->process($request, $this->nextHandler);
71
    }
72
73 1
    private function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
74
    {
75 1
        if ($this->stack === null) {
76 1
            $dispatcher = $this->container->get(EventDispatcherInterface::class);
77
78 1
            foreach ($this->middlewares as $middleware) {
79 1
                $handler = $this->wrap($middleware, $handler, $dispatcher);
80
            }
81 1
            $this->stack = $handler;
82
        }
83
84 1
        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

84
        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...
85
    }
86
87
    /**
88
     * Wraps handler by middlewares
89
     */
90 1
    private function wrap(
91
        MiddlewareInterface $middleware,
92
        RequestHandlerInterface $handler,
93
        EventDispatcherInterface $dispatcher
94
    ): RequestHandlerInterface {
95
        return new class($middleware, $handler, $dispatcher) implements RequestHandlerInterface {
96
            private MiddlewareInterface $middleware;
97
            private RequestHandlerInterface $handler;
98
            private EventDispatcherInterface $dispatcher;
99
100
            public function __construct(
101
                MiddlewareInterface $middleware,
102
                RequestHandlerInterface $handler,
103
                EventDispatcherInterface $dispatcher
104
            ) {
105 1
                $this->middleware = $middleware;
106 1
                $this->handler = $handler;
107 1
                $this->dispatcher = $dispatcher;
108 1
            }
109
110
            public function handle(ServerRequestInterface $request): ResponseInterface
111
            {
112 1
                $this->dispatcher->dispatch(new BeforeMiddleware($this->middleware, $request));
113
114 1
                $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
115
                try {
116 1
                    return $response = $this->middleware->process($request, $this->handler);
117
                } finally {
118 1
                    $this->dispatcher->dispatch(new AfterMiddleware($this->middleware, $response));
119
                }
120
            }
121
        };
122
    }
123
124 1
    private function getCallbackMiddleware(callable $callback, ContainerInterface $container): MiddlewareInterface
125
    {
126
        return new class($callback, $container) implements MiddlewareInterface {
127
            /**
128
             * @var callable a PHP callback matching signature of [[MiddlewareInterface::process()]].
129
             */
130
            private $callback;
131
            private ContainerInterface $container;
132
133
            public function __construct(callable $callback, ContainerInterface $container)
134
            {
135 1
                $this->callback = $callback;
136 1
                $this->container = $container;
137 1
            }
138
139
            public function process(
140
                ServerRequestInterface $request,
141
                RequestHandlerInterface $handler
142
            ): ResponseInterface {
143 1
                return (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
144
            }
145
        };
146
    }
147
}
148