Passed
Pull Request — master (#307)
by
unknown
15:03
created

MiddlewareDispatcher::addMiddleware()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 3
c 1
b 1
f 0
dl 0
loc 16
ccs 8
cts 8
cp 1
crap 3
rs 9.9332
eloc 10
nc 3
nop 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 \SplStack
25
     */
26
    private \SplStack $middlewares;
27
28
    private RequestHandlerInterface $nextHandler;
29
    private ContainerInterface $container;
30
31
    public function __construct(
32
        ContainerInterface $container,
33
        RequestHandlerInterface $nextHandler = null
34
    ) {
35
        $this->middlewares = new \SplStack();
36
        $this->container = $container;
37
        $this->nextHandler = $nextHandler ?? new NotFoundHandler($container->get(ResponseFactoryInterface::class));
38
    }
39 7
40
    /**
41
     * @param callable|MiddlewareInterface $middleware
42
     * @return self
43 7
     */
44 7
    public function addMiddleware($middleware): self
45 7
    {
46
        if ($middleware instanceof MiddlewareInterface) {
47
            $this->middlewares->push($middleware);
48
            return $this;
49
        }
50
51 7
        if (is_callable($middleware)) {
52
            $this->middlewares->push($this->getCallbackMiddleware($middleware, $this->container));
53 7
            return $this;
54 5
        }
55 5
56
        throw new \InvalidArgumentException(
57
            'Middleware should be either callable or MiddlewareInterface instance. ' . get_class(
58 2
                $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 1
        );
61
    }
62
63 1
    public function dispatch(ServerRequestInterface $request): ResponseInterface
64 1
    {
65 1
        return $this->process($request, $this->nextHandler);
66 1
    }
67
68
    private function process(ServerRequestInterface $request, RequestHandlerInterface $nextHandler): ResponseInterface
69
    {
70 2
        $dispatcher = $this->container->get(EventDispatcherInterface::class);
71
72 2
        return $this->createMiddlewareStackHandler($nextHandler, $dispatcher)->handle($request);
73
    }
74
75 2
    private function createMiddlewareStackHandler(
76
        RequestHandlerInterface $nextHandler,
77 2
        EventDispatcherInterface $dispatcher
78 2
    ): RequestHandlerInterface {
79
        return new class($this->middlewares, $nextHandler, $dispatcher) implements RequestHandlerInterface {
80 2
            private ?\SplStack $stack;
81 2
            private RequestHandlerInterface $fallbackHandler;
82
            private EventDispatcherInterface $eventDispatcher;
83 2
84
            public function __construct(\SplStack $stack, RequestHandlerInterface $fallbackHandler, EventDispatcherInterface $eventDispatcher)
85
            {
86 2
                $this->stack = clone $stack;
87
                $this->fallbackHandler = $fallbackHandler;
88
                $this->eventDispatcher = $eventDispatcher;
89
            }
90
91
            public function handle(ServerRequestInterface $request): ResponseInterface
92 2
            {
93
                if ($this->stack === null) {
94
                    throw \RuntimeException('Middleware handler was called already');
0 ignored issues
show
Bug introduced by
The function RuntimeException was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

94
                    throw /** @scrutinizer ignore-call */ \RuntimeException('Middleware handler was called already');
Loading history...
95
                }
96
97 2
                if ($this->stack->isEmpty()) {
98
                    $this->stack = null;
99
                    return $this->fallbackHandler->handle($request);
100
                }
101
102
                /** @var MiddlewareInterface $middleware */
103
                $middleware = $this->stack->pop();
104
                $next = clone $this; // deep clone is not used intentionally
105
                $this->stack = null; // mark queue as processed at this nesting level
106
107 2
                $this->eventDispatcher->dispatch(new BeforeMiddleware($middleware, $request));
108 2
109 2
                $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
110 2
                try {
111
                    return $response = $middleware->process($request, $next);
112
                } finally {
113
                    $this->eventDispatcher->dispatch(new AfterMiddleware($middleware, $response));
114 2
                }
115
            }
116 2
        };
117
    }
118 2
119
    private function getCallbackMiddleware(callable $callback, ContainerInterface $container): MiddlewareInterface
120 2
    {
121
        return new class($callback, $container) implements MiddlewareInterface {
122
            /**
123
             * @var callable a PHP callback matching signature of [[MiddlewareInterface::process()]].
124
             */
125
            private $callback;
126 1
            private ContainerInterface $container;
127
128 1
            public function __construct(callable $callback, ContainerInterface $container)
129
            {
130
                $this->callback = $callback;
131
                $this->container = $container;
132
            }
133
134
            public function process(
135
                ServerRequestInterface $request,
136
                RequestHandlerInterface $handler
137 1
            ): ResponseInterface {
138 1
                return (new Injector($this->container))->invoke($this->callback, [$request, $handler]);
139 1
            }
140
        };
141
    }
142
}
143