scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 <?php
2 namespace GuzzleHttp\Message;
3  
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;
10 use GuzzleHttp\Query;
11 use GuzzleHttp\Stream\Stream;
12 use GuzzleHttp\Subscriber\Cookie;
13 use GuzzleHttp\Subscriber\HttpError;
14 use GuzzleHttp\Subscriber\Redirect;
15 use GuzzleHttp\Url;
16  
17 /**
18 * Default HTTP request factory used to create Request and Response objects.
19 */
20 class MessageFactory implements MessageFactoryInterface
21 {
22 use ListenerAttacherTrait;
23  
24 /** @var HttpError */
25 private $errorPlugin;
26  
27 /** @var Redirect */
28 private $redirectPlugin;
29  
30 /** @var array */
31 protected static $classMethods = [];
32  
33 public function __construct()
34 {
35 $this->errorPlugin = new HttpError();
36 $this->redirectPlugin = new Redirect();
37 }
38  
39 public function createResponse(
40 $statusCode,
41 array $headers = [],
42 $body = null,
43 array $options = []
44 ) {
45 if (null !== $body) {
46 $body = Stream::factory($body);
47 }
48  
49 return new Response($statusCode, $headers, $body, $options);
50 }
51  
52 public function createRequest($method, $url, array $options = [])
53 {
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']);
59 }
60  
61 $request = new Request($method, $url, [], null,
62 isset($options['config']) ? $options['config'] : []);
63  
64 unset($options['config']);
65  
66 // Use a POST body by default
67 if ($method == 'POST' &&
68 !isset($options['body']) &&
69 !isset($options['json'])
70 ) {
71 $options['body'] = [];
72 }
73  
74 if ($options) {
75 $this->applyOptions($request, $options);
76 }
77  
78 return $request;
79 }
80  
81 /**
82 * Create a request or response object from an HTTP message string
83 *
84 * @param string $message Message to parse
85 *
86 * @return RequestInterface|ResponseInterface
87 * @throws \InvalidArgumentException if unable to parse a message
88 */
89 public function fromMessage($message)
90 {
91 static $parser;
92 if (!$parser) {
93 $parser = new MessageParser();
94 }
95  
96 // Parse a response
97 if (strtoupper(substr($message, 0, 4)) == 'HTTP') {
98 $data = $parser->parseResponse($message);
99 return $this->createResponse(
100 $data['code'],
101 $data['headers'],
102 $data['body'] === '' ? null : $data['body'],
103 $data
104 );
105 }
106  
107 // Parse a request
108 if (!($data = ($parser->parseRequest($message)))) {
109 throw new \InvalidArgumentException('Unable to parse request');
110 }
111  
112 return $this->createRequest(
113 $data['method'],
114 Url::buildUrl($data['request_url']),
115 [
116 'headers' => $data['headers'],
117 'body' => $data['body'] === '' ? null : $data['body'],
118 'config' => [
119 'protocol_version' => $data['protocol_version']
120 ]
121 ]
122 );
123 }
124  
125 /**
126 * Apply POST fields and files to a request to attempt to give an accurate
127 * representation.
128 *
129 * @param RequestInterface $request Request to update
130 * @param array $body Body to apply
131 */
132 protected function addPostData(RequestInterface $request, array $body)
133 {
134 static $fields = ['string' => true, 'array' => true, 'NULL' => true,
135 'boolean' => true, 'double' => true, 'integer' => true];
136  
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);
143 } else {
144 $post->addFile(new PostFile($key, $value));
145 }
146 }
147  
148 if ($request->getHeader('Content-Type') == 'multipart/form-data') {
149 $post->forceMultipartUpload(true);
150 }
151  
152 $request->setBody($post);
153 }
154  
155 protected function applyOptions(
156 RequestInterface $request,
157 array $options = []
158 ) {
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];
163  
164 // Take the class of the instance, not the parent
165 $selfClass = get_class($this);
166  
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));
170 }
171  
172 // Take class methods of this particular instance
173 $methods = self::$classMethods[$selfClass];
174  
175 // Iterate over each key value pair and attempt to apply a config using
176 // double dispatch.
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;
184 } else {
185 throw new \InvalidArgumentException("No method is configured "
186 . "to handle the {$key} config key");
187 }
188 }
189 }
190  
191 private function add_body(RequestInterface $request, $value)
192 {
193 if ($value !== null) {
194 if (is_array($value)) {
195 $this->addPostData($request, $value);
196 } else {
197 $request->setBody(Stream::factory($value));
198 }
199 }
200 }
201  
202 private function add_allow_redirects(RequestInterface $request, $value)
203 {
204 static $defaultRedirect = [
205 'max' => 5,
206 'strict' => false,
207 'referer' => false
208 ];
209  
210 if ($value === false) {
211 return;
212 }
213  
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');
219 } else {
220 // Merge the default settings with the provided settings
221 $value += $defaultRedirect;
222 }
223  
224 $request->getConfig()['redirect'] = $value;
225 $request->getEmitter()->attach($this->redirectPlugin);
226 }
227  
228 private function add_exceptions(RequestInterface $request, $value)
229 {
230 if ($value === true) {
231 $request->getEmitter()->attach($this->errorPlugin);
232 }
233 }
234  
235 private function add_auth(RequestInterface $request, $value)
236 {
237 if (!$value) {
238 return;
239 } elseif (is_array($value)) {
240 $authType = isset($value[2]) ? strtolower($value[2]) : 'basic';
241 } else {
242 $authType = strtolower($value);
243 }
244  
245 $request->getConfig()->set('auth', $value);
246  
247 if ($authType == 'basic') {
248 $request->setHeader(
249 'Authorization',
250 'Basic ' . base64_encode("$value[0]:$value[1]")
251 );
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]");
258 }
259 }
260  
261 private function add_query(RequestInterface $request, $value)
262 {
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])) {
273 $query[$k] = $v;
274 }
275 }
276 } else {
277 throw new \InvalidArgumentException('query value must be an array '
278 . 'or Query object');
279 }
280 }
281  
282 private function add_headers(RequestInterface $request, $value)
283 {
284 if (!is_array($value)) {
285 throw new \InvalidArgumentException('header value must be an array');
286 }
287  
288 // Do not overwrite existing headers
289 foreach ($value as $k => $v) {
290 if (!$request->hasHeader($k)) {
291 $request->setHeader($k, $v);
292 }
293 }
294 }
295  
296 private function add_cookies(RequestInterface $request, $value)
297 {
298 if ($value === true) {
299 static $cookie = null;
300 if (!$cookie) {
301 $cookie = new Cookie();
302 }
303 $request->getEmitter()->attach($cookie);
304 } elseif (is_array($value)) {
305 $request->getEmitter()->attach(
306 new Cookie(CookieJar::fromArray($value, $request->getHost()))
307 );
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');
313 }
314 }
315  
316 private function add_events(RequestInterface $request, $value)
317 {
318 if (!is_array($value)) {
319 throw new \InvalidArgumentException('events value must be an array');
320 }
321  
322 $this->attachListeners($request, $this->prepareListeners($value,
323 ['before', 'complete', 'error', 'headers']
324 ));
325 }
326  
327 private function add_subscribers(RequestInterface $request, $value)
328 {
329 if (!is_array($value)) {
330 throw new \InvalidArgumentException('subscribers must be an array');
331 }
332  
333 $emitter = $request->getEmitter();
334 foreach ($value as $subscribers) {
335 $emitter->attach($subscribers);
336 }
337 }
338  
339 private function add_json(RequestInterface $request, $value)
340 {
341 if (!$request->hasHeader('Content-Type')) {
342 $request->setHeader('Content-Type', 'application/json');
343 }
344  
345 $request->setBody(Stream::factory(json_encode($value)));
346 }
347  
348 private function add_decode_content(RequestInterface $request, $value)
349 {
350 if ($value === false) {
351 return;
352 }
353  
354 if ($value !== true) {
355 $request->setHeader('Accept-Encoding', $value);
356 }
357  
358 $request->getConfig()['decode_content'] = true;
359 }
360 }