1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Guzzle\Http\Message; |
4
|
|
|
|
5
|
|
|
use Guzzle\Common\Collection; |
6
|
|
|
use Guzzle\Common\Exception\InvalidArgumentException; |
7
|
|
|
use Guzzle\Http\RedirectPlugin; |
8
|
|
|
use Guzzle\Http\Url; |
9
|
|
|
use Guzzle\Parser\ParserRegistry; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Default HTTP request factory used to create the default {@see Request} and {@see EntityEnclosingRequest} objects. |
13
|
|
|
*/ |
14
|
|
|
class RequestFactory implements RequestFactoryInterface |
15
|
|
|
{ |
16
|
|
|
/** @var RequestFactory Singleton instance of the default request factory */ |
17
|
|
|
protected static $instance; |
18
|
|
|
|
19
|
|
|
/** @var array Hash of methods available to the class (provides fast isset() lookups) */ |
20
|
|
|
protected $methods; |
21
|
|
|
|
22
|
|
|
/** @var string Class to instantiate for requests with no body */ |
23
|
|
|
protected $requestClass = 'Guzzle\\Http\\Message\\Request'; |
24
|
|
|
|
25
|
|
|
/** @var string Class to instantiate for requests with a body */ |
26
|
|
|
protected $entityEnclosingRequestClass = 'Guzzle\\Http\\Message\\EntityEnclosingRequest'; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Get a cached instance of the default request factory |
30
|
|
|
* |
31
|
|
|
* @return RequestFactory |
32
|
|
|
*/ |
33
|
|
|
public static function getInstance() |
34
|
|
|
{ |
35
|
|
|
// @codeCoverageIgnoreStart |
36
|
|
|
if (!static::$instance) { |
37
|
|
|
static::$instance = new static(); |
38
|
|
|
} |
39
|
|
|
// @codeCoverageIgnoreEnd |
40
|
|
|
|
41
|
|
|
return static::$instance; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
public function __construct() |
45
|
|
|
{ |
46
|
|
|
$this->methods = array_flip(get_class_methods(__CLASS__)); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
public function fromMessage($message) |
50
|
|
|
{ |
51
|
|
|
$parsed = ParserRegistry::getInstance()->getParser('message')->parseRequest($message); |
52
|
|
|
|
53
|
|
|
if (!$parsed) { |
54
|
|
|
return false; |
|
|
|
|
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
$request = $this->fromParts($parsed['method'], $parsed['request_url'], |
58
|
|
|
$parsed['headers'], $parsed['body'], $parsed['protocol'], |
59
|
|
|
$parsed['version']); |
60
|
|
|
|
61
|
|
|
// EntityEnclosingRequest adds an "Expect: 100-Continue" header when using a raw request body for PUT or POST |
62
|
|
|
// requests. This factory method should accurately reflect the message, so here we are removing the Expect |
63
|
|
|
// header if one was not supplied in the message. |
64
|
|
|
if (!isset($parsed['headers']['Expect']) && !isset($parsed['headers']['expect'])) { |
65
|
|
|
$request->removeHeader('Expect'); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
return $request; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
public function fromParts( |
72
|
|
|
$method, |
73
|
|
|
array $urlParts, |
74
|
|
|
$headers = null, |
75
|
|
|
$body = null, |
76
|
|
|
$protocol = 'HTTP', |
77
|
|
|
$protocolVersion = '1.1' |
78
|
|
|
) { |
79
|
|
|
return $this->create($method, Url::buildUrl($urlParts), $headers, $body) |
80
|
|
|
->setProtocolVersion($protocolVersion); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
public function create($method, $url, $headers = null, $body = null, array $options = array()) |
84
|
|
|
{ |
85
|
|
|
$method = strtoupper($method); |
86
|
|
|
|
87
|
|
|
if ($method == 'GET' || $method == 'HEAD' || $method == 'TRACE') { |
88
|
|
|
// Handle non-entity-enclosing request methods |
89
|
|
|
$request = new $this->requestClass($method, $url, $headers); |
90
|
|
|
if ($body) { |
91
|
|
|
// The body is where the response body will be stored |
92
|
|
|
$type = gettype($body); |
93
|
|
|
if ($type == 'string' || $type == 'resource' || $type == 'object') { |
94
|
|
|
$request->setResponseBody($body); |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
} else { |
98
|
|
|
// Create an entity enclosing request by default |
99
|
|
|
$request = new $this->entityEnclosingRequestClass($method, $url, $headers); |
100
|
|
|
if ($body || $body === '0') { |
101
|
|
|
// Add POST fields and files to an entity enclosing request if an array is used |
102
|
|
|
if (is_array($body) || $body instanceof Collection) { |
103
|
|
|
// Normalize PHP style cURL uploads with a leading '@' symbol |
104
|
|
|
foreach ($body as $key => $value) { |
105
|
|
|
if (is_string($value) && substr($value, 0, 1) == '@') { |
106
|
|
|
$request->addPostFile($key, $value); |
107
|
|
|
unset($body[$key]); |
108
|
|
|
} |
109
|
|
|
} |
110
|
|
|
// Add the fields if they are still present and not all files |
111
|
|
|
$request->addPostFields($body); |
112
|
|
|
} else { |
113
|
|
|
// Add a raw entity body body to the request |
114
|
|
|
$request->setBody($body, (string) $request->getHeader('Content-Type')); |
115
|
|
|
if ((string) $request->getHeader('Transfer-Encoding') == 'chunked') { |
116
|
|
|
$request->removeHeader('Content-Length'); |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
if ($options) { |
|
|
|
|
123
|
|
|
$this->applyOptions($request, $options); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
return $request; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Clone a request while changing the method. Emulates the behavior of |
131
|
|
|
* {@see Guzzle\Http\Message\Request::clone}, but can change the HTTP method. |
132
|
|
|
* |
133
|
|
|
* @param RequestInterface $request Request to clone |
134
|
|
|
* @param string $method Method to set |
135
|
|
|
* |
136
|
|
|
* @return RequestInterface |
137
|
|
|
*/ |
138
|
|
|
public function cloneRequestWithMethod(RequestInterface $request, $method) |
139
|
|
|
{ |
140
|
|
|
// Create the request with the same client if possible |
141
|
|
|
if ($request->getClient()) { |
142
|
|
|
$cloned = $request->getClient()->createRequest($method, $request->getUrl(), $request->getHeaders()); |
|
|
|
|
143
|
|
|
} else { |
144
|
|
|
$cloned = $this->create($method, $request->getUrl(), $request->getHeaders()); |
|
|
|
|
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
$cloned->getCurlOptions()->replace($request->getCurlOptions()->toArray()); |
148
|
|
|
$cloned->setEventDispatcher(clone $request->getEventDispatcher()); |
149
|
|
|
// Ensure that that the Content-Length header is not copied if changing to GET or HEAD |
150
|
|
|
if (!($cloned instanceof EntityEnclosingRequestInterface)) { |
151
|
|
|
$cloned->removeHeader('Content-Length'); |
152
|
|
|
} elseif ($request instanceof EntityEnclosingRequestInterface) { |
153
|
|
|
$cloned->setBody($request->getBody()); |
|
|
|
|
154
|
|
|
} |
155
|
|
|
$cloned->getParams()->replace($request->getParams()->toArray()); |
156
|
|
|
$cloned->dispatch('request.clone', array('request' => $cloned)); |
157
|
|
|
|
158
|
|
|
return $cloned; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
public function applyOptions(RequestInterface $request, array $options = array(), $flags = self::OPTIONS_NONE) |
162
|
|
|
{ |
163
|
|
|
// Iterate over each key value pair and attempt to apply a config using function visitors |
164
|
|
|
foreach ($options as $key => $value) { |
165
|
|
|
$method = "visit_{$key}"; |
166
|
|
|
if (isset($this->methods[$method])) { |
167
|
|
|
$this->{$method}($request, $value, $flags); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
protected function visit_headers(RequestInterface $request, $value, $flags) |
173
|
|
|
{ |
174
|
|
|
if (!is_array($value)) { |
175
|
|
|
throw new InvalidArgumentException('headers value must be an array'); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
if ($flags & self::OPTIONS_AS_DEFAULTS) { |
179
|
|
|
// Merge headers in but do not overwrite existing values |
180
|
|
|
foreach ($value as $key => $header) { |
181
|
|
|
if (!$request->hasHeader($key)) { |
182
|
|
|
$request->setHeader($key, $header); |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
} else { |
186
|
|
|
$request->addHeaders($value); |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
protected function visit_body(RequestInterface $request, $value, $flags) |
|
|
|
|
191
|
|
|
{ |
192
|
|
|
if ($request instanceof EntityEnclosingRequestInterface) { |
193
|
|
|
$request->setBody($value); |
194
|
|
|
} else { |
195
|
|
|
throw new InvalidArgumentException('Attempting to set a body on a non-entity-enclosing request'); |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
protected function visit_allow_redirects(RequestInterface $request, $value, $flags) |
|
|
|
|
200
|
|
|
{ |
201
|
|
|
if ($value === false) { |
202
|
|
|
$request->getParams()->set(RedirectPlugin::DISABLE, true); |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
protected function visit_auth(RequestInterface $request, $value, $flags) |
|
|
|
|
207
|
|
|
{ |
208
|
|
|
if (!is_array($value)) { |
209
|
|
|
throw new InvalidArgumentException('auth value must be an array'); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
$request->setAuth($value[0], isset($value[1]) ? $value[1] : null, isset($value[2]) ? $value[2] : 'basic'); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
protected function visit_query(RequestInterface $request, $value, $flags) |
216
|
|
|
{ |
217
|
|
|
if (!is_array($value)) { |
218
|
|
|
throw new InvalidArgumentException('query value must be an array'); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
if ($flags & self::OPTIONS_AS_DEFAULTS) { |
222
|
|
|
// Merge query string values in but do not overwrite existing values |
223
|
|
|
$query = $request->getQuery(); |
224
|
|
|
$query->overwriteWith(array_diff_key($value, $query->toArray())); |
225
|
|
|
} else { |
226
|
|
|
$request->getQuery()->overwriteWith($value); |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
protected function visit_cookies(RequestInterface $request, $value, $flags) |
|
|
|
|
231
|
|
|
{ |
232
|
|
|
if (!is_array($value)) { |
233
|
|
|
throw new InvalidArgumentException('cookies value must be an array'); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
foreach ($value as $name => $v) { |
237
|
|
|
$request->addCookie($name, $v); |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
protected function visit_events(RequestInterface $request, $value, $flags) |
|
|
|
|
242
|
|
|
{ |
243
|
|
|
if (!is_array($value)) { |
244
|
|
|
throw new InvalidArgumentException('events value must be an array'); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
foreach ($value as $name => $method) { |
248
|
|
|
if (is_array($method)) { |
249
|
|
|
$request->getEventDispatcher()->addListener($name, $method[0], $method[1]); |
250
|
|
|
} else { |
251
|
|
|
$request->getEventDispatcher()->addListener($name, $method); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
protected function visit_plugins(RequestInterface $request, $value, $flags) |
|
|
|
|
257
|
|
|
{ |
258
|
|
|
if (!is_array($value)) { |
259
|
|
|
throw new InvalidArgumentException('plugins value must be an array'); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
foreach ($value as $plugin) { |
263
|
|
|
$request->addSubscriber($plugin); |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
protected function visit_exceptions(RequestInterface $request, $value, $flags) |
|
|
|
|
268
|
|
|
{ |
269
|
|
|
if ($value === false || $value === 0) { |
270
|
|
|
$dispatcher = $request->getEventDispatcher(); |
271
|
|
|
foreach ($dispatcher->getListeners('request.error') as $listener) { |
272
|
|
|
if (is_array($listener) && $listener[0] == 'Guzzle\Http\Message\Request' && $listener[1] = 'onRequestError') { |
273
|
|
|
$dispatcher->removeListener('request.error', $listener); |
274
|
|
|
break; |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
protected function visit_save_to(RequestInterface $request, $value, $flags) |
|
|
|
|
281
|
|
|
{ |
282
|
|
|
$request->setResponseBody($value); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
protected function visit_params(RequestInterface $request, $value, $flags) |
|
|
|
|
286
|
|
|
{ |
287
|
|
|
if (!is_array($value)) { |
288
|
|
|
throw new InvalidArgumentException('params value must be an array'); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
$request->getParams()->overwriteWith($value); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
protected function visit_timeout(RequestInterface $request, $value, $flags) |
|
|
|
|
295
|
|
|
{ |
296
|
|
|
if (defined('CURLOPT_TIMEOUT_MS')) { |
297
|
|
|
$request->getCurlOptions()->set(CURLOPT_TIMEOUT_MS, $value * 1000); |
298
|
|
|
} else { |
299
|
|
|
$request->getCurlOptions()->set(CURLOPT_TIMEOUT, $value); |
300
|
|
|
} |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
protected function visit_connect_timeout(RequestInterface $request, $value, $flags) |
|
|
|
|
304
|
|
|
{ |
305
|
|
|
if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { |
306
|
|
|
$request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT_MS, $value * 1000); |
307
|
|
|
} else { |
308
|
|
|
$request->getCurlOptions()->set(CURLOPT_CONNECTTIMEOUT, $value); |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
protected function visit_debug(RequestInterface $request, $value, $flags) |
|
|
|
|
313
|
|
|
{ |
314
|
|
|
if ($value) { |
315
|
|
|
$request->getCurlOptions()->set(CURLOPT_VERBOSE, true); |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
protected function visit_verify(RequestInterface $request, $value, $flags) |
|
|
|
|
320
|
|
|
{ |
321
|
|
|
$curl = $request->getCurlOptions(); |
322
|
|
|
if ($value === true || is_string($value)) { |
323
|
|
|
$curl[CURLOPT_SSL_VERIFYHOST] = 2; |
324
|
|
|
$curl[CURLOPT_SSL_VERIFYPEER] = true; |
325
|
|
|
if ($value !== true) { |
326
|
|
|
$curl[CURLOPT_CAINFO] = $value; |
327
|
|
|
} |
328
|
|
View Code Duplication |
} elseif ($value === false) { |
|
|
|
|
329
|
|
|
unset($curl[CURLOPT_CAINFO]); |
330
|
|
|
$curl[CURLOPT_SSL_VERIFYHOST] = 0; |
331
|
|
|
$curl[CURLOPT_SSL_VERIFYPEER] = false; |
332
|
|
|
} |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
protected function visit_proxy(RequestInterface $request, $value, $flags) |
336
|
|
|
{ |
337
|
|
|
$request->getCurlOptions()->set(CURLOPT_PROXY, $value, $flags); |
|
|
|
|
338
|
|
|
} |
339
|
|
|
|
340
|
|
View Code Duplication |
protected function visit_cert(RequestInterface $request, $value, $flags) |
|
|
|
|
341
|
|
|
{ |
342
|
|
|
if (is_array($value)) { |
343
|
|
|
$request->getCurlOptions()->set(CURLOPT_SSLCERT, $value[0]); |
344
|
|
|
$request->getCurlOptions()->set(CURLOPT_SSLCERTPASSWD, $value[1]); |
345
|
|
|
} else { |
346
|
|
|
$request->getCurlOptions()->set(CURLOPT_SSLCERT, $value); |
347
|
|
|
} |
348
|
|
|
} |
349
|
|
|
|
350
|
|
View Code Duplication |
protected function visit_ssl_key(RequestInterface $request, $value, $flags) |
|
|
|
|
351
|
|
|
{ |
352
|
|
|
if (is_array($value)) { |
353
|
|
|
$request->getCurlOptions()->set(CURLOPT_SSLKEY, $value[0]); |
354
|
|
|
$request->getCurlOptions()->set(CURLOPT_SSLKEYPASSWD, $value[1]); |
355
|
|
|
} else { |
356
|
|
|
$request->getCurlOptions()->set(CURLOPT_SSLKEY, $value); |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
} |
360
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.