|
1 <?php |
|
2 /** |
|
3 * Session handler for persistent requests and default parameters |
|
4 * |
|
5 * @package Requests\SessionHandler |
|
6 */ |
|
7 |
|
8 namespace WpOrg\Requests; |
|
9 |
|
10 use WpOrg\Requests\Cookie\Jar; |
|
11 use WpOrg\Requests\Exception\InvalidArgument; |
|
12 use WpOrg\Requests\Iri; |
|
13 use WpOrg\Requests\Requests; |
|
14 use WpOrg\Requests\Utility\InputValidator; |
|
15 |
|
16 /** |
|
17 * Session handler for persistent requests and default parameters |
|
18 * |
|
19 * Allows various options to be set as default values, and merges both the |
|
20 * options and URL properties together. A base URL can be set for all requests, |
|
21 * with all subrequests resolved from this. Base options can be set (including |
|
22 * a shared cookie jar), then overridden for individual requests. |
|
23 * |
|
24 * @package Requests\SessionHandler |
|
25 */ |
|
26 class Session { |
|
27 /** |
|
28 * Base URL for requests |
|
29 * |
|
30 * URLs will be made absolute using this as the base |
|
31 * |
|
32 * @var string|null |
|
33 */ |
|
34 public $url = null; |
|
35 |
|
36 /** |
|
37 * Base headers for requests |
|
38 * |
|
39 * @var array |
|
40 */ |
|
41 public $headers = []; |
|
42 |
|
43 /** |
|
44 * Base data for requests |
|
45 * |
|
46 * If both the base data and the per-request data are arrays, the data will |
|
47 * be merged before sending the request. |
|
48 * |
|
49 * @var array |
|
50 */ |
|
51 public $data = []; |
|
52 |
|
53 /** |
|
54 * Base options for requests |
|
55 * |
|
56 * The base options are merged with the per-request data for each request. |
|
57 * The only default option is a shared cookie jar between requests. |
|
58 * |
|
59 * Values here can also be set directly via properties on the Session |
|
60 * object, e.g. `$session->useragent = 'X';` |
|
61 * |
|
62 * @var array |
|
63 */ |
|
64 public $options = []; |
|
65 |
|
66 /** |
|
67 * Create a new session |
|
68 * |
|
69 * @param string|Stringable|null $url Base URL for requests |
|
70 * @param array $headers Default headers for requests |
|
71 * @param array $data Default data for requests |
|
72 * @param array $options Default options for requests |
|
73 * |
|
74 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null. |
|
75 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array. |
|
76 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array. |
|
77 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. |
|
78 */ |
|
79 public function __construct($url = null, $headers = [], $data = [], $options = []) { |
|
80 if ($url !== null && InputValidator::is_string_or_stringable($url) === false) { |
|
81 throw InvalidArgument::create(1, '$url', 'string|Stringable|null', gettype($url)); |
|
82 } |
|
83 |
|
84 if (is_array($headers) === false) { |
|
85 throw InvalidArgument::create(2, '$headers', 'array', gettype($headers)); |
|
86 } |
|
87 |
|
88 if (is_array($data) === false) { |
|
89 throw InvalidArgument::create(3, '$data', 'array', gettype($data)); |
|
90 } |
|
91 |
|
92 if (is_array($options) === false) { |
|
93 throw InvalidArgument::create(4, '$options', 'array', gettype($options)); |
|
94 } |
|
95 |
|
96 $this->url = $url; |
|
97 $this->headers = $headers; |
|
98 $this->data = $data; |
|
99 $this->options = $options; |
|
100 |
|
101 if (empty($this->options['cookies'])) { |
|
102 $this->options['cookies'] = new Jar(); |
|
103 } |
|
104 } |
|
105 |
|
106 /** |
|
107 * Get a property's value |
|
108 * |
|
109 * @param string $name Property name. |
|
110 * @return mixed|null Property value, null if none found |
|
111 */ |
|
112 public function __get($name) { |
|
113 if (isset($this->options[$name])) { |
|
114 return $this->options[$name]; |
|
115 } |
|
116 |
|
117 return null; |
|
118 } |
|
119 |
|
120 /** |
|
121 * Set a property's value |
|
122 * |
|
123 * @param string $name Property name. |
|
124 * @param mixed $value Property value |
|
125 */ |
|
126 public function __set($name, $value) { |
|
127 $this->options[$name] = $value; |
|
128 } |
|
129 |
|
130 /** |
|
131 * Remove a property's value |
|
132 * |
|
133 * @param string $name Property name. |
|
134 */ |
|
135 public function __isset($name) { |
|
136 return isset($this->options[$name]); |
|
137 } |
|
138 |
|
139 /** |
|
140 * Remove a property's value |
|
141 * |
|
142 * @param string $name Property name. |
|
143 */ |
|
144 public function __unset($name) { |
|
145 unset($this->options[$name]); |
|
146 } |
|
147 |
|
148 /**#@+ |
|
149 * @see \WpOrg\Requests\Session::request() |
|
150 * @param string $url |
|
151 * @param array $headers |
|
152 * @param array $options |
|
153 * @return \WpOrg\Requests\Response |
|
154 */ |
|
155 /** |
|
156 * Send a GET request |
|
157 */ |
|
158 public function get($url, $headers = [], $options = []) { |
|
159 return $this->request($url, $headers, null, Requests::GET, $options); |
|
160 } |
|
161 |
|
162 /** |
|
163 * Send a HEAD request |
|
164 */ |
|
165 public function head($url, $headers = [], $options = []) { |
|
166 return $this->request($url, $headers, null, Requests::HEAD, $options); |
|
167 } |
|
168 |
|
169 /** |
|
170 * Send a DELETE request |
|
171 */ |
|
172 public function delete($url, $headers = [], $options = []) { |
|
173 return $this->request($url, $headers, null, Requests::DELETE, $options); |
|
174 } |
|
175 /**#@-*/ |
|
176 |
|
177 /**#@+ |
|
178 * @see \WpOrg\Requests\Session::request() |
|
179 * @param string $url |
|
180 * @param array $headers |
|
181 * @param array $data |
|
182 * @param array $options |
|
183 * @return \WpOrg\Requests\Response |
|
184 */ |
|
185 /** |
|
186 * Send a POST request |
|
187 */ |
|
188 public function post($url, $headers = [], $data = [], $options = []) { |
|
189 return $this->request($url, $headers, $data, Requests::POST, $options); |
|
190 } |
|
191 |
|
192 /** |
|
193 * Send a PUT request |
|
194 */ |
|
195 public function put($url, $headers = [], $data = [], $options = []) { |
|
196 return $this->request($url, $headers, $data, Requests::PUT, $options); |
|
197 } |
|
198 |
|
199 /** |
|
200 * Send a PATCH request |
|
201 * |
|
202 * Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()}, |
|
203 * `$headers` is required, as the specification recommends that should send an ETag |
|
204 * |
|
205 * @link https://tools.ietf.org/html/rfc5789 |
|
206 */ |
|
207 public function patch($url, $headers, $data = [], $options = []) { |
|
208 return $this->request($url, $headers, $data, Requests::PATCH, $options); |
|
209 } |
|
210 /**#@-*/ |
|
211 |
|
212 /** |
|
213 * Main interface for HTTP requests |
|
214 * |
|
215 * This method initiates a request and sends it via a transport before |
|
216 * parsing. |
|
217 * |
|
218 * @see \WpOrg\Requests\Requests::request() |
|
219 * |
|
220 * @param string $url URL to request |
|
221 * @param array $headers Extra headers to send with the request |
|
222 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests |
|
223 * @param string $type HTTP request type (use \WpOrg\Requests\Requests constants) |
|
224 * @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()}) |
|
225 * @return \WpOrg\Requests\Response |
|
226 * |
|
227 * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`) |
|
228 */ |
|
229 public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = []) { |
|
230 $request = $this->merge_request(compact('url', 'headers', 'data', 'options')); |
|
231 |
|
232 return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); |
|
233 } |
|
234 |
|
235 /** |
|
236 * Send multiple HTTP requests simultaneously |
|
237 * |
|
238 * @see \WpOrg\Requests\Requests::request_multiple() |
|
239 * |
|
240 * @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()}) |
|
241 * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) |
|
242 * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) |
|
243 * |
|
244 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. |
|
245 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. |
|
246 */ |
|
247 public function request_multiple($requests, $options = []) { |
|
248 if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { |
|
249 throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); |
|
250 } |
|
251 |
|
252 if (is_array($options) === false) { |
|
253 throw InvalidArgument::create(2, '$options', 'array', gettype($options)); |
|
254 } |
|
255 |
|
256 foreach ($requests as $key => $request) { |
|
257 $requests[$key] = $this->merge_request($request, false); |
|
258 } |
|
259 |
|
260 $options = array_merge($this->options, $options); |
|
261 |
|
262 // Disallow forcing the type, as that's a per request setting |
|
263 unset($options['type']); |
|
264 |
|
265 return Requests::request_multiple($requests, $options); |
|
266 } |
|
267 |
|
268 public function __wakeup() { |
|
269 throw new \LogicException( __CLASS__ . ' should never be unserialized' ); |
|
270 } |
|
271 |
|
272 /** |
|
273 * Merge a request's data with the default data |
|
274 * |
|
275 * @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()}) |
|
276 * @param boolean $merge_options Should we merge options as well? |
|
277 * @return array Request data |
|
278 */ |
|
279 protected function merge_request($request, $merge_options = true) { |
|
280 if ($this->url !== null) { |
|
281 $request['url'] = Iri::absolutize($this->url, $request['url']); |
|
282 $request['url'] = $request['url']->uri; |
|
283 } |
|
284 |
|
285 if (empty($request['headers'])) { |
|
286 $request['headers'] = []; |
|
287 } |
|
288 |
|
289 $request['headers'] = array_merge($this->headers, $request['headers']); |
|
290 |
|
291 if (empty($request['data'])) { |
|
292 if (is_array($this->data)) { |
|
293 $request['data'] = $this->data; |
|
294 } |
|
295 } elseif (is_array($request['data']) && is_array($this->data)) { |
|
296 $request['data'] = array_merge($this->data, $request['data']); |
|
297 } |
|
298 |
|
299 if ($merge_options === true) { |
|
300 $request['options'] = array_merge($this->options, $request['options']); |
|
301 |
|
302 // Disallow forcing the type, as that's a per request setting |
|
303 unset($request['options']['type']); |
|
304 } |
|
305 |
|
306 return $request; |
|
307 } |
|
308 } |