Completed
Branch 09branch (946dde)
by Anton
05:16
created

HttpCore   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 180
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 12

Importance

Changes 0
Metric Value
dl 0
loc 180
rs 10
c 0
b 0
f 0
wmc 16
lcom 2
cbo 12

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setEmitter() 0 6 1
A setEndpoint() 0 6 1
B perform() 0 27 2
B __invoke() 0 18 5
A dispatch() 0 10 2
A initResponse() 0 4 1
A getEndpoint() 0 14 3
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\Http;
10
11
use Psr\Http\Message\ResponseInterface as Response;
12
use Psr\Http\Message\ServerRequestInterface as Request;
13
use Spiral\Core\Component;
14
use Spiral\Core\ContainerInterface;
15
use Spiral\Debug\Traits\BenchmarkTrait;
16
use Spiral\Http\Exceptions\ClientException;
17
use Spiral\Http\Exceptions\HttpException;
18
use Spiral\Http\Traits\MiddlewaresTrait;
19
use Zend\Diactoros\Response as ZendResponse;
20
use Zend\Diactoros\Response\EmitterInterface;
21
22
/**
23
 * Magically simple implementation of PRS7 Http core.
24
 */
25
class HttpCore extends Component implements HttpInterface
26
{
27
    use BenchmarkTrait, MiddlewaresTrait;
28
29
    /**
30
     * @var EmitterInterface
31
     */
32
    private $emitter = null;
33
34
    /**
35
     * Dispatcher endpoint.
36
     *
37
     * @var string|callable|null
38
     */
39
    private $endpoint = null;
40
41
    /**
42
     * @invisible
43
     * @var ContainerInterface
44
     */
45
    protected $container = null;
46
47
    /**
48
     * @param callable|null|string $endpoint    Default endpoint, Router in HttpDispatcher.
49
     * @param array                $middlewares Set of http middlewares to run on every request.
50
     * @param ContainerInterface   $container   Https requests are executed in a container scopes.
51
     */
52
    public function __construct(
53
        callable $endpoint = null,
54
        array $middlewares = [],
55
        ContainerInterface $container = null
56
    ) {
57
        $this->container = $container;
58
        $this->middlewares = $middlewares;
59
        $this->endpoint = $endpoint;
60
    }
61
62
    /**
63
     * @param EmitterInterface $emitter
64
     *
65
     * @return $this|self
66
     */
67
    public function setEmitter(EmitterInterface $emitter): HttpCore
68
    {
69
        $this->emitter = $emitter;
70
71
        return $this;
72
    }
73
74
    /**
75
     * Set endpoint as callable function or invokable class name (will be resolved using container).
76
     *
77
     * @param callable|string $endpoint
78
     *
79
     * @return $this|self
80
     */
81
    public function setEndpoint($endpoint): HttpCore
82
    {
83
        $this->endpoint = $endpoint;
84
85
        return $this;
86
    }
87
88
    /**
89
     * Pass request thought all http middlewares to appropriate endpoint. Default endpoint will be
90
     * used as fallback. Can thrown an exception happen in internal code.
91
     *
92
     * @param Request  $request
93
     * @param Response $response
94
     *
95
     * @return Response
96
     *
97
     * @throws HttpException
98
     */
99
    public function perform(Request $request, Response $response = null): Response
100
    {
101
        //Init response with default headers and etc
102
        $response = $response ?? $this->initResponse();
103
104
        $endpoint = $this->getEndpoint();
105
        if (empty($endpoint)) {
106
            throw new HttpException("Unable to execute request without destination endpoint");
107
        }
108
109
        $pipeline = new MiddlewarePipeline($this->middlewares, $this->container);
110
111
        //Ensure global container scope
112
        $scope = self::staticContainer($this->container);
113
114
        //Working in a scope
115
        $benchmark = $this->benchmark('request', $request->getUri());
116
        try {
117
            //Exceptions (including client one) must be handled by pipeline
118
            return $pipeline->target($endpoint)->run($request, $response);
119
        } finally {
120
            $this->benchmark($benchmark);
121
122
            //Restore global container scope
123
            self::staticContainer($scope);
124
        }
125
    }
126
127
    /**
128
     * Running spiral as middleware.
129
     *
130
     * @param Request  $request
131
     * @param Response $response
132
     * @param callable $next
133
     *
134
     * @return Response
135
     *
136
     * @throws HttpException
137
     */
138
    public function __invoke(Request $request, Response $response, callable $next): Response
139
    {
140
        try {
141
            $response = $this->perform($request, $response);
142
        } catch (ClientException $e) {
143
            if ($e->getCode() != 404) {
144
                //Server, forbidden and other exceptions
145
                throw new $e;
146
            }
147
        }
148
149
        if (!empty($response) && $response->getStatusCode() != 404) {
150
            //Not empty response
151
            return $response;
152
        }
153
154
        return $next($request, $response);
155
    }
156
157
    /**
158
     * Dispatch response to client.
159
     *
160
     * @param Response $response
161
     *
162
     * @return null Specifically.
163
     */
164
    public function dispatch(Response $response)
165
    {
166
        if (empty($this->emitter)) {
167
            $this->emitter = new ZendResponse\SapiEmitter();
168
        }
169
170
        $this->emitter->emit($response, ob_get_level());
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with ob_get_level().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
171
172
        return null;
173
    }
174
175
    /**
176
     * Create instance of initial response.
177
     *
178
     * @return Response
179
     */
180
    protected function initResponse(): Response
181
    {
182
        return new ZendResponse('php://memory');
183
    }
184
185
    /**
186
     * Default endpoint.
187
     *
188
     * @return callable|null
189
     */
190
    protected function getEndpoint()
191
    {
192
        if (empty($this->endpoint)) {
193
            return null;
194
        }
195
196
        if (!is_string($this->endpoint)) {
197
            //Presumably callable
198
            return $this->endpoint;
199
        }
200
201
        //Specified as class name
202
        return $this->container->get($this->endpoint);
203
    }
204
}
205