Passed
Pull Request — master (#1104)
by Maxim
10:52
created

Pipeline::handle()   B

Complexity

Conditions 6
Paths 15

Size

Total Lines 49
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 31
dl 0
loc 49
ccs 35
cts 35
cp 1
rs 8.8017
c 0
b 0
f 0
cc 6
nc 15
nop 1
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Http;
6
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use Psr\Http\Message\ResponseInterface as Response;
9
use Psr\Http\Message\ServerRequestInterface as Request;
10
use Psr\Http\Server\MiddlewareInterface;
11
use Psr\Http\Server\RequestHandlerInterface;
12
use Spiral\Core\Attribute\Proxy;
13
use Spiral\Core\ContainerScope;
14
use Spiral\Core\ScopeInterface;
15
use Spiral\Http\Event\MiddlewareProcessing;
16
use Spiral\Http\Exception\PipelineException;
17
use Spiral\Http\Traits\MiddlewareTrait;
18
use Spiral\Telemetry\NullTracer;
19
use Spiral\Telemetry\SpanInterface;
20
use Spiral\Telemetry\TracerInterface;
21
22
/**
23
 * Pipeline used to pass request and response thought the chain of middleware.
24
 */
25
final class Pipeline implements RequestHandlerInterface, MiddlewareInterface
26
{
27
    use MiddlewareTrait;
28
29
    private int $position = 0;
30
    private readonly TracerInterface $tracer;
31
    private ?RequestHandlerInterface $handler = null;
32
33 507
    public function __construct(
34
        #[Proxy] private readonly ScopeInterface $scope,
35
        private readonly ?EventDispatcherInterface $dispatcher = null,
36
        ?TracerInterface $tracer = null
37
    ) {
38 507
        $this->tracer = $tracer ?? new NullTracer($scope);
0 ignored issues
show
Bug introduced by
The property tracer is declared read-only in Spiral\Http\Pipeline.
Loading history...
39
    }
40
41
    /**
42
     * Configures pipeline with target endpoint.
43
     *
44
     * @throws PipelineException
45
     */
46 131
    public function withHandler(RequestHandlerInterface $handler): self
47
    {
48 131
        $pipeline = clone $this;
49 131
        $pipeline->handler = $handler;
50 131
        $pipeline->position = 0;
51
52 131
        return $pipeline;
53
    }
54
55 53
    public function process(Request $request, RequestHandlerInterface $handler): Response
56
    {
57 53
        return $this->withHandler($handler)->handle($request);
58
    }
59
60 132
    public function handle(Request $request): Response
61
    {
62 132
        if ($this->handler === null) {
63 1
            throw new PipelineException('Unable to run pipeline, no handler given.');
64
        }
65
66
        // todo: find a better solution in the Spiral v4.0
67
        /** @var CurrentRequest|null $currentRequest */
68 131
        $currentRequest = ContainerScope::getContainer()?->get(CurrentRequest::class);
69
70 131
        $previousRequest = $currentRequest?->get();
71 131
        $currentRequest?->set($request);
72
        try {
73 131
            $position = $this->position++;
74 131
            if (!isset($this->middleware[$position])) {
75 120
                return $this->handler->handle($request);
76
            }
77
78 91
            $middleware = $this->middleware[$position];
79 91
            $this->dispatcher?->dispatch(new MiddlewareProcessing($request, $middleware));
80
81 91
            $callback = function (SpanInterface $span) use ($request, $middleware): Response {
82 91
                $response = $middleware->process($request, $this);
83
84 87
                $span
85 87
                    ->setAttribute(
86 87
                        'http.status_code',
87 87
                        $response->getStatusCode()
88 87
                    )
89 87
                    ->setAttribute(
90 87
                        'http.response_content_length',
91 87
                        $response->getHeaderLine('Content-Length') ?: $response->getBody()->getSize()
92 87
                    )
93 87
                    ->setStatus($response->getStatusCode() < 500 ? 'OK' : 'ERROR');
94
95 87
                return $response;
96 91
            };
97
98 91
            return $this->tracer->trace(
99 91
                name: \sprintf('Middleware processing [%s]', $middleware::class),
100 91
                callback: $callback,
101 91
                attributes: [
102 91
                    'http.middleware' => $middleware::class,
103 91
                ],
104 91
                scoped: true
105 91
            );
106
        } finally {
107 131
            if ($previousRequest !== null) {
108 131
                $currentRequest?->set($previousRequest);
109
            }
110
        }
111
    }
112
}
113