Session.php 8.8 KB

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