Core.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. <?php
  2. namespace GuzzleHttp\Ring;
  3. use GuzzleHttp\Stream\StreamInterface;
  4. use GuzzleHttp\Ring\Future\FutureArrayInterface;
  5. use GuzzleHttp\Ring\Future\FutureArray;
  6. /**
  7. * Provides core functionality of Ring handlers and middleware.
  8. */
  9. class Core
  10. {
  11. /**
  12. * Returns a function that calls all of the provided functions, in order,
  13. * passing the arguments provided to the composed function to each function.
  14. *
  15. * @param callable[] $functions Array of functions to proxy to.
  16. *
  17. * @return callable
  18. */
  19. public static function callArray(array $functions)
  20. {
  21. return function () use ($functions) {
  22. $args = func_get_args();
  23. foreach ($functions as $fn) {
  24. call_user_func_array($fn, $args);
  25. }
  26. };
  27. }
  28. /**
  29. * Gets an array of header line values from a message for a specific header
  30. *
  31. * This method searches through the "headers" key of a message for a header
  32. * using a case-insensitive search.
  33. *
  34. * @param array $message Request or response hash.
  35. * @param string $header Header to retrieve
  36. *
  37. * @return array
  38. */
  39. public static function headerLines($message, $header)
  40. {
  41. $result = [];
  42. if (!empty($message['headers'])) {
  43. foreach ($message['headers'] as $name => $value) {
  44. if (!strcasecmp($name, $header)) {
  45. $result = array_merge($result, $value);
  46. }
  47. }
  48. }
  49. return $result;
  50. }
  51. /**
  52. * Gets a header value from a message as a string or null
  53. *
  54. * This method searches through the "headers" key of a message for a header
  55. * using a case-insensitive search. The lines of the header are imploded
  56. * using commas into a single string return value.
  57. *
  58. * @param array $message Request or response hash.
  59. * @param string $header Header to retrieve
  60. *
  61. * @return string|null Returns the header string if found, or null if not.
  62. */
  63. public static function header($message, $header)
  64. {
  65. $match = self::headerLines($message, $header);
  66. return $match ? implode(', ', $match) : null;
  67. }
  68. /**
  69. * Returns the first header value from a message as a string or null. If
  70. * a header line contains multiple values separated by a comma, then this
  71. * function will return the first value in the list.
  72. *
  73. * @param array $message Request or response hash.
  74. * @param string $header Header to retrieve
  75. *
  76. * @return string|null Returns the value as a string if found.
  77. */
  78. public static function firstHeader($message, $header)
  79. {
  80. if (!empty($message['headers'])) {
  81. foreach ($message['headers'] as $name => $value) {
  82. if (!strcasecmp($name, $header)) {
  83. // Return the match itself if it is a single value.
  84. $pos = strpos($value[0], ',');
  85. return $pos ? substr($value[0], 0, $pos) : $value[0];
  86. }
  87. }
  88. }
  89. return null;
  90. }
  91. /**
  92. * Returns true if a message has the provided case-insensitive header.
  93. *
  94. * @param array $message Request or response hash.
  95. * @param string $header Header to check
  96. *
  97. * @return bool
  98. */
  99. public static function hasHeader($message, $header)
  100. {
  101. if (!empty($message['headers'])) {
  102. foreach ($message['headers'] as $name => $value) {
  103. if (!strcasecmp($name, $header)) {
  104. return true;
  105. }
  106. }
  107. }
  108. return false;
  109. }
  110. /**
  111. * Parses an array of header lines into an associative array of headers.
  112. *
  113. * @param array $lines Header lines array of strings in the following
  114. * format: "Name: Value"
  115. * @return array
  116. */
  117. public static function headersFromLines($lines)
  118. {
  119. $headers = [];
  120. foreach ($lines as $line) {
  121. $parts = explode(':', $line, 2);
  122. $headers[trim($parts[0])][] = isset($parts[1])
  123. ? trim($parts[1])
  124. : null;
  125. }
  126. return $headers;
  127. }
  128. /**
  129. * Removes a header from a message using a case-insensitive comparison.
  130. *
  131. * @param array $message Message that contains 'headers'
  132. * @param string $header Header to remove
  133. *
  134. * @return array
  135. */
  136. public static function removeHeader(array $message, $header)
  137. {
  138. if (isset($message['headers'])) {
  139. foreach (array_keys($message['headers']) as $key) {
  140. if (!strcasecmp($header, $key)) {
  141. unset($message['headers'][$key]);
  142. }
  143. }
  144. }
  145. return $message;
  146. }
  147. /**
  148. * Replaces any existing case insensitive headers with the given value.
  149. *
  150. * @param array $message Message that contains 'headers'
  151. * @param string $header Header to set.
  152. * @param array $value Value to set.
  153. *
  154. * @return array
  155. */
  156. public static function setHeader(array $message, $header, array $value)
  157. {
  158. $message = self::removeHeader($message, $header);
  159. $message['headers'][$header] = $value;
  160. return $message;
  161. }
  162. /**
  163. * Creates a URL string from a request.
  164. *
  165. * If the "url" key is present on the request, it is returned, otherwise
  166. * the url is built up based on the scheme, host, uri, and query_string
  167. * request values.
  168. *
  169. * @param array $request Request to get the URL from
  170. *
  171. * @return string Returns the request URL as a string.
  172. * @throws \InvalidArgumentException if no Host header is present.
  173. */
  174. public static function url(array $request)
  175. {
  176. if (isset($request['url'])) {
  177. return $request['url'];
  178. }
  179. $uri = (isset($request['scheme'])
  180. ? $request['scheme'] : 'http') . '://';
  181. if ($host = self::header($request, 'host')) {
  182. $uri .= $host;
  183. } else {
  184. throw new \InvalidArgumentException('No Host header was provided');
  185. }
  186. if (isset($request['uri'])) {
  187. $uri .= $request['uri'];
  188. }
  189. if (isset($request['query_string'])) {
  190. $uri .= '?' . $request['query_string'];
  191. }
  192. return $uri;
  193. }
  194. /**
  195. * Reads the body of a message into a string.
  196. *
  197. * @param array|FutureArrayInterface $message Array containing a "body" key
  198. *
  199. * @return null|string Returns the body as a string or null if not set.
  200. * @throws \InvalidArgumentException if a request body is invalid.
  201. */
  202. public static function body($message)
  203. {
  204. if (!isset($message['body'])) {
  205. return null;
  206. }
  207. if ($message['body'] instanceof StreamInterface) {
  208. return (string) $message['body'];
  209. }
  210. switch (gettype($message['body'])) {
  211. case 'string':
  212. return $message['body'];
  213. case 'resource':
  214. return stream_get_contents($message['body']);
  215. case 'object':
  216. if ($message['body'] instanceof \Iterator) {
  217. return implode('', iterator_to_array($message['body']));
  218. } elseif (method_exists($message['body'], '__toString')) {
  219. return (string) $message['body'];
  220. }
  221. default:
  222. throw new \InvalidArgumentException('Invalid request body: '
  223. . self::describeType($message['body']));
  224. }
  225. }
  226. /**
  227. * Rewind the body of the provided message if possible.
  228. *
  229. * @param array $message Message that contains a 'body' field.
  230. *
  231. * @return bool Returns true on success, false on failure
  232. */
  233. public static function rewindBody($message)
  234. {
  235. if ($message['body'] instanceof StreamInterface) {
  236. return $message['body']->seek(0);
  237. }
  238. if ($message['body'] instanceof \Generator) {
  239. return false;
  240. }
  241. if ($message['body'] instanceof \Iterator) {
  242. $message['body']->rewind();
  243. return true;
  244. }
  245. if (is_resource($message['body'])) {
  246. return rewind($message['body']);
  247. }
  248. return is_string($message['body'])
  249. || (is_object($message['body'])
  250. && method_exists($message['body'], '__toString'));
  251. }
  252. /**
  253. * Debug function used to describe the provided value type and class.
  254. *
  255. * @param mixed $input
  256. *
  257. * @return string Returns a string containing the type of the variable and
  258. * if a class is provided, the class name.
  259. */
  260. public static function describeType($input)
  261. {
  262. switch (gettype($input)) {
  263. case 'object':
  264. return 'object(' . get_class($input) . ')';
  265. case 'array':
  266. return 'array(' . count($input) . ')';
  267. default:
  268. ob_start();
  269. var_dump($input);
  270. // normalize float vs double
  271. return str_replace('double(', 'float(', rtrim(ob_get_clean()));
  272. }
  273. }
  274. /**
  275. * Sleep for the specified amount of time specified in the request's
  276. * ['client']['delay'] option if present.
  277. *
  278. * This function should only be used when a non-blocking sleep is not
  279. * possible.
  280. *
  281. * @param array $request Request to sleep
  282. */
  283. public static function doSleep(array $request)
  284. {
  285. if (isset($request['client']['delay'])) {
  286. usleep($request['client']['delay'] * 1000);
  287. }
  288. }
  289. /**
  290. * Returns a proxied future that modifies the dereferenced value of another
  291. * future using a promise.
  292. *
  293. * @param FutureArrayInterface $future Future to wrap with a new future
  294. * @param callable $onFulfilled Invoked when the future fulfilled
  295. * @param callable $onRejected Invoked when the future rejected
  296. * @param callable $onProgress Invoked when the future progresses
  297. *
  298. * @return FutureArray
  299. */
  300. public static function proxy(
  301. FutureArrayInterface $future,
  302. callable $onFulfilled = null,
  303. callable $onRejected = null,
  304. callable $onProgress = null
  305. ) {
  306. return new FutureArray(
  307. $future->then($onFulfilled, $onRejected, $onProgress),
  308. [$future, 'wait'],
  309. [$future, 'cancel']
  310. );
  311. }
  312. /**
  313. * Returns a debug stream based on the provided variable.
  314. *
  315. * @param mixed $value Optional value
  316. *
  317. * @return resource
  318. */
  319. public static function getDebugResource($value = null)
  320. {
  321. if (is_resource($value)) {
  322. return $value;
  323. } elseif (defined('STDOUT')) {
  324. return STDOUT;
  325. } else {
  326. return fopen('php://output', 'w');
  327. }
  328. }
  329. }