Passed
Pull Request — master (#34)
by Aleksei
02:10
created

SquashedMiddleware::__destruct()

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 1
c 2
b 1
f 0
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Middleware\Dispatcher;
6
7
use Generator;
8
use Iterator;
9
use Psr\EventDispatcher\EventDispatcherInterface;
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\Middleware\Dispatcher\Event\AfterMiddleware;
15
use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware;
16
use Yiisoft\Middleware\Dispatcher\Exception\InvalidMiddlewareDefinitionException;
17
18
final class SquashedMiddleware implements MiddlewareInterface
19
{
20
    private RequestHandlerInterface $handler;
21
22 7
    private function __construct(
23
        iterable $middlewares,
24
        ?MiddlewareFactoryInterface $factory = null,
25
        ?EventDispatcherInterface $dispatcher = null
26
    ) {
27 7
        $this->handler = $this->createHandler($this->iterateMiddlewares($middlewares, $factory), $dispatcher);
28 7
    }
29
30
    /**
31
     * @param iterable $middlewares Middlewares to squash. It can be definitions for the MiddlewareFactoryInterface in
32
     * case when the $factory parameter specified.
33
     * @param MiddlewareFactoryInterface|null $factory Specify this parameter for middleware definitions resolving.
34
     * @param EventDispatcherInterface|null $dispatcher Specify this parameter if you need to listen related events:
35
     * - {@see \Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware}
36
     * - {@see \Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware}
37
     *
38
     * @return static
39
     */
40 7
    public static function create(
41
        iterable $middlewares,
42
        ?MiddlewareFactoryInterface $factory = null,
43
        ?EventDispatcherInterface $dispatcher = null
44
    ): self {
45 7
        return new self($middlewares, $factory, $dispatcher);
46
    }
47
48 7
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
49
    {
50
        /**
51
         * @psalm-suppress UndefinedInterfaceMethod
52
         *
53
         * @var RequestHandlerInterface $newHandler
54
         */
55 7
        $newHandler = $this->handler->withRequestHandler($handler);
0 ignored issues
show
Bug introduced by
The method withRequestHandler() does not exist on Psr\Http\Server\RequestHandlerInterface. It seems like you code against a sub-type of Psr\Http\Server\RequestHandlerInterface such as anonymous//src/SquashedMiddleware.php$0. ( Ignorable by Annotation )

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

55
        /** @scrutinizer ignore-call */ 
56
        $newHandler = $this->handler->withRequestHandler($handler);
Loading history...
56 7
        return $newHandler->handle($request);
57
    }
58
59
    /**
60
     * @psalm-param Iterator<mixed, MiddlewareInterface> $iterator
61
     */
62 7
    private function createHandler(Iterator $iterator, ?EventDispatcherInterface $dispatcher): RequestHandlerInterface
63
    {
64 7
        return new class(null, $iterator, $dispatcher) implements RequestHandlerInterface {
65
            private ?self $root;
66
            private ?self $nextHandler = null;
67
68
            /** @var Iterator<mixed, MiddlewareInterface>|null  */
69
            private ?Iterator $iterator;
70
            private ?MiddlewareInterface $middleware = null;
71
            private ?EventDispatcherInterface $dispatcher;
72
            /** @psalm-suppress PropertyNotSetInConstructor */
73
            private RequestHandlerInterface $fallbackHandler;
74
75
            public function __clone()
76
            {
77 7
                if ($this->root !== null) {
78
                    return;
79
                }
80 7
                $current = $this;
81 7
                while ($current->nextHandler !== null) {
82
                    $current->nextHandler = clone $current->nextHandler;
83
                    $current->nextHandler->root = $this;
84
                    $current = $current->nextHandler;
85
                }
86 7
            }
87
88
            final public function withRequestHandler(RequestHandlerInterface $handler): self
89
            {
90 7
                $new = clone $this;
91 7
                $new->fallbackHandler = $handler;
92 7
                return $new;
93
            }
94
95
            /**
96
             * @psalm-param Iterator<mixed, MiddlewareInterface> $iterator
97
             */
98
            public function __construct(?self $root, Iterator $iterator, ?EventDispatcherInterface $dispatcher)
99
            {
100 7
                $this->iterator = $iterator;
101 7
                $this->dispatcher = $dispatcher;
102 7
                $this->root = $root;
103 7
            }
104
105
            /**
106
             * @psalm-suppress PossiblyNullReference
107
             * @psalm-suppress PossiblyNullArgument
108
             */
109
            final public function handle(ServerRequestInterface $request): ResponseInterface
110
            {
111
                // If Middleware is not cached
112 7
                if ($this->middleware === null) {
113 7
                    $this->middleware = $this->iterator->current();
0 ignored issues
show
Bug introduced by
The method current() 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

113
                    /** @scrutinizer ignore-call */ 
114
                    $this->middleware = $this->iterator->current();

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...
114 7
                    $this->iterator->next();
115 7
                    if ($this->iterator->valid()) {
116 3
                        $this->nextHandler = new self($this->root ?? $this, $this->iterator, $this->dispatcher);
0 ignored issues
show
Bug introduced by
It seems like $this->iterator can also be of type null; however, parameter $iterator of anonymous//src/SquashedM...re.php$0::__construct() does only seem to accept Iterator, 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

116
                        $this->nextHandler = new self($this->root ?? $this, /** @scrutinizer ignore-type */ $this->iterator, $this->dispatcher);
Loading history...
117
                    }
118 7
                    $this->iterator = null;
119
                }
120 7
                return $this->processMiddleware($request);
121
            }
122
123
            private function processMiddleware(ServerRequestInterface $request): ResponseInterface
124
            {
125 7
                $nextHandler = $this->nextHandler ?? ($this->root ?? $this)->fallbackHandler;
126 7
                if ($this->middleware === null) {
127 1
                    return $nextHandler->handle($request);
128
                }
129
                // If the event dispatcher not exists
130 7
                if ($this->dispatcher === null) {
131 5
                    return $this->middleware->process($request, $nextHandler);
132
                }
133 2
                $this->dispatcher->dispatch(new BeforeMiddleware($this->middleware, $request));
134
                try {
135 2
                    return $response = $this->middleware->process($request, $nextHandler);
136
                } finally {
137 2
                    $this->dispatcher->dispatch(new AfterMiddleware($this->middleware, $response ?? null));
138
                }
139
            }
140
141
            public function __destruct()
142
            {
143 6
                $this->root = null;
144 6
                $this->nextHandler = null;
145 6
            }
146
        };
147
    }
148
149
    /**
150
     * @psalm-param iterable<mixed, mixed> $middlewares
151
     * @psalm-return Generator<int, MiddlewareInterface>
152
     */
153 7
    private function iterateMiddlewares(iterable $middlewares, ?MiddlewareFactoryInterface $factory): Generator
154
    {
155
        /** @var mixed $middleware */
156 7
        foreach ($middlewares as $middleware) {
157 7
            if ($middleware instanceof MiddlewareInterface) {
158
                yield $middleware;
159
            }
160 7
            if ($factory === null) {
161
                // todo: create better and friendly exception
162
                throw new InvalidMiddlewareDefinitionException(
163
                    $middleware,
164
                    'Invalid middleware. If you pass middleware definition then pass middleware factory also.'
165
                );
166
            }
167 7
            yield $factory->create($middleware);
168
        }
169 6
    }
170
171 6
    public function __destruct()
172
    {
173
        /** @psalm-suppress UndefinedInterfaceMethod */
174 6
        $this->handler->__destruct();
0 ignored issues
show
Bug introduced by
The method __destruct() does not exist on Psr\Http\Server\RequestHandlerInterface. It seems like you code against a sub-type of Psr\Http\Server\RequestHandlerInterface such as anonymous//src/SquashedMiddleware.php$0. ( Ignorable by Annotation )

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

174
        $this->handler->/** @scrutinizer ignore-call */ 
175
                        __destruct();
Loading history...
175 6
    }
176
}
177