ServiceClient.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <?php
  2. namespace GuzzleHttp\Command;
  3. use GuzzleHttp\ClientInterface as HttpClient;
  4. use GuzzleHttp\Command\Exception\CommandException;
  5. use GuzzleHttp\HandlerStack;
  6. use GuzzleHttp\Promise;
  7. use GuzzleHttp\Promise\PromiseInterface;
  8. use Psr\Http\Message\RequestInterface;
  9. use Psr\Http\Message\ResponseInterface;
  10. /**
  11. * The Guzzle ServiceClient serves as the foundation for creating web service
  12. * clients that interact with RPC-style APIs.
  13. */
  14. class ServiceClient implements ServiceClientInterface
  15. {
  16. /** @var HttpClient HTTP client used to send requests */
  17. private $httpClient;
  18. /** @var HandlerStack */
  19. private $handlerStack;
  20. /** @var callable */
  21. private $commandToRequestTransformer;
  22. /** @var callable */
  23. private $responseToResultTransformer;
  24. /**
  25. * Instantiates a Guzzle ServiceClient for making requests to a web service.
  26. *
  27. * @param HttpClient $httpClient A fully-configured Guzzle HTTP client that
  28. * will be used to perform the underlying HTTP requests.
  29. * @param callable $commandToRequestTransformer A callable that transforms
  30. * a Command into a Request. The function should accept a
  31. * `GuzzleHttp\Command\CommandInterface` object and return a
  32. * `Psr\Http\Message\RequestInterface` object.
  33. * @param callable $responseToResultTransformer A callable that transforms a
  34. * Response into a Result. The function should accept a
  35. * `Psr\Http\Message\ResponseInterface` object (and optionally a
  36. * `Psr\Http\Message\RequestInterface` object) and return a
  37. * `GuzzleHttp\Command\ResultInterface` object.
  38. * @param HandlerStack $commandHandlerStack A Guzzle HandlerStack, which can
  39. * be used to add command-level middleware to the service client.
  40. */
  41. public function __construct(
  42. HttpClient $httpClient,
  43. callable $commandToRequestTransformer,
  44. callable $responseToResultTransformer,
  45. HandlerStack $commandHandlerStack = null
  46. ) {
  47. $this->httpClient = $httpClient;
  48. $this->commandToRequestTransformer = $commandToRequestTransformer;
  49. $this->responseToResultTransformer = $responseToResultTransformer;
  50. $this->handlerStack = $commandHandlerStack ?: new HandlerStack();
  51. $this->handlerStack->setHandler($this->createCommandHandler());
  52. }
  53. public function getHttpClient()
  54. {
  55. return $this->httpClient;
  56. }
  57. public function getHandlerStack()
  58. {
  59. return $this->handlerStack;
  60. }
  61. public function getCommand($name, array $params = [])
  62. {
  63. return new Command($name, $params, clone $this->handlerStack);
  64. }
  65. public function execute(CommandInterface $command)
  66. {
  67. return $this->executeAsync($command)->wait();
  68. }
  69. public function executeAsync(CommandInterface $command)
  70. {
  71. $stack = $command->getHandlerStack() ?: $this->handlerStack;
  72. $handler = $stack->resolve();
  73. return $handler($command);
  74. }
  75. public function executeAll($commands, array $options = [])
  76. {
  77. // Modify provided callbacks to track results.
  78. $results = [];
  79. $options['fulfilled'] = function ($v, $k) use (&$results, $options) {
  80. if (isset($options['fulfilled'])) {
  81. $options['fulfilled']($v, $k);
  82. }
  83. $results[$k] = $v;
  84. };
  85. $options['rejected'] = function ($v, $k) use (&$results, $options) {
  86. if (isset($options['rejected'])) {
  87. $options['rejected']($v, $k);
  88. }
  89. $results[$k] = $v;
  90. };
  91. // Execute multiple commands synchronously, then sort and return the results.
  92. return $this->executeAllAsync($commands, $options)
  93. ->then(function () use (&$results) {
  94. ksort($results);
  95. return $results;
  96. })
  97. ->wait();
  98. }
  99. public function executeAllAsync($commands, array $options = [])
  100. {
  101. // Apply default concurrency.
  102. if (!isset($options['concurrency'])) {
  103. $options['concurrency'] = 25;
  104. }
  105. // Convert the iterator of commands to a generator of promises.
  106. $commands = Promise\iter_for($commands);
  107. $promises = function () use ($commands) {
  108. foreach ($commands as $key => $command) {
  109. if (!$command instanceof CommandInterface) {
  110. throw new \InvalidArgumentException('The iterator must '
  111. . 'yield instances of ' . CommandInterface::class);
  112. }
  113. yield $key => $this->executeAsync($command);
  114. }
  115. };
  116. // Execute the commands using a pool.
  117. return (new Promise\EachPromise($promises(), $options))->promise();
  118. }
  119. /**
  120. * Creates and executes a command for an operation by name.
  121. *
  122. * @param string $name Name of the command to execute.
  123. * @param array $args Arguments to pass to the getCommand method.
  124. *
  125. * @return ResultInterface|PromiseInterface
  126. * @see \GuzzleHttp\Command\ServiceClientInterface::getCommand
  127. */
  128. public function __call($name, array $args)
  129. {
  130. $args = isset($args[0]) ? $args[0] : [];
  131. if (substr($name, -5) === 'Async') {
  132. $command = $this->getCommand(substr($name, 0, -5), $args);
  133. return $this->executeAsync($command);
  134. } else {
  135. return $this->execute($this->getCommand($name, $args));
  136. }
  137. }
  138. /**
  139. * Defines the main handler for commands that uses the HTTP client.
  140. *
  141. * @return callable
  142. */
  143. private function createCommandHandler()
  144. {
  145. return function (CommandInterface $command) {
  146. return Promise\coroutine(function () use ($command) {
  147. // Prepare the HTTP options.
  148. $opts = $command['@http'] ?: [];
  149. unset($command['@http']);
  150. try {
  151. // Prepare the request from the command and send it.
  152. $request = $this->transformCommandToRequest($command);
  153. $promise = $this->httpClient->sendAsync($request, $opts);
  154. // Create a result from the response.
  155. $response = (yield $promise);
  156. yield $this->transformResponseToResult($response, $request, $command);
  157. } catch (\Exception $e) {
  158. throw CommandException::fromPrevious($command, $e);
  159. }
  160. });
  161. };
  162. }
  163. /**
  164. * Transforms a Command object into a Request object.
  165. *
  166. * @param CommandInterface $command
  167. * @return RequestInterface
  168. */
  169. private function transformCommandToRequest(CommandInterface $command)
  170. {
  171. $transform = $this->commandToRequestTransformer;
  172. return $transform($command);
  173. }
  174. /**
  175. * Transforms a Response object, also using data from the Request object,
  176. * into a Result object.
  177. *
  178. * @param ResponseInterface $response
  179. * @param RequestInterface $request
  180. * @param CommandInterface $command
  181. * @return ResultInterface
  182. */
  183. private function transformResponseToResult(
  184. ResponseInterface $response,
  185. RequestInterface $request,
  186. CommandInterface $command
  187. ) {
  188. $transform = $this->responseToResultTransformer;
  189. return $transform($response, $request, $command);
  190. }
  191. }