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
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. 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.