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()); |
|
|
|
|
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
|
|
|
|
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.