MultiRegionClient.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <?php
  2. namespace Aws;
  3. use Aws\Endpoint\PartitionEndpointProvider;
  4. use Aws\Endpoint\PartitionInterface;
  5. use GuzzleHttp\Promise\FulfilledPromise;
  6. use Psr\Http\Message\RequestInterface;
  7. class MultiRegionClient implements AwsClientInterface
  8. {
  9. use AwsClientTrait;
  10. /** @var AwsClientInterface[] A pool of clients keyed by region. */
  11. private $clientPool = [];
  12. /** @var callable */
  13. private $factory;
  14. /** @var PartitionInterface */
  15. private $partition;
  16. /** @var array */
  17. private $args;
  18. /** @var array */
  19. private $config;
  20. /** @var HandlerList */
  21. private $handlerList;
  22. public static function getArguments()
  23. {
  24. $args = array_intersect_key(
  25. ClientResolver::getDefaultArguments(),
  26. ['service' => true, 'region' => true]
  27. );
  28. $args['region']['required'] = false;
  29. return $args + [
  30. 'client_factory' => [
  31. 'type' => 'config',
  32. 'valid' => ['callable'],
  33. 'doc' => 'A callable that takes an array of client'
  34. . ' configuration arguments and returns a regionalized'
  35. . ' client.',
  36. 'required' => true,
  37. 'internal' => true,
  38. 'default' => function (array $args) {
  39. $namespace = manifest($args['service'])['namespace'];
  40. $klass = "Aws\\{$namespace}\\{$namespace}Client";
  41. $region = isset($args['region']) ? $args['region'] : null;
  42. return function (array $args) use ($klass, $region) {
  43. if ($region && empty($args['region'])) {
  44. $args['region'] = $region;
  45. }
  46. return new $klass($args);
  47. };
  48. },
  49. ],
  50. 'partition' => [
  51. 'type' => 'config',
  52. 'valid' => ['string', PartitionInterface::class],
  53. 'doc' => 'AWS partition to connect to. Valid partitions'
  54. . ' include "aws," "aws-cn," and "aws-us-gov." Used to'
  55. . ' restrict the scope of the mapRegions method.',
  56. 'default' => function (array $args) {
  57. $region = isset($args['region']) ? $args['region'] : '';
  58. return PartitionEndpointProvider::defaultProvider()
  59. ->getPartition($region, $args['service']);
  60. },
  61. 'fn' => function ($value, array &$args) {
  62. if (is_string($value)) {
  63. $value = PartitionEndpointProvider::defaultProvider()
  64. ->getPartitionByName($value);
  65. }
  66. if (!$value instanceof PartitionInterface) {
  67. throw new \InvalidArgumentException('No valid partition'
  68. . ' was provided. Provide a concrete partition or'
  69. . ' the name of a partition (e.g., "aws," "aws-cn,"'
  70. . ' or "aws-us-gov").'
  71. );
  72. }
  73. $args['partition'] = $value;
  74. $args['endpoint_provider'] = $value;
  75. }
  76. ],
  77. ];
  78. }
  79. /**
  80. * The multi-region client constructor accepts the following options:
  81. *
  82. * - client_factory: (callable) An optional callable that takes an array of
  83. * client configuration arguments and returns a regionalized client.
  84. * - partition: (Aws\Endpoint\Partition|string) AWS partition to connect to.
  85. * Valid partitions include "aws," "aws-cn," and "aws-us-gov." Used to
  86. * restrict the scope of the mapRegions method.
  87. * - region: (string) Region to connect to when no override is provided.
  88. * Used to create the default client factory and determine the appropriate
  89. * AWS partition when present.
  90. *
  91. * @param array $args Client configuration arguments.
  92. */
  93. public function __construct(array $args = [])
  94. {
  95. if (!isset($args['service'])) {
  96. $args['service'] = $this->parseClass();
  97. }
  98. $this->handlerList = new HandlerList(function (
  99. CommandInterface $command
  100. ) {
  101. list($region, $args) = $this->getRegionFromArgs($command->toArray());
  102. $command = $this->getClientFromPool($region)
  103. ->getCommand($command->getName(), $args);
  104. return $this->executeAsync($command);
  105. });
  106. $argDefinitions = static::getArguments();
  107. $resolver = new ClientResolver($argDefinitions);
  108. $args = $resolver->resolve($args, $this->handlerList);
  109. $this->config = $args['config'];
  110. $this->factory = $args['client_factory'];
  111. $this->partition = $args['partition'];
  112. $this->args = array_diff_key($args, $args['config']);
  113. }
  114. /**
  115. * Get the region to which the client is configured to send requests by
  116. * default.
  117. *
  118. * @return string
  119. */
  120. public function getRegion()
  121. {
  122. return $this->getClientFromPool()->getRegion();
  123. }
  124. /**
  125. * Create a command for an operation name.
  126. *
  127. * Special keys may be set on the command to control how it behaves,
  128. * including:
  129. *
  130. * - @http: Associative array of transfer specific options to apply to the
  131. * request that is serialized for this command. Available keys include
  132. * "proxy", "verify", "timeout", "connect_timeout", "debug", "delay", and
  133. * "headers".
  134. * - @region: The region to which the command should be sent.
  135. *
  136. * @param string $name Name of the operation to use in the command
  137. * @param array $args Arguments to pass to the command
  138. *
  139. * @return CommandInterface
  140. * @throws \InvalidArgumentException if no command can be found by name
  141. */
  142. public function getCommand($name, array $args = [])
  143. {
  144. return new Command($name, $args, clone $this->getHandlerList());
  145. }
  146. public function getConfig($option = null)
  147. {
  148. if (null === $option) {
  149. return $this->config;
  150. }
  151. if (isset($this->config[$option])) {
  152. return $this->config[$option];
  153. }
  154. return $this->getClientFromPool()->getConfig($option);
  155. }
  156. public function getCredentials()
  157. {
  158. return $this->getClientFromPool()->getCredentials();
  159. }
  160. public function getHandlerList()
  161. {
  162. return $this->handlerList;
  163. }
  164. public function getApi()
  165. {
  166. return $this->getClientFromPool()->getApi();
  167. }
  168. public function getEndpoint()
  169. {
  170. return $this->getClientFromPool()->getEndpoint();
  171. }
  172. /**
  173. * @param string $region Omit this argument or pass in an empty string to
  174. * allow the configured client factory to apply the
  175. * region.
  176. *
  177. * @return AwsClientInterface
  178. */
  179. protected function getClientFromPool($region = '')
  180. {
  181. if (empty($this->clientPool[$region])) {
  182. $factory = $this->factory;
  183. $this->clientPool[$region] = $factory(
  184. array_replace($this->args, array_filter(['region' => $region]))
  185. );
  186. }
  187. return $this->clientPool[$region];
  188. }
  189. /**
  190. * Parse the class name and return the "service" name of the client.
  191. *
  192. * @return string
  193. */
  194. private function parseClass()
  195. {
  196. $klass = get_class($this);
  197. if ($klass === __CLASS__) {
  198. return '';
  199. }
  200. return strtolower(substr($klass, strrpos($klass, '\\') + 1, -17));
  201. }
  202. private function getRegionFromArgs(array $args)
  203. {
  204. $region = isset($args['@region'])
  205. ? $args['@region']
  206. : $this->getRegion();
  207. unset($args['@region']);
  208. return [$region, $args];
  209. }
  210. }