|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Http\Adapter\React; |
|
4
|
|
|
|
|
5
|
|
|
use Psr\Http\Message\ResponseInterface; |
|
6
|
|
|
use React\EventLoop\LoopInterface; |
|
7
|
|
|
use React\Promise\Deferred; |
|
8
|
|
|
use React\HttpClient\Client as ReactClient; |
|
9
|
|
|
use React\HttpClient\Request as ReactRequest; |
|
10
|
|
|
use React\HttpClient\Response as ReactResponse; |
|
11
|
|
|
use Http\Client\HttpClient; |
|
12
|
|
|
use Http\Client\HttpAsyncClient; |
|
13
|
|
|
use Http\Client\Exception\HttpException; |
|
14
|
|
|
use Http\Client\Exception\RequestException; |
|
15
|
|
|
use Http\Message\MessageFactory; |
|
16
|
|
|
use Psr\Http\Message\RequestInterface; |
|
17
|
|
|
use Psr\Http\Message\StreamInterface; |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* Client for the React promise implementation. |
|
21
|
|
|
* |
|
22
|
|
|
* @author Stéphane Hulard <[email protected]> |
|
23
|
|
|
*/ |
|
24
|
|
|
class Client implements HttpClient, HttpAsyncClient |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* React HTTP client. |
|
28
|
|
|
* |
|
29
|
|
|
* @var Client |
|
30
|
|
|
*/ |
|
31
|
|
|
private $client; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* React event loop. |
|
35
|
|
|
* |
|
36
|
|
|
* @var LoopInterface |
|
37
|
|
|
*/ |
|
38
|
|
|
private $loop; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* HttpPlug message factory. |
|
42
|
|
|
* |
|
43
|
|
|
* @var MessageFactory |
|
44
|
|
|
*/ |
|
45
|
|
|
private $messageFactory; |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* Initialize the React client. |
|
49
|
|
|
* |
|
50
|
|
|
* @param MessageFactory $messageFactory |
|
51
|
|
|
* @param LoopInterface|null $loop React Event loop |
|
52
|
|
|
* @param ReactClient $client React client to use |
|
53
|
|
|
*/ |
|
54
|
108 |
|
public function __construct( |
|
55
|
|
|
MessageFactory $messageFactory, |
|
56
|
|
|
LoopInterface $loop = null, |
|
57
|
|
|
ReactClient $client = null |
|
58
|
|
|
) { |
|
59
|
108 |
|
if (null !== $client && null === $loop) { |
|
60
|
|
|
throw new \RuntimeException( |
|
61
|
|
|
'You must give a LoopInterface instance with the Client' |
|
62
|
1 |
|
); |
|
63
|
|
|
} |
|
64
|
108 |
|
$this->loop = (null !== $loop) ?: ReactFactory::buildEventLoop(); |
|
|
|
|
|
|
65
|
108 |
|
$this->client = (null !== $client) ?: ReactFactory::buildHttpClient($this->loop); |
|
|
|
|
|
|
66
|
|
|
|
|
67
|
108 |
|
$this->messageFactory = $messageFactory; |
|
68
|
108 |
|
} |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* {@inheritdoc} |
|
72
|
|
|
*/ |
|
73
|
53 |
|
public function sendRequest(RequestInterface $request) |
|
74
|
|
|
{ |
|
75
|
53 |
|
$promise = $this->sendAsyncRequest($request); |
|
76
|
|
|
|
|
77
|
53 |
|
return $promise->wait(); |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* {@inheritdoc} |
|
82
|
|
|
*/ |
|
83
|
108 |
|
public function sendAsyncRequest(RequestInterface $request) |
|
84
|
|
|
{ |
|
85
|
108 |
|
$reactRequest = $this->buildReactRequest($request); |
|
86
|
108 |
|
$deferred = new Deferred(); |
|
87
|
|
|
|
|
88
|
|
|
$reactRequest->on('error', function (\Exception $error) use ($deferred, $request) { |
|
89
|
3 |
|
$deferred->reject(new RequestException( |
|
90
|
3 |
|
$error->getMessage(), |
|
91
|
3 |
|
$request, |
|
92
|
|
|
$error |
|
93
|
3 |
|
)); |
|
94
|
108 |
|
}); |
|
95
|
|
|
$reactRequest->on('response', function (ReactResponse $reactResponse = null) use ($deferred, $reactRequest, $request) { |
|
96
|
105 |
|
$bodyStream = null; |
|
97
|
|
|
$reactResponse->on('data', function ($data) use (&$bodyStream) { |
|
|
|
|
|
|
98
|
105 |
|
if ($data instanceof StreamInterface) { |
|
99
|
105 |
|
$bodyStream = $data; |
|
100
|
105 |
|
} else { |
|
101
|
|
|
$bodyStream->write($data); |
|
|
|
|
|
|
102
|
|
|
} |
|
103
|
105 |
|
}); |
|
104
|
|
|
|
|
105
|
105 |
|
$reactResponse->on('end', function (\Exception $error = null) use ($deferred, $request, $reactResponse, &$bodyStream) { |
|
|
|
|
|
|
106
|
105 |
|
$bodyStream->rewind(); |
|
|
|
|
|
|
107
|
105 |
|
$response = $this->buildResponse( |
|
108
|
105 |
|
$reactResponse, |
|
|
|
|
|
|
109
|
|
|
$bodyStream |
|
|
|
|
|
|
110
|
105 |
|
); |
|
111
|
105 |
|
if (null !== $error) { |
|
112
|
|
|
$deferred->reject(new HttpException( |
|
113
|
|
|
$error->getMessage(), |
|
114
|
|
|
$request, |
|
115
|
|
|
$response, |
|
116
|
|
|
$error |
|
117
|
|
|
)); |
|
118
|
|
|
} else { |
|
119
|
105 |
|
$deferred->resolve($response); |
|
120
|
|
|
} |
|
121
|
105 |
|
}); |
|
122
|
108 |
|
}); |
|
123
|
|
|
|
|
124
|
108 |
|
$reactRequest->end((string) $request->getBody()); |
|
125
|
|
|
|
|
126
|
108 |
|
$promise = new Promise($deferred->promise()); |
|
127
|
108 |
|
$promise->setLoop($this->loop); |
|
128
|
|
|
|
|
129
|
108 |
|
return $promise; |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* Build a React request from the PSR7 RequestInterface. |
|
134
|
|
|
* |
|
135
|
|
|
* @param RequestInterface $request |
|
136
|
|
|
* |
|
137
|
|
|
* @return ReactRequest |
|
138
|
|
|
*/ |
|
139
|
108 |
|
private function buildReactRequest(RequestInterface $request) |
|
140
|
|
|
{ |
|
141
|
108 |
|
$headers = []; |
|
142
|
108 |
|
foreach ($request->getHeaders() as $name => $value) { |
|
143
|
108 |
|
$headers[$name] = (is_array($value) ? $value[0] : $value); |
|
144
|
108 |
|
} |
|
145
|
|
|
|
|
146
|
108 |
|
$reactRequest = $this->client->request( |
|
|
|
|
|
|
147
|
108 |
|
$request->getMethod(), |
|
148
|
108 |
|
(string) $request->getUri(), |
|
149
|
108 |
|
$headers, |
|
150
|
108 |
|
$request->getProtocolVersion() |
|
151
|
108 |
|
); |
|
152
|
|
|
|
|
153
|
108 |
|
return $reactRequest; |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
/** |
|
157
|
|
|
* Transform a React Response to a valid PSR7 ResponseInterface instance. |
|
158
|
|
|
* |
|
159
|
|
|
* @param ReactResponse $response |
|
160
|
|
|
* |
|
161
|
|
|
* @return ResponseInterface |
|
162
|
|
|
*/ |
|
163
|
105 |
|
private function buildResponse( |
|
164
|
|
|
ReactResponse $response, |
|
165
|
|
|
StreamInterface $body |
|
166
|
|
|
) { |
|
167
|
105 |
|
$body->rewind(); |
|
168
|
|
|
|
|
169
|
105 |
|
return $this->messageFactory->createResponse( |
|
170
|
105 |
|
$response->getCode(), |
|
171
|
105 |
|
$response->getReasonPhrase(), |
|
172
|
105 |
|
$response->getHeaders(), |
|
173
|
105 |
|
$body, |
|
174
|
105 |
|
$response->getVersion() |
|
175
|
105 |
|
); |
|
176
|
|
|
} |
|
177
|
|
|
} |
|
178
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.