Passed
Pull Request — master (#336)
by Wilmer
02:46
created

MiddlewareDispatcher.php$0 ➔ wrap()   A

Complexity

Conditions 1

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 29
ccs 10
cts 10
cp 1
crap 1
rs 9.456
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A MiddlewareDispatcher.php$0 ➔ handle() 0 9 1
A MiddlewareDispatcher.php$0 ➔ __construct() 0 8 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use Psr\Http\Message\ResponseFactoryInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Psr\Http\Server\MiddlewareInterface;
13
use Psr\Http\Server\RequestHandlerInterface;
14
use Yiisoft\Injector\Injector;
15
use Yiisoft\Yii\Web\Event\AfterMiddleware;
16
use Yiisoft\Yii\Web\Event\BeforeMiddleware;
17
18
/**
19
 * MiddlewareDispatcher
20
 */
21
final class MiddlewareDispatcher
22
{
23
    /**
24
     * @var MiddlewareInterface[]
25
     */
26
    private array $middlewares = [];
27
28
    private RequestHandlerInterface $nextHandler;
29
    private ContainerInterface $container;
30
31
    /**
32
     * Contains a chain of middleware wrapped in handlers.
33
     * Each handler points to the handler of middleware that will be processed next.
34
     *
35
     * @var RequestHandlerInterface|null stack of middleware
36
     */
37
    private ?RequestHandlerInterface $stack = null;
38
39 7
    public function __construct(
40
        ContainerInterface $container,
41
        RequestHandlerInterface $nextHandler = null
42
    ) {
43 7
        $this->container = $container;
44 7
        $this->nextHandler = $nextHandler ?? new NotFoundHandler($container->get(ResponseFactoryInterface::class));
45 7
    }
46
47
    /**
48
     * @param callable|MiddlewareInterface $middleware
49
     * @return self
50
     */
51 7
    public function addMiddleware($middleware): self
52
    {
53 7
        if ($middleware instanceof MiddlewareInterface) {
54 5
            $this->middlewares[] = $middleware;
55 5
            return $this;
56
        }
57
58 2
        if (is_callable($middleware)) {
59 1
            $this->middlewares[] = $this->getCallbackMiddleware($middleware, $this->container);
60 1
            return $this;
61
        }
62
63 1
        throw new \InvalidArgumentException(
64
            'Middleware should be either callable or MiddlewareInterface instance. ' . get_class(
65 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

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

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