1
|
|
|
<?php |
2
|
|
|
namespace Dshafik\GuzzleHttp; |
3
|
|
|
|
4
|
|
|
use \GuzzleHttp\Psr7\Response; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* guzzlehttp-vcr middleware |
8
|
|
|
* |
9
|
|
|
* Records and automatically replays responses on subsequent requests |
10
|
|
|
* for unit testing |
11
|
|
|
* |
12
|
|
|
* @package Dshafik\GuzzleHttp |
13
|
|
|
*/ |
14
|
|
|
class VcrHandler |
15
|
|
|
{ |
16
|
|
|
const CONFIG_ONLY_ENCODE_BINARY = 'only_encode_binary'; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var string |
20
|
|
|
*/ |
21
|
|
|
protected $cassette; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Configuration values |
25
|
|
|
* |
26
|
|
|
* @var array |
27
|
|
|
*/ |
28
|
|
|
protected static $config; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @param string $cassette fixture path |
32
|
|
|
* @return \GuzzleHttp\HandlerStack |
33
|
|
|
*/ |
34
|
5 |
|
public static function turnOn($cassette, array $config = null) |
35
|
|
|
{ |
36
|
5 |
|
if (! is_null($config)) { |
37
|
|
|
static::$config = $config; |
38
|
|
|
} |
39
|
|
|
|
40
|
5 |
|
if (!file_exists($cassette)) { |
41
|
4 |
|
$handler = \GuzzleHttp\HandlerStack::create(); |
42
|
4 |
|
$handler->after('allow_redirects', new static($cassette), 'vcr_recorder'); |
43
|
4 |
|
return $handler; |
44
|
|
|
} else { |
45
|
5 |
|
$responses = self::decodeResponses($cassette); |
46
|
|
|
|
47
|
5 |
|
$queue = []; |
48
|
5 |
|
$class = new \ReflectionClass(\GuzzleHttp\Psr7\Response::class); |
49
|
5 |
|
foreach ($responses as $response) { |
50
|
5 |
|
$queue[] = $class->newInstanceArgs($response); |
51
|
|
|
} |
52
|
|
|
|
53
|
5 |
|
return \GuzzleHttp\HandlerStack::create(new \GuzzleHttp\Handler\MockHandler($queue)); |
54
|
|
|
} |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Constructor |
59
|
|
|
* |
60
|
|
|
* @param string $cassette fixture path |
61
|
|
|
*/ |
62
|
4 |
|
protected function __construct($cassette) |
63
|
|
|
{ |
64
|
4 |
|
$this->cassette = $cassette; |
65
|
4 |
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Returns configuration value by name |
69
|
|
|
* |
70
|
|
|
* @param string $name Name of configuration value |
71
|
|
|
* |
72
|
|
|
* @return null|mixed |
73
|
|
|
*/ |
74
|
5 |
|
protected static function getConfig($name = null) |
75
|
|
|
{ |
76
|
5 |
|
if (is_null($name)) { |
77
|
|
|
return static::$config; |
78
|
|
|
} |
79
|
|
|
|
80
|
5 |
|
if (! static::$config || ! isset(static::$config[$name])) { |
|
|
|
|
81
|
5 |
|
return null; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return $config[$name]; |
|
|
|
|
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Returns True if response content is binary |
89
|
|
|
* |
90
|
|
|
* @param GuzzleHttp\Psr7\Response $response Response object |
91
|
|
|
* |
92
|
|
|
* @return boolean |
93
|
|
|
*/ |
94
|
|
|
protected static function isBinary(Response $response) |
95
|
|
|
{ |
96
|
|
|
return strpos($response->getContentType(), 'application/x-gzip') !== false |
|
|
|
|
97
|
|
|
|| $response->getHeader('Content-Transfer-Encoding') == 'binary'; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Resolve an object Response from given value. |
102
|
|
|
* If value is already a Response returns value. |
103
|
|
|
* If value is an array attempts to create a Response object |
104
|
|
|
* otherwise throws an exception. |
105
|
|
|
* |
106
|
|
|
* @param Response|array $value |
107
|
|
|
* |
108
|
|
|
* @throws \InvalidArgumentException |
109
|
|
|
* |
110
|
|
|
* @return void |
111
|
|
|
*/ |
112
|
5 |
|
protected static function ensureResponse($value) |
113
|
|
|
{ |
114
|
5 |
|
if ($value instanceof Response) { |
115
|
4 |
|
return $value; |
116
|
|
|
} |
117
|
|
|
|
118
|
5 |
|
if (! is_array($value)) { |
119
|
|
|
throw new \InvalidArgumentException('Invalid value for response'); |
120
|
|
|
} |
121
|
|
|
|
122
|
5 |
|
return new Response( |
123
|
5 |
|
$value['status'], |
124
|
5 |
|
$value['headers'], |
125
|
5 |
|
$value['body'], |
126
|
5 |
|
$value['version'], |
127
|
5 |
|
$value['reason'] |
128
|
|
|
); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Encode body content from given response. |
133
|
|
|
* |
134
|
|
|
* @param Response|array $response Response value |
135
|
|
|
* |
136
|
|
|
* @return string |
137
|
|
|
*/ |
138
|
4 |
View Code Duplication |
protected static function encodeBodyFrom($response) |
|
|
|
|
139
|
|
|
{ |
140
|
4 |
|
$response = static::ensureResponse($response); |
141
|
4 |
|
$body = (string) $response->getBody(); |
142
|
|
|
|
143
|
4 |
|
if (static::getConfig(self::CONFIG_ONLY_ENCODE_BINARY) |
144
|
4 |
|
&& ! static::isBinary($response) |
145
|
|
|
) { |
146
|
|
|
return $body; |
147
|
|
|
} |
148
|
|
|
|
149
|
4 |
|
return \base64_encode($body); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Decode body content from given response. |
154
|
|
|
* |
155
|
|
|
* @param Response|array $response Response value |
156
|
|
|
* |
157
|
|
|
* @return string |
158
|
|
|
*/ |
159
|
5 |
View Code Duplication |
protected static function decodeBodyFrom($response) |
|
|
|
|
160
|
|
|
{ |
161
|
5 |
|
$response = static::ensureResponse($response); |
162
|
5 |
|
$body = (string) $response->getBody(); |
163
|
|
|
|
164
|
5 |
|
if (static::getConfig(self::CONFIG_ONLY_ENCODE_BINARY) |
165
|
5 |
|
&& ! static::isBinary($response) |
166
|
|
|
) { |
167
|
|
|
return $body; |
168
|
|
|
} |
169
|
|
|
|
170
|
5 |
|
return \base64_decode($body); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Decodes every responses body from base64 |
175
|
|
|
* |
176
|
|
|
* @param $cassette |
177
|
|
|
* @return array |
178
|
|
|
*/ |
179
|
5 |
|
protected static function decodeResponses($cassette) |
180
|
|
|
{ |
181
|
5 |
|
$responses = json_decode(file_get_contents($cassette), true); |
182
|
|
|
|
183
|
5 |
|
array_walk( |
184
|
|
|
$responses, function (&$response) { |
185
|
5 |
|
$response['body'] = static::decodeBodyFrom($response); |
186
|
5 |
|
} |
187
|
|
|
); |
188
|
|
|
|
189
|
5 |
|
return $responses; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Handle the request/response |
194
|
|
|
* |
195
|
|
|
* @param callable $handler |
196
|
|
|
* @return \Closure |
197
|
|
|
*/ |
198
|
4 |
|
public function __invoke(callable $handler) |
199
|
|
|
{ |
200
|
|
|
return function (\Psr\Http\Message\RequestInterface $request, array $config) use ($handler) { |
201
|
4 |
|
return $handler($request, $config)->then( |
202
|
|
|
function (\Psr\Http\Message\ResponseInterface $response) use ($request) { |
203
|
4 |
|
$responses = []; |
204
|
4 |
|
if (file_exists($this->cassette)) { |
205
|
|
|
//No need to base64 decode body of response here. |
206
|
2 |
|
$responses = json_decode(file_get_contents($this->cassette), true); |
207
|
|
|
} |
208
|
4 |
|
$cassette = $response->withAddedHeader('X-VCR-Recording', time()); |
209
|
4 |
|
$responses[] = [ |
210
|
4 |
|
'status' => $cassette->getStatusCode(), |
211
|
4 |
|
'headers' => $cassette->getHeaders(), |
212
|
4 |
|
'body' => static::encodeBodyFrom($cassette), |
|
|
|
|
213
|
4 |
|
'version' => $cassette->getProtocolVersion(), |
214
|
4 |
|
'reason' => $cassette->getReasonPhrase() |
215
|
|
|
]; |
216
|
|
|
|
217
|
4 |
|
file_put_contents($this->cassette, json_encode($responses, JSON_PRETTY_PRINT)); |
218
|
4 |
|
return $response; |
219
|
4 |
|
}, |
220
|
4 |
|
function (\Exception $reason) { |
221
|
|
|
return new \GuzzleHttp\Promise\RejectedPromise($reason); |
222
|
4 |
|
} |
223
|
|
|
); |
224
|
4 |
|
}; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.