2
namespace GuzzleHttp\Message;
4
use GuzzleHttp\Cookie\CookieJar;
5
use GuzzleHttp\Cookie\CookieJarInterface;
6
use GuzzleHttp\Event\ListenerAttacherTrait;
7
use GuzzleHttp\Post\PostBody;
8
use GuzzleHttp\Post\PostFile;
9
use GuzzleHttp\Post\PostFileInterface;
11
use GuzzleHttp\Stream\Stream;
12
use GuzzleHttp\Subscriber\Cookie;
13
use GuzzleHttp\Subscriber\HttpError;
14
use GuzzleHttp\Subscriber\Redirect;
18
* Default HTTP request factory used to create Request and Response objects.
20
class MessageFactory implements MessageFactoryInterface
22
use ListenerAttacherTrait;
28
private $redirectPlugin;
31
protected static $classMethods = [];
33
public function __construct()
35
$this->errorPlugin = new HttpError();
36
$this->redirectPlugin = new Redirect();
39
public function createResponse(
46
$body = Stream::factory($body);
49
return new Response($statusCode, $headers, $body, $options);
52
public function createRequest($method, $url, array $options = [])
54
// Handle the request protocol version option that needs to be
55
// specified in the request constructor.
56
if (isset($options['version'])) {
57
$options['config']['protocol_version'] = $options['version'];
58
unset($options['version']);
61
$request = new Request($method, $url, [], null,
62
isset($options['config']) ? $options['config'] : []);
64
unset($options['config']);
66
// Use a POST body by default
67
if ($method == 'POST' &&
68
!isset($options['body']) &&
69
!isset($options['json'])
71
$options['body'] = [];
75
$this->applyOptions($request, $options);
82
* Create a request or response object from an HTTP message string
84
* @param string $message Message to parse
86
* @return RequestInterface|ResponseInterface
87
* @throws \InvalidArgumentException if unable to parse a message
89
public function fromMessage($message)
93
$parser = new MessageParser();
97
if (strtoupper(substr($message, 0, 4)) == 'HTTP') {
98
$data = $parser->parseResponse($message);
99
return $this->createResponse(
102
$data['body'] === '' ? null : $data['body'],
108
if (!($data = ($parser->parseRequest($message)))) {
109
throw new \InvalidArgumentException('Unable to parse request');
112
return $this->createRequest(
114
Url::buildUrl($data['request_url']),
116
'headers' => $data['headers'],
117
'body' => $data['body'] === '' ? null : $data['body'],
119
'protocol_version' => $data['protocol_version']
126
* Apply POST fields and files to a request to attempt to give an accurate
129
* @param RequestInterface $request Request to update
130
* @param array $body Body to apply
132
protected function addPostData(RequestInterface $request, array $body)
134
static $fields = ['string' => true, 'array' => true, 'NULL' => true,
135
'boolean' => true, 'double' => true, 'integer' => true];
137
$post = new PostBody();
138
foreach ($body as $key => $value) {
139
if (isset($fields[gettype($value)])) {
140
$post->setField($key, $value);
141
} elseif ($value instanceof PostFileInterface) {
142
$post->addFile($value);
144
$post->addFile(new PostFile($key, $value));
148
if ($request->getHeader('Content-Type') == 'multipart/form-data') {
149
$post->forceMultipartUpload(true);
152
$request->setBody($post);
155
protected function applyOptions(
156
RequestInterface $request,
159
// Values specified in the config map are passed to request options
160
static $configMap = ['connect_timeout' => 1, 'timeout' => 1,
161
'verify' => 1, 'ssl_key' => 1, 'cert' => 1, 'proxy' => 1,
162
'debug' => 1, 'save_to' => 1, 'stream' => 1, 'expect' => 1];
164
// Take the class of the instance, not the parent
165
$selfClass = get_class($this);
167
// Check if we already took it's class methods and had them saved
168
if (!isset(self::$classMethods[$selfClass])) {
169
self::$classMethods[$selfClass] = array_flip(get_class_methods($this));
172
// Take class methods of this particular instance
173
$methods = self::$classMethods[$selfClass];
175
// Iterate over each key value pair and attempt to apply a config using
177
$config = $request->getConfig();
178
foreach ($options as $key => $value) {
179
$method = "add_{$key}";
180
if (isset($methods[$method])) {
181
$this->{$method}($request, $value);
182
} elseif (isset($configMap[$key])) {
183
$config[$key] = $value;
185
throw new \InvalidArgumentException("No method is configured "
186
. "to handle the {$key} config key");
191
private function add_body(RequestInterface $request, $value)
193
if ($value !== null) {
194
if (is_array($value)) {
195
$this->addPostData($request, $value);
197
$request->setBody(Stream::factory($value));
202
private function add_allow_redirects(RequestInterface $request, $value)
204
static $defaultRedirect = [
210
if ($value === false) {
214
if ($value === true) {
215
$value = $defaultRedirect;
216
} elseif (!isset($value['max'])) {
217
throw new \InvalidArgumentException('allow_redirects must be '
218
. 'true, false, or an array that contains the \'max\' key');
220
// Merge the default settings with the provided settings
221
$value += $defaultRedirect;
224
$request->getConfig()['redirect'] = $value;
225
$request->getEmitter()->attach($this->redirectPlugin);
228
private function add_exceptions(RequestInterface $request, $value)
230
if ($value === true) {
231
$request->getEmitter()->attach($this->errorPlugin);
235
private function add_auth(RequestInterface $request, $value)
239
} elseif (is_array($value)) {
240
$authType = isset($value[2]) ? strtolower($value[2]) : 'basic';
242
$authType = strtolower($value);
245
$request->getConfig()->set('auth', $value);
247
if ($authType == 'basic') {
250
'Basic ' . base64_encode("$value[0]:$value[1]")
252
} elseif ($authType == 'digest') {
253
// Currently only implemented by the cURL adapter.
254
// @todo: Need an event listener solution that does not rely on cURL
255
$config = $request->getConfig();
256
$config->setPath('curl/' . CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
257
$config->setPath('curl/' . CURLOPT_USERPWD, "$value[0]:$value[1]");
261
private function add_query(RequestInterface $request, $value)
263
if ($value instanceof Query) {
264
$original = $request->getQuery();
265
// Do not overwrite existing query string variables by overwriting
266
// the object with the query string data passed in the URL
267
$request->setQuery($value->overwriteWith($original->toArray()));
268
} elseif (is_array($value)) {
269
// Do not overwrite existing query string variables
270
$query = $request->getQuery();
271
foreach ($value as $k => $v) {
272
if (!isset($query[$k])) {
277
throw new \InvalidArgumentException('query value must be an array '
278
. 'or Query object');
282
private function add_headers(RequestInterface $request, $value)
284
if (!is_array($value)) {
285
throw new \InvalidArgumentException('header value must be an array');
288
// Do not overwrite existing headers
289
foreach ($value as $k => $v) {
290
if (!$request->hasHeader($k)) {
291
$request->setHeader($k, $v);
296
private function add_cookies(RequestInterface $request, $value)
298
if ($value === true) {
299
static $cookie = null;
301
$cookie = new Cookie();
303
$request->getEmitter()->attach($cookie);
304
} elseif (is_array($value)) {
305
$request->getEmitter()->attach(
306
new Cookie(CookieJar::fromArray($value, $request->getHost()))
308
} elseif ($value instanceof CookieJarInterface) {
309
$request->getEmitter()->attach(new Cookie($value));
310
} elseif ($value !== false) {
311
throw new \InvalidArgumentException('cookies must be an array, '
312
. 'true, or a CookieJarInterface object');
316
private function add_events(RequestInterface $request, $value)
318
if (!is_array($value)) {
319
throw new \InvalidArgumentException('events value must be an array');
322
$this->attachListeners($request, $this->prepareListeners($value,
323
['before', 'complete', 'error', 'headers']
327
private function add_subscribers(RequestInterface $request, $value)
329
if (!is_array($value)) {
330
throw new \InvalidArgumentException('subscribers must be an array');
333
$emitter = $request->getEmitter();
334
foreach ($value as $subscribers) {
335
$emitter->attach($subscribers);
339
private function add_json(RequestInterface $request, $value)
341
if (!$request->hasHeader('Content-Type')) {
342
$request->setHeader('Content-Type', 'application/json');
345
$request->setBody(Stream::factory(json_encode($value)));
348
private function add_decode_content(RequestInterface $request, $value)
350
if ($value === false) {
354
if ($value !== true) {
355
$request->setHeader('Accept-Encoding', $value);
358
$request->getConfig()['decode_content'] = true;