ServiceClient.php 7.3 KB

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