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