Passed
Pull Request — master (#1168)
by Aleksei
12:55
created

LazyPipeline   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 124
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
eloc 51
c 1
b 0
f 0
dl 0
loc 124
ccs 55
cts 55
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A withAddedMiddleware() 0 5 1
A resolveMiddleware() 0 6 2
A next() 0 6 1
A process() 0 3 1
A __construct() 0 4 1
A withHandler() 0 5 1
B handle() 0 45 6
A withMiddleware() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Http;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use Psr\Http\Message\ResponseInterface as Response;
10
use Psr\Http\Message\ServerRequestInterface as Request;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Spiral\Core\Attribute\Proxy;
14
use Spiral\Core\Container\Autowire;
15
use Spiral\Http\Event\MiddlewareProcessing;
16
use Spiral\Http\Exception\PipelineException;
17
use Spiral\Telemetry\SpanInterface;
18
use Spiral\Telemetry\TracerInterface;
19
20
/**
21
 * Pipeline used to pass request and response thought the chain of middleware.
22
 * This kind of pipeline creates middleware on the fly.
23
 */
24
final class LazyPipeline implements RequestHandlerInterface, MiddlewareInterface
25
{
26
    /**
27
     * Set of middleware to be applied for every request.
28
     *
29
     * @var list<MiddlewareInterface|Autowire|string>
0 ignored issues
show
Bug introduced by
The type Spiral\Http\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
30
     */
31
    protected array $middleware = [];
32
    private ?RequestHandlerInterface $handler = null;
33
    private int $position = 0;
34
    /**
35
     * Trace span for the current pipeline run.
36
     */
37
    private ?SpanInterface $span = null;
38
39 438
    public function __construct(
40
        #[Proxy] private readonly ContainerInterface $container,
41
        private readonly ?EventDispatcherInterface $dispatcher = null,
42
    ) {
43 438
    }
44
45
    /**
46
     * Add middleware to the pipeline.
47
     *
48
     * @param MiddlewareInterface ...$middleware List of middleware or its definition.
49
     */
50 49
    public function withAddedMiddleware(MiddlewareInterface|Autowire|string ...$middleware): self
51
    {
52 49
        $pipeline = clone $this;
53 49
        $pipeline->middleware = \array_merge($pipeline->middleware, $middleware);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($pipeline->middleware, $middleware) of type array is incompatible with the declared type Spiral\Http\list of property $middleware.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
54 49
        return $pipeline;
55
    }
56
57
    /**
58
     * Replace middleware in the pipeline.
59
     *
60
     * @param MiddlewareInterface ...$middleware List of middleware or its definition.
61
     */
62 436
    public function withMiddleware(MiddlewareInterface|Autowire|string ...$middleware): self
63
    {
64 436
        $pipeline = clone $this;
65 436
        $pipeline->middleware = $middleware;
0 ignored issues
show
Documentation Bug introduced by
It seems like $middleware of type array<integer,Psr\Http\S...tainer\Autowire|string> is incompatible with the declared type Spiral\Http\list of property $middleware.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
66 436
        return $pipeline;
67
    }
68
69
    /**
70
     * Configures pipeline with target endpoint.
71
     *
72
     * @throws PipelineException
73
     */
74 72
    public function withHandler(RequestHandlerInterface $handler): self
75
    {
76 72
        $pipeline = clone $this;
77 72
        $pipeline->handler = $handler;
78 72
        return $pipeline;
79
    }
80
81 54
    public function process(Request $request, RequestHandlerInterface $handler): Response
82
    {
83 54
        return $this->withHandler($handler)->handle($request);
84
    }
85
86 72
    public function handle(Request $request): Response
87
    {
88 72
        $this->handler === null and throw new PipelineException('Unable to run pipeline, no handler given.');
89
90
        /** @var CurrentRequest $currentRequest */
91 72
        $currentRequest = $this->container->get(CurrentRequest::class);
92
93 72
        $previousRequest = $currentRequest->get();
94 72
        $currentRequest->set($request);
95
        try {
96
            // There is no middleware to process, let's pass the request to the handler
97 72
            if (!\array_key_exists($this->position, $this->middleware)) {
98 70
                return $this->handler->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

98
                return $this->handler->/** @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...
99
            }
100
101 45
            $middleware = $this->resolveMiddleware($this->position);
102 45
            $this->dispatcher?->dispatch(new MiddlewareProcessing($request, $middleware));
103
104 45
            $span = $this->span;
105
106 45
            $middlewareTitle = \is_string($this->middleware[$this->position])
107 45
            && $this->middleware[$this->position] !== $middleware::class
108 12
                ? \sprintf('%s=%s', $this->middleware[$this->position], $middleware::class)
109 45
                : $middleware::class;
110
            // Init a tracing span when the pipeline starts
111 45
            if ($span === null) {
112
                /** @var TracerInterface $tracer */
113 45
                $tracer = $this->container->get(TracerInterface::class);
114 45
                return $tracer->trace(
115 45
                    name: 'HTTP Pipeline',
116 45
                    callback: function (SpanInterface $span) use ($request, $middleware, $middlewareTitle): Response {
117 45
                        $span->setAttribute('http.middleware', [$middlewareTitle]);
118 45
                        return $middleware->process($request, $this->next($span));
119 45
                    },
120 45
                    scoped: true,
121 45
                );
122
            }
123
124 42
            $middlewares = $span->getAttribute('http.middleware') ?? [];
125 42
            $middlewares[] = $middlewareTitle;
126 42
            $span->setAttribute('http.middleware', $middlewares);
127
128 42
            return $middleware->process($request, $this->next($span));
129
        } finally {
130 72
            $currentRequest->set($previousRequest);
131
        }
132
    }
133
134 45
    private function next(SpanInterface $span): self
135
    {
136 45
        $pipeline = clone $this;
137 45
        ++$pipeline->position;
138 45
        $pipeline->span = $span;
139 45
        return $pipeline;
140
    }
141
142 45
    private function resolveMiddleware(int $position): MiddlewareInterface
143
    {
144 45
        $middleware = $this->middleware[$position];
145 45
        return $middleware instanceof MiddlewareInterface
146 3
            ? $middleware
147 45
            : $this->container->get($middleware);
148
    }
149
}
150