<?php
namespace Aws;

use Aws\Api\ApiProvider;
use Aws\Api\DocModel;
use Aws\Api\Service;
use Aws\EndpointDiscovery\EndpointDiscoveryMiddleware;
use Aws\EndpointV2\EndpointProviderV2;
use Aws\Exception\AwsException;
use Aws\Signature\SignatureProvider;
use GuzzleHttp\Psr7\Uri;

/**
 * Default AWS client implementation
 */
class AwsClient implements AwsClientInterface
{
    use AwsClientTrait;

    /** @var array */
    private $aliases;

    /** @var array */
    private $config;

    /** @var string */
    private $region;

    /** @var string */
    private $endpoint;

    /** @var Service */
    private $api;

    /** @var callable */
    private $signatureProvider;

    /** @var callable */
    private $credentialProvider;

    /** @var callable */
    private $tokenProvider;

    /** @var HandlerList */
    private $handlerList;

    /** @var array*/
    private $defaultRequestOptions;

    /** @var array*/
    private $clientContextParams = [];

    /** @var array*/
    protected $clientBuiltIns = [];

    /** @var  EndpointProviderV2 | callable */
    protected $endpointProvider;

    /** @var callable */
    protected $serializer;

    /**
     * Get an array of client constructor arguments used by the client.
     *
     * @return array
     */
    public static function getArguments()
    {
        return ClientResolver::getDefaultArguments();
    }

    /**
     * The client constructor accepts the following options:
     *
     * - api_provider: (callable) An optional PHP callable that accepts a
     *   type, service, and version argument, and returns an array of
     *   corresponding configuration data. The type value can be one of api,
     *   waiter, or paginator.
     * - credentials:
     *   (Aws\Credentials\CredentialsInterface|array|bool|callable) Specifies
     *   the credentials used to sign requests. Provide an
     *   Aws\Credentials\CredentialsInterface object, an associative array of
     *   "key", "secret", and an optional "token" key, `false` to use null
     *   credentials, or a callable credentials provider used to create
     *   credentials or return null. See Aws\Credentials\CredentialProvider for
     *   a list of built-in credentials providers. If no credentials are
     *   provided, the SDK will attempt to load them from the environment.
     * - token:
     *   (Aws\Token\TokenInterface|array|bool|callable) Specifies
     *   the token used to authorize requests. Provide an
     *   Aws\Token\TokenInterface object, an associative array of
     *   "token" and an optional "expires" key, `false` to use no
     *   token, or a callable token provider used to create a
     *   token or return null. See Aws\Token\TokenProvider for
     *   a list of built-in token providers. If no token is
     *   provided, the SDK will attempt to load one from the environment.
     * - csm:
     *   (Aws\ClientSideMonitoring\ConfigurationInterface|array|callable) Specifies
     *   the credentials used to sign requests. Provide an
     *   Aws\ClientSideMonitoring\ConfigurationInterface object, a callable
     *   configuration provider used to create client-side monitoring configuration,
     *   `false` to disable csm, or an associative array with the following keys:
     *   enabled: (bool) Set to true to enable client-side monitoring, defaults
     *   to false; host: (string) the host location to send monitoring events to,
     *   defaults to 127.0.0.1; port: (int) The port used for the host connection,
     *   defaults to 31000; client_id: (string) An identifier for this project
     * - debug: (bool|array) Set to true to display debug information when
     *   sending requests. Alternatively, you can provide an associative array
     *   with the following keys: logfn: (callable) Function that is invoked
     *   with log messages; stream_size: (int) When the size of a stream is
     *   greater than this number, the stream data will not be logged (set to
     *   "0" to not log any stream data); scrub_auth: (bool) Set to false to
     *   disable the scrubbing of auth data from the logged messages; http:
     *   (bool) Set to false to disable the "debug" feature of lower level HTTP
     *   adapters (e.g., verbose curl output).
     * - stats: (bool|array) Set to true to gather transfer statistics on
     *   requests sent. Alternatively, you can provide an associative array with
     *   the following keys: retries: (bool) Set to false to disable reporting
     *   on retries attempted; http: (bool) Set to true to enable collecting
     *   statistics from lower level HTTP adapters (e.g., values returned in
     *   GuzzleHttp\TransferStats). HTTP handlers must support an
     *   `http_stats_receiver` option for this to have an effect; timer: (bool)
     *   Set to true to enable a command timer that reports the total wall clock
     *   time spent on an operation in seconds.
     * - disable_host_prefix_injection: (bool) Set to true to disable host prefix
     *   injection logic for services that use it. This disables the entire
     *   prefix injection, including the portions supplied by user-defined
     *   parameters. Setting this flag will have no effect on services that do
     *   not use host prefix injection.
     * - endpoint: (string) The full URI of the webservice. This is only
     *   required when connecting to a custom endpoint (e.g., a local version
     *   of S3).
     * - endpoint_discovery: (Aws\EndpointDiscovery\ConfigurationInterface,
     *   Aws\CacheInterface, array, callable) Settings for endpoint discovery.
     *   Provide an instance of Aws\EndpointDiscovery\ConfigurationInterface,
     *   an instance Aws\CacheInterface, a callable that provides a promise for
     *   a Configuration object, or an associative array with the following
     *   keys: enabled: (bool) Set to true to enable endpoint discovery, false
     *   to explicitly disable it, defaults to false; cache_limit: (int) The
     *   maximum number of keys in the endpoints cache, defaults to 1000.
     * - endpoint_provider: (callable) An optional PHP callable that
     *   accepts a hash of options including a "service" and "region" key and
     *   returns NULL or a hash of endpoint data, of which the "endpoint" key
     *   is required. See Aws\Endpoint\EndpointProvider for a list of built-in
     *   providers.
     * - handler: (callable) A handler that accepts a command object,
     *   request object and returns a promise that is fulfilled with an
     *   Aws\ResultInterface object or rejected with an
     *   Aws\Exception\AwsException. A handler does not accept a next handler
     *   as it is terminal and expected to fulfill a command. If no handler is
     *   provided, a default Guzzle handler will be utilized.
     * - http: (array, default=array(0)) Set to an array of SDK request
     *   options to apply to each request (e.g., proxy, verify, etc.).
     * - http_handler: (callable) An HTTP handler is a function that
     *   accepts a PSR-7 request object and returns a promise that is fulfilled
     *   with a PSR-7 response object or rejected with an array of exception
     *   data. NOTE: This option supersedes any provided "handler" option.
     * - idempotency_auto_fill: (bool|callable) Set to false to disable SDK to
     *   populate parameters that enabled 'idempotencyToken' trait with a random
     *   UUID v4 value on your behalf. Using default value 'true' still allows
     *   parameter value to be overwritten when provided. Note: auto-fill only
     *   works when cryptographically secure random bytes generator functions
     *   (random_bytes, openssl_random_pseudo_bytes or mcrypt_create_iv) can be
     *   found. You may also provide a callable source of random bytes.
     * - profile: (string) Allows you to specify which profile to use when
     *   credentials are created from the AWS credentials file in your HOME
     *   directory. This setting overrides the AWS_PROFILE environment
     *   variable. Note: Specifying "profile" will cause the "credentials" key
     *   to be ignored.
     * - region: (string, required) Region to connect to. See
     *   http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of
     *   available regions.
     * - retries: (int, Aws\Retry\ConfigurationInterface, Aws\CacheInterface,
     *   array, callable) Configures the retry mode and maximum number of
     *   allowed retries for a client (pass 0 to disable retries). Provide an
     *   integer for 'legacy' mode with the specified number of retries.
     *   Otherwise provide an instance of Aws\Retry\ConfigurationInterface, an
     *   instance of  Aws\CacheInterface, a callable function, or an array with
     *   the following keys: mode: (string) Set to 'legacy', 'standard' (uses
     *   retry quota management), or 'adapative' (an experimental mode that adds
     *   client-side rate limiting to standard mode); max_attempts (int) The
     *   maximum number of attempts for a given request.
     * - scheme: (string, default=string(5) "https") URI scheme to use when
     *   connecting connect. The SDK will utilize "https" endpoints (i.e.,
     *   utilize SSL/TLS connections) by default. You can attempt to connect to
     *   a service over an unencrypted "http" endpoint by setting ``scheme`` to
     *   "http".
     * - signature_provider: (callable) A callable that accepts a signature
     *   version name (e.g., "v4"), a service name, and region, and
     *   returns a SignatureInterface object or null. This provider is used to
     *   create signers utilized by the client. See
     *   Aws\Signature\SignatureProvider for a list of built-in providers
     * - signature_version: (string) A string representing a custom
     *   signature version to use with a service (e.g., v4). Note that
     *   per/operation signature version MAY override this requested signature
     *   version.
     * - use_aws_shared_config_files: (bool, default=bool(true)) Set to false to
     *   disable checking for shared config file in '~/.aws/config' and
     *   '~/.aws/credentials'.  This will override the AWS_CONFIG_FILE
     *   environment variable.
     * - validate: (bool, default=bool(true)) Set to false to disable
     *   client-side parameter validation.
     * - version: (string, required) The version of the webservice to
     *   utilize (e.g., 2006-03-01).
     *
     * @param array $args Client configuration arguments.
     *
     * @throws \InvalidArgumentException if any required options are missing or
     *                                   the service is not supported.
     */
    public function __construct(array $args)
    {
        list($service, $exceptionClass) = $this->parseClass();
        if (!isset($args['service'])) {
            $args['service'] = manifest($service)['endpoint'];
        }
        if (!isset($args['exception_class'])) {
            $args['exception_class'] = $exceptionClass;
        }
        $this->handlerList = new HandlerList();
        $resolver = new ClientResolver(static::getArguments());
        $config = $resolver->resolve($args, $this->handlerList);
        $this->api = $config['api'];
        $this->signatureProvider = $config['signature_provider'];
        $this->endpoint = new Uri($config['endpoint']);
        $this->credentialProvider = $config['credentials'];
        $this->tokenProvider = $config['token'];
        $this->region = isset($config['region']) ? $config['region'] : null;
        $this->config = $config['config'];
        $this->setClientBuiltIns($args);
        $this->clientContextParams = $this->setClientContextParams($args);
        $this->defaultRequestOptions = $config['http'];
        $this->endpointProvider = $config['endpoint_provider'];
        $this->serializer = $config['serializer'];
        $this->addSignatureMiddleware();
        $this->addInvocationId();
        $this->addEndpointParameterMiddleware($args);
        $this->addEndpointDiscoveryMiddleware($config, $args);
        $this->addRequestCompressionMiddleware($config);
        $this->loadAliases();
        $this->addStreamRequestPayload();
        $this->addRecursionDetection();
        $this->addRequestBuilder();

        if (!$config['suppress_php_deprecation_warning']) {
            $this->emitDeprecationWarning();
        }

        if (isset($args['with_resolved'])) {
            $args['with_resolved']($config);
        }
    }

    public function getHandlerList()
    {
        return $this->handlerList;
    }

    public function getConfig($option = null)
    {
        return $option === null
            ? $this->config
            : (isset($this->config[$option])
                ? $this->config[$option]
                : null);
    }

    public function getCredentials()
    {
        $fn = $this->credentialProvider;
        return $fn();
    }

    public function getEndpoint()
    {
        return $this->endpoint;
    }

    public function getRegion()
    {
        return $this->region;
    }

    public function getApi()
    {
        return $this->api;
    }

    public function getCommand($name, array $args = [])
    {
        // Fail fast if the command cannot be found in the description.
        if (!isset($this->getApi()['operations'][$name])) {
            $name = ucfirst($name);
            if (!isset($this->getApi()['operations'][$name])) {
                throw new \InvalidArgumentException("Operation not found: $name");
            }
        }

        if (!isset($args['@http'])) {
            $args['@http'] = $this->defaultRequestOptions;
        } else {
            $args['@http'] += $this->defaultRequestOptions;
        }

        return new Command($name, $args, clone $this->getHandlerList());
    }

    public function getEndpointProvider()
    {
        return $this->endpointProvider;
    }

    /**
     * Provides the set of service context parameter
     * key-value pairs used for endpoint resolution.
     *
     * @return array
     */
    public function getClientContextParams()
    {
        return $this->clientContextParams;
    }

    /**
     * Provides the set of built-in keys and values
     * used for endpoint resolution
     *
     * @return array
     */
    public function getClientBuiltIns()
    {
        return $this->clientBuiltIns;
    }

    public function __sleep()
    {
        throw new \RuntimeException('Instances of ' . static::class
            . ' cannot be serialized');
    }

    /**
     * Get the signature_provider function of the client.
     *
     * @return callable
     */
    final public function getSignatureProvider()
    {
        return $this->signatureProvider;
    }

    /**
     * Parse the class name and setup the custom exception class of the client
     * and return the "service" name of the client and "exception_class".
     *
     * @return array
     */
    private function parseClass()
    {
        $klass = get_class($this);

        if ($klass === __CLASS__) {
            return ['', AwsException::class];
        }

        $service = substr($klass, strrpos($klass, '\\') + 1, -6);

        return [
            strtolower($service),
            "Aws\\{$service}\\Exception\\{$service}Exception"
        ];
    }

    private function addEndpointParameterMiddleware($args)
    {
        if (empty($args['disable_host_prefix_injection'])) {
            $list = $this->getHandlerList();
            $list->appendBuild(
                EndpointParameterMiddleware::wrap(
                    $this->api
                ),
                'endpoint_parameter'
            );
        }
    }

    private function addEndpointDiscoveryMiddleware($config, $args)
    {
        $list = $this->getHandlerList();

        if (!isset($args['endpoint'])) {
            $list->appendBuild(
                EndpointDiscoveryMiddleware::wrap(
                    $this,
                    $args,
                    $config['endpoint_discovery']
                ),
                'EndpointDiscoveryMiddleware'
            );
        }
    }

    private function addSignatureMiddleware()
    {
        $api = $this->getApi();
        $provider = $this->signatureProvider;
        $version = $this->config['signature_version'];
        $name = $this->config['signing_name'];
        $region = $this->config['signing_region'];

        $resolver = static function (
            CommandInterface $c
        ) use ($api, $provider, $name, $region, $version) {
            if (!empty($c['@context']['signing_region'])) {
                $region = $c['@context']['signing_region'];
            }
            if (!empty($c['@context']['signing_service'])) {
                $name = $c['@context']['signing_service'];
            }

            $authType = $api->getOperation($c->getName())['authtype'];
            switch ($authType){
                case 'none':
                    $version = 'anonymous';
                    break;
                case 'v4-unsigned-body':
                    $version = 'v4-unsigned-body';
                    break;
                case 'bearer':
                    $version = 'bearer';
                    break;
            }
            if (isset($c['@context']['signature_version'])) {
                if ($c['@context']['signature_version'] == 'v4a') {
                    $version = 'v4a';
                }
            }
            if (!empty($endpointAuthSchemes = $c->getAuthSchemes())) {
                $version = $endpointAuthSchemes['version'];
                $name = isset($endpointAuthSchemes['name']) ?
                    $endpointAuthSchemes['name'] : $name;
                $region = isset($endpointAuthSchemes['region']) ?
                    $endpointAuthSchemes['region'] : $region;
            }
            return SignatureProvider::resolve($provider, $version, $name, $region);
        };
        $this->handlerList->appendSign(
            Middleware::signer($this->credentialProvider, $resolver, $this->tokenProvider),
            'signer'
        );
    }

    private function addRequestCompressionMiddleware($config)
    {
        if (empty($config['disable_request_compression'])) {
            $list = $this->getHandlerList();
            $list->appendBuild(
                RequestCompressionMiddleware::wrap($config),
                'request-compression'
            );
        }
    }

    private function addInvocationId()
    {
        // Add invocation id to each request
        $this->handlerList->prependSign(Middleware::invocationId(), 'invocation-id');
    }

    private function loadAliases($file = null)
    {
        if (!isset($this->aliases)) {
            if (is_null($file)) {
                $file = __DIR__ . '/data/aliases.json';
            }
            $aliases = \Aws\load_compiled_json($file);
            $serviceId = $this->api->getServiceId();
            $version = $this->getApi()->getApiVersion();
            if (!empty($aliases['operations'][$serviceId][$version])) {
                $this->aliases = array_flip($aliases['operations'][$serviceId][$version]);
            }
        }
    }

    private function addStreamRequestPayload()
    {
        $streamRequestPayloadMiddleware = StreamRequestPayloadMiddleware::wrap(
            $this->api
        );

        $this->handlerList->prependSign(
            $streamRequestPayloadMiddleware,
            'StreamRequestPayloadMiddleware'
        );
    }

    private function addRecursionDetection()
    {
        // Add recursion detection header to requests
        // originating in supported Lambda runtimes
        $this->handlerList->appendBuild(
            Middleware::recursionDetection(), 'recursion-detection'
        );
    }

    /**
     * Adds the `builder` middleware such that a client's endpoint
     * provider and endpoint resolution arguments can be passed.
     */
    private function addRequestBuilder()
    {
        $handlerList = $this->getHandlerList();
        $serializer = $this->serializer;
        $endpointProvider = $this->endpointProvider;
        $endpointArgs = $this->getEndpointProviderArgs();

        $handlerList->prependBuild(
            Middleware::requestBuilder(
                $serializer,
                $endpointProvider,
                $endpointArgs
            ),
            'builderV2'
        );
    }

    /**
     * Retrieves client context param definition from service model,
     * creates mapping of client context param names with client-provided
     * values.
     *
     * @return array
     */
    private function setClientContextParams($args)
    {
        $api = $this->getApi();
        $resolvedParams = [];
        if (!empty($paramDefinitions = $api->getClientContextParams())) {
            foreach($paramDefinitions as $paramName => $paramValue) {
                if (isset($args[$paramName])) {
                   $result[$paramName] = $args[$paramName];
               }
            }
        }
        return $resolvedParams;
    }

    /**
     * Retrieves and sets default values used for endpoint resolution.
     */
    private function setClientBuiltIns($args)
    {
        $builtIns = [];
        $config = $this->getConfig();
        $service = $args['service'];

        $builtIns['SDK::Endpoint'] = isset($args['endpoint']) ? $args['endpoint'] : null;
        $builtIns['AWS::Region'] = $this->getRegion();
        $builtIns['AWS::UseFIPS'] = $config['use_fips_endpoint']->isUseFipsEndpoint();
        $builtIns['AWS::UseDualStack'] = $config['use_dual_stack_endpoint']->isUseDualstackEndpoint();
        if ($service === 's3' || $service === 's3control'){
            $builtIns['AWS::S3::UseArnRegion'] = $config['use_arn_region']->isUseArnRegion();
        }
        if ($service === 's3') {
            $builtIns['AWS::S3::UseArnRegion'] = $config['use_arn_region']->isUseArnRegion();
            $builtIns['AWS::S3::Accelerate'] = $config['use_accelerate_endpoint'];
            $builtIns['AWS::S3::ForcePathStyle'] = $config['use_path_style_endpoint'];
            $builtIns['AWS::S3::DisableMultiRegionAccessPoints'] = $config['disable_multiregion_access_points'];
        }
        $this->clientBuiltIns += $builtIns;
    }

    /**
     * Retrieves arguments to be used in endpoint resolution.
     *
     * @return array
     */
    public function getEndpointProviderArgs()
    {
        return $this->normalizeEndpointProviderArgs();
    }

    /**
     * Combines built-in and client context parameter values in
     * order of specificity.  Client context parameter values supersede
     * built-in values.
     *
     * @return array
     */
    private function normalizeEndpointProviderArgs()
    {
        $normalizedBuiltIns = [];

        foreach($this->clientBuiltIns as $name => $value) {
            $normalizedName = explode('::', $name);
            $normalizedName = $normalizedName[count($normalizedName) - 1];
            $normalizedBuiltIns[$normalizedName] = $value;
        }

        return array_merge($normalizedBuiltIns, $this->getClientContextParams());
    }

    protected function isUseEndpointV2()
    {
        return $this->endpointProvider instanceof EndpointProviderV2;
    }

    public static function emitDeprecationWarning() {
        $phpVersion = PHP_VERSION_ID;
        if ($phpVersion <  70205) {
            $phpVersionString = phpversion();
            @trigger_error(
                "This installation of the SDK is using PHP version"
                .  " {$phpVersionString}, which will be deprecated on August"
                .  " 15th, 2023.  Please upgrade your PHP version to a minimum of"
                .  " 7.2.5 before then to continue receiving updates to the AWS"
                .  " SDK for PHP.  To disable this warning, set"
                .  " suppress_php_deprecation_warning to true on the client constructor"
                .  " or set the environment variable AWS_SUPPRESS_PHP_DEPRECATION_WARNING"
                .  " to true.",
                E_USER_DEPRECATED
            );
        }
    }

    /**
     * Returns a service model and doc model with any necessary changes
     * applied.
     *
     * @param array $api  Array of service data being documented.
     * @param array $docs Array of doc model data.
     *
     * @return array Tuple containing a [Service, DocModel]
     *
     * @internal This should only used to document the service API.
     * @codeCoverageIgnore
     */
    public static function applyDocFilters(array $api, array $docs)
    {
        $aliases = \Aws\load_compiled_json(__DIR__ . '/data/aliases.json');
        $serviceId = $api['metadata']['serviceId'];
        $version = $api['metadata']['apiVersion'];

        // Replace names for any operations with SDK aliases
        if (!empty($aliases['operations'][$serviceId][$version])) {
            foreach ($aliases['operations'][$serviceId][$version] as $op => $alias) {
                $api['operations'][$alias] = $api['operations'][$op];
                $docs['operations'][$alias] = $docs['operations'][$op];
                unset($api['operations'][$op], $docs['operations'][$op]);
            }
        }
        ksort($api['operations']);

        return [
            new Service($api, ApiProvider::defaultProvider()),
            new DocModel($docs)
        ];
    }

    /**
     * @deprecated
     * @return static
     */
    public static function factory(array $config = [])
    {
        return new static($config);
    }
}


<?php
namespace Aws;

use Psr\Http\Message\UriInterface;
use GuzzleHttp\Promise\PromiseInterface;

/**
 * Represents an AWS client.
 */
interface AwsClientInterface
{
    /**
     * Creates and executes a command for an operation by name.
     *
     * Suffixing an operation name with "Async" will return a
     * promise that can be used to execute commands asynchronously.
     *
     * @param string $name      Name of the command to execute.
     * @param array  $arguments Arguments to pass to the getCommand method.
     *
     * @return ResultInterface
     * @throws \Exception
     */
    public function __call($name, array $arguments);

    /**
     * Create a command for an operation name.
     *
     * Special keys may be set on the command to control how it behaves,
     * including:
     *
     * - @http: Associative array of transfer specific options to apply to the
     *   request that is serialized for this command. Available keys include
     *   "proxy", "verify", "timeout", "connect_timeout", "debug", "delay", and
     *   "headers".
     *
     * @param string $name Name of the operation to use in the command
     * @param array  $args Arguments to pass to the command
     *
     * @return CommandInterface
     * @throws \InvalidArgumentException if no command can be found by name
     */
    public function getCommand($name, array $args = []);

    /**
     * Execute a single command.
     *
     * @param CommandInterface $command Command to execute
     *
     * @return ResultInterface
     * @throws \Exception
     */
    public function execute(CommandInterface $command);

    /**
     * Execute a command asynchronously.
     *
     * @param CommandInterface $command Command to execute
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function executeAsync(CommandInterface $command);

    /**
     * Returns a promise that is fulfilled with an
     * {@see \Aws\Credentials\CredentialsInterface} object.
     *
     * If you need the credentials synchronously, then call the wait() method
     * on the returned promise.
     *
     * @return PromiseInterface
     */
    public function getCredentials();

    /**
     * Get the region to which the client is configured to send requests.
     *
     * @return string
     */
    public function getRegion();

    /**
     * Gets the default endpoint, or base URL, used by the client.
     *
     * @return UriInterface
     */
    public function getEndpoint();

    /**
     * Get the service description associated with the client.
     *
     * @return \Aws\Api\Service
     */
    public function getApi();

    /**
     * Get a client configuration value.
     *
     * @param string|null $option The option to retrieve. Pass null to retrieve
     *                            all options.
     * @return mixed|null
     */
    public function getConfig($option = null);

    /**
     * Get the handler list used to transfer commands.
     *
     * This list can be modified to add middleware or to change the underlying
     * handler used to send HTTP requests.
     *
     * @return HandlerList
     */
    public function getHandlerList();

    /**
     * Get a resource iterator for the specified operation.
     *
     * @param string $name Name of the iterator to retrieve.
     * @param array  $args Command arguments to use with each command.
     *
     * @return \Iterator
     * @throws \UnexpectedValueException if the iterator config is invalid.
     */
    public function getIterator($name, array $args = []);

    /**
     * Get a result paginator for the specified operation.
     *
     * @param string $name   Name of the operation used for iterator
     * @param array  $args   Command args to be used with each command
     *
     * @return \Aws\ResultPaginator
     * @throws \UnexpectedValueException if the iterator config is invalid.
     */
    public function getPaginator($name, array $args = []);

    /**
     * Wait until a resource is in a particular state.
     *
     * @param string|callable $name Name of the waiter that defines the wait
     *                              configuration and conditions.
     * @param array  $args          Args to be used with each command executed
     *                              by the waiter. Waiter configuration options
     *                              can be provided in an associative array in
     *                              the @waiter key.
     * @return void
     * @throws \UnexpectedValueException if the waiter is invalid.
     */
    public function waitUntil($name, array $args = []);

    /**
     * Get a waiter that waits until a resource is in a particular state.
     *
     * Retrieving a waiter can be useful when you wish to wait asynchronously:
     *
     *     $waiter = $client->getWaiter('foo', ['bar' => 'baz']);
     *     $waiter->promise()->then(function () { echo 'Done!'; });
     *
     * @param string|callable $name Name of the waiter that defines the wait
     *                              configuration and conditions.
     * @param array  $args          Args to be used with each command executed
     *                              by the waiter. Waiter configuration options
     *                              can be provided in an associative array in
     *                              the @waiter key.
     * @return \Aws\Waiter
     * @throws \UnexpectedValueException if the waiter is invalid.
     */
    public function getWaiter($name, array $args = []);
}


<?php
namespace Aws;

use Aws\Api\Service;

/**
 * A trait providing generic functionality for interacting with Amazon Web
 * Services. This is meant to be used in classes implementing
 * \Aws\AwsClientInterface
 */
trait AwsClientTrait
{
    public function getPaginator($name, array $args = [])
    {
        $config = $this->getApi()->getPaginatorConfig($name);

        return new ResultPaginator($this, $name, $args, $config);
    }

    public function getIterator($name, array $args = [])
    {
        $config = $this->getApi()->getPaginatorConfig($name);
        if (!$config['result_key']) {
            throw new \UnexpectedValueException(sprintf(
                'There are no resources to iterate for the %s operation of %s',
                $name, $this->getApi()['serviceFullName']
            ));
        }

        $key = is_array($config['result_key'])
            ? $config['result_key'][0]
            : $config['result_key'];

        if ($config['output_token'] && $config['input_token']) {
            return $this->getPaginator($name, $args)->search($key);
        }

        $result = $this->execute($this->getCommand($name, $args))->search($key);

        return new \ArrayIterator((array) $result);
    }

    public function waitUntil($name, array $args = [])
    {
        return $this->getWaiter($name, $args)->promise()->wait();
    }

    public function getWaiter($name, array $args = [])
    {
        $config = isset($args['@waiter']) ? $args['@waiter'] : [];
        $config += $this->getApi()->getWaiterConfig($name);

        return new Waiter($this, $name, $args, $config);
    }

    public function execute(CommandInterface $command)
    {
        return $this->executeAsync($command)->wait();
    }

    public function executeAsync(CommandInterface $command)
    {
        $handler = $command->getHandlerList()->resolve();
        return $handler($command);
    }

    public function __call($name, array $args)
    {
        if (substr($name, -5) === 'Async') {
            $name = substr($name, 0, -5);
            $isAsync = true;
        }

        if (!empty($this->aliases[ucfirst($name)])) {
            $name = $this->aliases[ucfirst($name)];
        }

        $params = isset($args[0]) ? $args[0] : [];

        if (!empty($isAsync)) {
            return $this->executeAsync(
                $this->getCommand($name, $params)
            );
        }

        return $this->execute($this->getCommand($name, $params));
    }

    /**
     * @param string $name
     * @param array $args
     *
     * @return CommandInterface
     */
    abstract public function getCommand($name, array $args = []);

    /**
     * @return Service
     */
    abstract public function getApi();
}


<?php
namespace Aws;

/**
 * Represents a simple cache interface.
 */
interface CacheInterface
{
    /**
     * Get a cache item by key.
     *
     * @param string $key Key to retrieve.
     *
     * @return mixed|null Returns the value or null if not found.
     */
    public function get($key);

    /**
     * Set a cache key value.
     *
     * @param string $key   Key to set
     * @param mixed  $value Value to set.
     * @param int    $ttl   Number of seconds the item is allowed to live. Set
     *                      to 0 to allow an unlimited lifetime.
     */
    public function set($key, $value, $ttl = 0);

    /**
     * Remove a cache key.
     *
     * @param string $key Key to remove.
     */
    public function remove($key);
}


<?php
namespace Aws;

use Aws\Api\ApiProvider;
use Aws\Api\Service;
use Aws\Api\Validator;
use Aws\ClientSideMonitoring\ApiCallAttemptMonitoringMiddleware;
use Aws\ClientSideMonitoring\ApiCallMonitoringMiddleware;
use Aws\ClientSideMonitoring\Configuration;
use Aws\Configuration\ConfigurationResolver;
use Aws\Credentials\CredentialProvider;
use Aws\Credentials\Credentials;
use Aws\Credentials\CredentialsInterface;
use Aws\DefaultsMode\ConfigurationInterface as ConfigModeInterface;
use Aws\DefaultsMode\ConfigurationProvider as ConfigModeProvider;
use Aws\Endpoint\EndpointProvider;
use Aws\Endpoint\PartitionEndpointProvider;
use Aws\Endpoint\UseDualstackEndpoint\Configuration as UseDualStackEndpointConfiguration;
use Aws\Endpoint\UseDualstackEndpoint\ConfigurationInterface as UseDualStackEndpointConfigurationInterface;
use Aws\Endpoint\UseDualstackEndpoint\ConfigurationProvider as UseDualStackConfigProvider;
use Aws\Endpoint\UseFipsEndpoint\Configuration as UseFipsEndpointConfiguration;
use Aws\Endpoint\UseFipsEndpoint\ConfigurationInterface as UseFipsEndpointConfigurationInterface;
use Aws\Endpoint\UseFipsEndpoint\ConfigurationProvider as UseFipsConfigProvider;
use Aws\EndpointDiscovery\ConfigurationInterface;
use Aws\EndpointDiscovery\ConfigurationProvider;
use Aws\EndpointV2\EndpointDefinitionProvider;
use Aws\Exception\AwsException;
use Aws\Exception\InvalidRegionException;
use Aws\Retry\ConfigurationInterface as RetryConfigInterface;
use Aws\Retry\ConfigurationProvider as RetryConfigProvider;
use Aws\Signature\SignatureProvider;
use Aws\Token\Token;
use Aws\Token\TokenInterface;
use Aws\Token\TokenProvider;
use GuzzleHttp\Promise\PromiseInterface;
use InvalidArgumentException as IAE;
use Psr\Http\Message\RequestInterface;

/**
 * @internal Resolves a hash of client arguments to construct a client.
 */
class ClientResolver
{
    /** @var array */
    private $argDefinitions;

    /** @var array Map of types to a corresponding function */
    private static $typeMap = [
        'resource' => 'is_resource',
        'callable' => 'is_callable',
        'int'      => 'is_int',
        'bool'     => 'is_bool',
        'boolean'  => 'is_bool',
        'string'   => 'is_string',
        'object'   => 'is_object',
        'array'    => 'is_array',
    ];

    private static $defaultArgs = [
        'service' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'doc'      => 'Name of the service to utilize. This value will be supplied by default when using one of the SDK clients (e.g., Aws\\S3\\S3Client).',
            'required' => true,
            'internal' => true
        ],
        'exception_class' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'doc'      => 'Exception class to create when an error occurs.',
            'default'  => AwsException::class,
            'internal' => true
        ],
        'scheme' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'default'  => 'https',
            'doc'      => 'URI scheme to use when connecting connect. The SDK will utilize "https" endpoints (i.e., utilize SSL/TLS connections) by default. You can attempt to connect to a service over an unencrypted "http" endpoint by setting ``scheme`` to "http".',
        ],
        'disable_host_prefix_injection' => [
            'type'      => 'value',
            'valid'     => ['bool'],
            'doc'       => 'Set to true to disable host prefix injection logic for services that use it. This disables the entire prefix injection, including the portions supplied by user-defined parameters. Setting this flag will have no effect on services that do not use host prefix injection.',
            'default'   => false,
        ],
        'endpoint' => [
            'type'  => 'value',
            'valid' => ['string'],
            'doc'   => 'The full URI of the webservice. This is only required when connecting to a custom endpoint (e.g., a local version of S3).',
            'fn'    => [__CLASS__, '_apply_endpoint'],
        ],
        'region' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'required' => [__CLASS__, '_missing_region'],
            'doc'      => 'Region to connect to. See http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of available regions.',
        ],
        'version' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'required' => [__CLASS__, '_missing_version'],
            'doc'      => 'The version of the webservice to utilize (e.g., 2006-03-01).',
        ],
        'signature_provider' => [
            'type'    => 'value',
            'valid'   => ['callable'],
            'doc'     => 'A callable that accepts a signature version name (e.g., "v4"), a service name, and region, and  returns a SignatureInterface object or null. This provider is used to create signers utilized by the client. See Aws\\Signature\\SignatureProvider for a list of built-in providers',
            'default' => [__CLASS__, '_default_signature_provider'],
        ],
        'api_provider' => [
            'type'     => 'value',
            'valid'    => ['callable'],
            'doc'      => 'An optional PHP callable that accepts a type, service, and version argument, and returns an array of corresponding configuration data. The type value can be one of api, waiter, or paginator.',
            'fn'       => [__CLASS__, '_apply_api_provider'],
            'default'  => [ApiProvider::class, 'defaultProvider'],
        ],
        'configuration_mode' => [
            'type'    => 'value',
            'valid'   => [ConfigModeInterface::class, CacheInterface::class, 'string', 'closure'],
            'doc'     => "Sets the default configuration mode. Otherwise provide an instance of Aws\DefaultsMode\ConfigurationInterface, an instance of  Aws\CacheInterface, or a string containing a valid mode",
            'fn'      => [__CLASS__, '_apply_defaults'],
            'default' => [ConfigModeProvider::class, 'defaultProvider']
        ],
        'use_fips_endpoint' => [
            'type'      => 'value',
            'valid'     => ['bool', UseFipsEndpointConfiguration::class, CacheInterface::class, 'callable'],
            'doc'       => 'Set to true to enable the use of FIPS pseudo regions',
            'fn'        => [__CLASS__, '_apply_use_fips_endpoint'],
            'default'   => [__CLASS__, '_default_use_fips_endpoint'],
        ],
        'use_dual_stack_endpoint' => [
            'type'      => 'value',
            'valid'     => ['bool', UseDualStackEndpointConfiguration::class, CacheInterface::class, 'callable'],
            'doc'       => 'Set to true to enable the use of dual-stack endpoints',
            'fn'        => [__CLASS__, '_apply_use_dual_stack_endpoint'],
            'default'   => [__CLASS__, '_default_use_dual_stack_endpoint'],
        ],
        'endpoint_provider' => [
            'type'     => 'value',
            'valid'    => ['callable', EndpointV2\EndpointProviderV2::class],
            'fn'       => [__CLASS__, '_apply_endpoint_provider'],
            'doc'      => 'An optional PHP callable that accepts a hash of options including a "service" and "region" key and returns NULL or a hash of endpoint data, of which the "endpoint" key is required. See Aws\\Endpoint\\EndpointProvider for a list of built-in providers.',
            'default'  => [__CLASS__, '_default_endpoint_provider'],
        ],
        'serializer' => [
            'default'   => [__CLASS__, '_default_serializer'],
            'internal'  => true,
            'type'      => 'value',
            'valid'     => ['callable'],
        ],
        'signature_version' => [
            'type'    => 'config',
            'valid'   => ['string'],
            'doc'     => 'A string representing a custom signature version to use with a service (e.g., v4). Note that per/operation signature version MAY override this requested signature version.',
            'default' => [__CLASS__, '_default_signature_version'],
        ],
        'signing_name' => [
            'type'    => 'config',
            'valid'   => ['string'],
            'doc'     => 'A string representing a custom service name to be used when calculating a request signature.',
            'default' => [__CLASS__, '_default_signing_name'],
        ],
        'signing_region' => [
            'type'    => 'config',
            'valid'   => ['string'],
            'doc'     => 'A string representing a custom region name to be used when calculating a request signature.',
            'default' => [__CLASS__, '_default_signing_region'],
        ],
        'profile' => [
            'type'  => 'config',
            'valid' => ['string'],
            'doc'   => 'Allows you to specify which profile to use when credentials are created from the AWS credentials file in your HOME directory. This setting overrides the AWS_PROFILE environment variable. Note: Specifying "profile" will cause the "credentials" and "use_aws_shared_config_files" keys to be ignored.',
            'fn'    => [__CLASS__, '_apply_profile'],
        ],
        'credentials' => [
            'type'    => 'value',
            'valid'   => [CredentialsInterface::class, CacheInterface::class, 'array', 'bool', 'callable'],
            'doc'     => 'Specifies the credentials used to sign requests. Provide an Aws\Credentials\CredentialsInterface object, an associative array of "key", "secret", and an optional "token" key, `false` to use null credentials, or a callable credentials provider used to create credentials or return null. See Aws\\Credentials\\CredentialProvider for a list of built-in credentials providers. If no credentials are provided, the SDK will attempt to load them from the environment.',
            'fn'      => [__CLASS__, '_apply_credentials'],
            'default' => [__CLASS__, '_default_credential_provider'],
        ],
        'token' => [
            'type'    => 'value',
            'valid'   => [TokenInterface::class, CacheInterface::class, 'array', 'bool', 'callable'],
            'doc'     => 'Specifies the token used to authorize requests. Provide an Aws\Token\TokenInterface object, an associative array of "token", and an optional "expiration" key, `false` to use a null token, or a callable token provider used to fetch a token or return null. See Aws\\Token\\TokenProvider for a list of built-in credentials providers. If no token is provided, the SDK will attempt to load one from the environment.',
            'fn'      => [__CLASS__, '_apply_token'],
            'default' => [__CLASS__, '_default_token_provider'],
        ],
        'endpoint_discovery' => [
            'type'     => 'value',
            'valid'    => [ConfigurationInterface::class, CacheInterface::class, 'array', 'callable'],
            'doc'      => 'Specifies settings for endpoint discovery. Provide an instance of Aws\EndpointDiscovery\ConfigurationInterface, an instance Aws\CacheInterface, a callable that provides a promise for a Configuration object, or an associative array with the following keys: enabled: (bool) Set to true to enable endpoint discovery, false to explicitly disable it. Defaults to false; cache_limit: (int) The maximum number of keys in the endpoints cache. Defaults to 1000.',
            'fn'       => [__CLASS__, '_apply_endpoint_discovery'],
            'default'  => [__CLASS__, '_default_endpoint_discovery_provider']
        ],
        'stats' => [
            'type'  => 'value',
            'valid' => ['bool', 'array'],
            'default' => false,
            'doc'   => 'Set to true to gather transfer statistics on requests sent. Alternatively, you can provide an associative array with the following keys: retries: (bool) Set to false to disable reporting on retries attempted; http: (bool) Set to true to enable collecting statistics from lower level HTTP adapters (e.g., values returned in GuzzleHttp\TransferStats). HTTP handlers must support an http_stats_receiver option for this to have an effect; timer: (bool) Set to true to enable a command timer that reports the total wall clock time spent on an operation in seconds.',
            'fn'    => [__CLASS__, '_apply_stats'],
        ],
        'retries' => [
            'type'    => 'value',
            'valid'   => ['int', RetryConfigInterface::class, CacheInterface::class, 'callable', 'array'],
            'doc'     => "Configures the retry mode and maximum number of allowed retries for a client (pass 0 to disable retries). Provide an integer for 'legacy' mode with the specified number of retries. Otherwise provide an instance of Aws\Retry\ConfigurationInterface, an instance of  Aws\CacheInterface, a callable function, or an array with the following keys: mode: (string) Set to 'legacy', 'standard' (uses retry quota management), or 'adapative' (an experimental mode that adds client-side rate limiting to standard mode); max_attempts: (int) The maximum number of attempts for a given request. ",
            'fn'      => [__CLASS__, '_apply_retries'],
            'default' => [RetryConfigProvider::class, 'defaultProvider']
        ],
        'validate' => [
            'type'    => 'value',
            'valid'   => ['bool', 'array'],
            'default' => true,
            'doc'     => 'Set to false to disable client-side parameter validation. Set to true to utilize default validation constraints. Set to an associative array of validation options to enable specific validation constraints.',
            'fn'      => [__CLASS__, '_apply_validate'],
        ],
        'debug' => [
            'type'  => 'value',
            'valid' => ['bool', 'array'],
            'doc'   => 'Set to true to display debug information when sending requests. Alternatively, you can provide an associative array with the following keys: logfn: (callable) Function that is invoked with log messages; stream_size: (int) When the size of a stream is greater than this number, the stream data will not be logged (set to "0" to not log any stream data); scrub_auth: (bool) Set to false to disable the scrubbing of auth data from the logged messages; http: (bool) Set to false to disable the "debug" feature of lower level HTTP adapters (e.g., verbose curl output).',
            'fn'    => [__CLASS__, '_apply_debug'],
        ],
        'disable_request_compression' => [
            'type'      => 'value',
            'valid'     => ['bool', 'callable'],
            'doc'       => 'Set to true to disable request compression for supported operations',
            'fn'        => [__CLASS__, '_apply_disable_request_compression'],
            'default'   => [__CLASS__, '_default_disable_request_compression'],
        ],
        'request_min_compression_size_bytes' => [
            'type'      => 'value',
            'valid'     => ['int', 'callable'],
            'doc'       => 'Set to a value between between 0 and 10485760 bytes, inclusive. This value will be ignored if `disable_request_compression` is set to `true`',
            'fn'        => [__CLASS__, '_apply_min_compression_size'],
            'default'   => [__CLASS__, '_default_min_compression_size'],
        ],
        'csm' => [
            'type'     => 'value',
            'valid'    => [\Aws\ClientSideMonitoring\ConfigurationInterface::class, 'callable', 'array', 'bool'],
            'doc'      => 'CSM options for the client. Provides a callable wrapping a promise, a boolean "false", an instance of ConfigurationInterface, or an associative array of "enabled", "host", "port", and "client_id".',
            'fn'       => [__CLASS__, '_apply_csm'],
            'default'  => [\Aws\ClientSideMonitoring\ConfigurationProvider::class, 'defaultProvider']
        ],
        'http' => [
            'type'    => 'value',
            'valid'   => ['array'],
            'default' => [],
            'doc'     => 'Set to an array of SDK request options to apply to each request (e.g., proxy, verify, etc.).',
        ],
        'http_handler' => [
            'type'    => 'value',
            'valid'   => ['callable'],
            'doc'     => 'An HTTP handler is a function that accepts a PSR-7 request object and returns a promise that is fulfilled with a PSR-7 response object or rejected with an array of exception data. NOTE: This option supersedes any provided "handler" option.',
            'fn'      => [__CLASS__, '_apply_http_handler']
        ],
        'handler' => [
            'type'     => 'value',
            'valid'    => ['callable'],
            'doc'      => 'A handler that accepts a command object, request object and returns a promise that is fulfilled with an Aws\ResultInterface object or rejected with an Aws\Exception\AwsException. A handler does not accept a next handler as it is terminal and expected to fulfill a command. If no handler is provided, a default Guzzle handler will be utilized.',
            'fn'       => [__CLASS__, '_apply_handler'],
            'default'  => [__CLASS__, '_default_handler']
        ],
        'ua_append' => [
            'type'     => 'value',
            'valid'    => ['string', 'array'],
            'doc'      => 'Provide a string or array of strings to send in the User-Agent header.',
            'fn'       => [__CLASS__, '_apply_user_agent'],
            'default'  => [],
        ],
        'idempotency_auto_fill' => [
            'type'      => 'value',
            'valid'     => ['bool', 'callable'],
            'doc'       => 'Set to false to disable SDK to populate parameters that enabled \'idempotencyToken\' trait with a random UUID v4 value on your behalf. Using default value \'true\' still allows parameter value to be overwritten when provided. Note: auto-fill only works when cryptographically secure random bytes generator functions(random_bytes, openssl_random_pseudo_bytes or mcrypt_create_iv) can be found. You may also provide a callable source of random bytes.',
            'default'   => true,
            'fn'        => [__CLASS__, '_apply_idempotency_auto_fill']
        ],
        'use_aws_shared_config_files' => [
            'type'      => 'value',
            'valid'     => ['bool'],
            'doc'       => 'Set to false to disable checking for shared aws config files usually located in \'~/.aws/config\' and \'~/.aws/credentials\'.  This will be ignored if you set the \'profile\' setting.',
            'default'   => true,
        ],
        'suppress_php_deprecation_warning' => [
            'type'      => 'value',
            'valid'     => ['bool'],
            'doc'       => 'Set to false to disable the deprecation warning of PHP versions 7.2.4 and below',
            'default'   => false,
            'fn'        => [__CLASS__, '_apply_suppress_php_deprecation_warning']
        ],
    ];

    /**
     * Gets an array of default client arguments, each argument containing a
     * hash of the following:
     *
     * - type: (string, required) option type described as follows:
     *   - value: The default option type.
     *   - config: The provided value is made available in the client's
     *     getConfig() method.
     * - valid: (array, required) Valid PHP types or class names. Note: null
     *   is not an allowed type.
     * - required: (bool, callable) Whether or not the argument is required.
     *   Provide a function that accepts an array of arguments and returns a
     *   string to provide a custom error message.
     * - default: (mixed) The default value of the argument if not provided. If
     *   a function is provided, then it will be invoked to provide a default
     *   value. The function is provided the array of options and is expected
     *   to return the default value of the option. The default value can be a
     *   closure and can not be a callable string that is not  part of the
     *   defaultArgs array.
     * - doc: (string) The argument documentation string.
     * - fn: (callable) Function used to apply the argument. The function
     *   accepts the provided value, array of arguments by reference, and an
     *   event emitter.
     *
     * Note: Order is honored and important when applying arguments.
     *
     * @return array
     */
    public static function getDefaultArguments()
    {
        return self::$defaultArgs;
    }

    /**
     * @param array $argDefinitions Client arguments.
     */
    public function __construct(array $argDefinitions)
    {
        $this->argDefinitions = $argDefinitions;
    }

    /**
     * Resolves client configuration options and attached event listeners.
     * Check for missing keys in passed arguments
     *
     * @param array       $args Provided constructor arguments.
     * @param HandlerList $list Handler list to augment.
     *
     * @return array Returns the array of provided options.
     * @throws \InvalidArgumentException
     * @see Aws\AwsClient::__construct for a list of available options.
     */
    public function resolve(array $args, HandlerList $list)
    {
        $args['config'] = [];
        foreach ($this->argDefinitions as $key => $a) {
            // Add defaults, validate required values, and skip if not set.
            if (!isset($args[$key])) {
                if (isset($a['default'])) {
                    // Merge defaults in when not present.
                    if (is_callable($a['default'])
                        && (
                            is_array($a['default'])
                            || $a['default'] instanceof \Closure
                        )
                    ) {
                        $args[$key] = $a['default']($args);
                    } else {
                        $args[$key] = $a['default'];
                    }
                } elseif (empty($a['required'])) {
                    continue;
                } else {
                    $this->throwRequired($args);
                }
            }

            // Validate the types against the provided value.
            foreach ($a['valid'] as $check) {
                if (isset(self::$typeMap[$check])) {
                    $fn = self::$typeMap[$check];
                    if ($fn($args[$key])) {
                        goto is_valid;
                    }
                } elseif ($args[$key] instanceof $check) {
                    goto is_valid;
                }
            }

            $this->invalidType($key, $args[$key]);

            // Apply the value
            is_valid:
            if (isset($a['fn'])) {
                $a['fn']($args[$key], $args, $list);
            }

            if ($a['type'] === 'config') {
                $args['config'][$key] = $args[$key];
            }
        }
        $this->_apply_client_context_params($args);

        return $args;
    }

    /**
     * Creates a verbose error message for an invalid argument.
     *
     * @param string $name        Name of the argument that is missing.
     * @param array  $args        Provided arguments
     * @param bool   $useRequired Set to true to show the required fn text if
     *                            available instead of the documentation.
     * @return string
     */
    private function getArgMessage($name, $args = [], $useRequired = false)
    {
        $arg = $this->argDefinitions[$name];
        $msg = '';
        $modifiers = [];
        if (isset($arg['valid'])) {
            $modifiers[] = implode('|', $arg['valid']);
        }
        if (isset($arg['choice'])) {
            $modifiers[] = 'One of ' . implode(', ', $arg['choice']);
        }
        if ($modifiers) {
            $msg .= '(' . implode('; ', $modifiers) . ')';
        }
        $msg = wordwrap("{$name}: {$msg}", 75, "\n  ");

        if ($useRequired && is_callable($arg['required'])) {
            $msg .= "\n\n  ";
            $msg .= str_replace("\n", "\n  ", call_user_func($arg['required'], $args));
        } elseif (isset($arg['doc'])) {
            $msg .= wordwrap("\n\n  {$arg['doc']}", 75, "\n  ");
        }

        return $msg;
    }

    /**
     * Throw when an invalid type is encountered.
     *
     * @param string $name     Name of the value being validated.
     * @param mixed  $provided The provided value.
     * @throws \InvalidArgumentException
     */
    private function invalidType($name, $provided)
    {
        $expected = implode('|', $this->argDefinitions[$name]['valid']);
        $msg = "Invalid configuration value "
            . "provided for \"{$name}\". Expected {$expected}, but got "
            . describe_type($provided) . "\n\n"
            . $this->getArgMessage($name);
        throw new IAE($msg);
    }

    /**
     * Throws an exception for missing required arguments.
     *
     * @param array $args Passed in arguments.
     * @throws \InvalidArgumentException
     */
    private function throwRequired(array $args)
    {
        $missing = [];
        foreach ($this->argDefinitions as $k => $a) {
            if (empty($a['required'])
                || isset($a['default'])
                || isset($args[$k])
            ) {
                continue;
            }
            $missing[] = $this->getArgMessage($k, $args, true);
        }
        $msg = "Missing required client configuration options: \n\n";
        $msg .= implode("\n\n", $missing);
        throw new IAE($msg);
    }

    public static function _apply_retries($value, array &$args, HandlerList $list)
    {
        // A value of 0 for the config option disables retries
        if ($value) {
            $config = RetryConfigProvider::unwrap($value);

            if ($config->getMode() === 'legacy') {
                // # of retries is 1 less than # of attempts
                $decider = RetryMiddleware::createDefaultDecider(
                    $config->getMaxAttempts() - 1
                );
                $list->appendSign(
                    Middleware::retry($decider, null, $args['stats']['retries']),
                    'retry'
                );
            } else {
                $list->appendSign(
                    RetryMiddlewareV2::wrap(
                        $config,
                        ['collect_stats' => $args['stats']['retries']]
                    ),
                    'retry'
                );
            }
        }
    }

    public static function _apply_defaults($value, array &$args, HandlerList $list)
    {
        $config = ConfigModeProvider::unwrap($value);
        if ($config->getMode() !== 'legacy') {
            if (!isset($args['retries']) && !is_null($config->getRetryMode())) {
                $args['retries'] = ['mode' => $config->getRetryMode()];
            }
            if (
                !isset($args['sts_regional_endpoints'])
                && !is_null($config->getStsRegionalEndpoints())
            ) {
                $args['sts_regional_endpoints'] = ['mode' => $config->getStsRegionalEndpoints()];
            }
            if (
                !isset($args['s3_us_east_1_regional_endpoint'])
                && !is_null($config->getS3UsEast1RegionalEndpoints())
            ) {
                $args['s3_us_east_1_regional_endpoint'] = ['mode' => $config->getS3UsEast1RegionalEndpoints()];
            }

            if (!isset($args['http'])) {
                $args['http'] = [];
            }
            if (
                !isset($args['http']['connect_timeout'])
                && !is_null($config->getConnectTimeoutInMillis())
            ) {
                $args['http']['connect_timeout'] = $config->getConnectTimeoutInMillis() / 1000;
            }
            if (
                !isset($args['http']['timeout'])
                && !is_null($config->getHttpRequestTimeoutInMillis())
            ) {
                $args['http']['timeout'] = $config->getHttpRequestTimeoutInMillis() / 1000;
            }
        }
    }

    public static function _apply_disable_request_compression($value, array &$args) {
        if (is_callable($value)) {
            $value = $value();
        }
        if (!is_bool($value)) {
           throw new IAE(
              "Invalid configuration value provided for 'disable_request_compression'."
              . " value must be a bool."
           );
        }
        $args['config']['disable_request_compression'] = $value;
    }

    public static function _default_disable_request_compression(array &$args) {
        return ConfigurationResolver::resolve(
            'disable_request_compression',
            false,
            'bool',
            $args
        );
    }

    public static function _apply_min_compression_size($value, array &$args) {
        if (is_callable($value)) {
            $value = $value();
        }
        if (!is_int($value)
            || (is_int($value)
            && ($value < 0 || $value > 10485760))
        ) {
            throw new IAE(" Invalid configuration value provided for 'min_compression_size_bytes'."
            . " value must be an integer between 0 and 10485760, inclusive.");
        }
        $args['config']['request_min_compression_size_bytes'] = $value;
    }

    public static function _default_min_compression_size(array &$args) {
        return ConfigurationResolver::resolve(
            'request_min_compression_size_bytes',
            10240,
            'int',
            $args
        );
    }

    public static function _apply_credentials($value, array &$args)
    {
        if (is_callable($value)) {
            return;
        }

        if ($value instanceof CredentialsInterface) {
            $args['credentials'] = CredentialProvider::fromCredentials($value);
        } elseif (is_array($value)
            && isset($value['key'])
            && isset($value['secret'])
        ) {
            $args['credentials'] = CredentialProvider::fromCredentials(
                new Credentials(
                    $value['key'],
                    $value['secret'],
                    isset($value['token']) ? $value['token'] : null,
                    isset($value['expires']) ? $value['expires'] : null
                )
            );
        } elseif ($value === false) {
            $args['credentials'] = CredentialProvider::fromCredentials(
                new Credentials('', '')
            );
            $args['config']['signature_version'] = 'anonymous';
        } elseif ($value instanceof CacheInterface) {
            $args['credentials'] = CredentialProvider::defaultProvider($args);
        } else {
            throw new IAE('Credentials must be an instance of '
                . "'" . CredentialsInterface::class . ', an associative '
                . 'array that contains "key", "secret", and an optional "token" '
                . 'key-value pairs, a credentials provider function, or false.');
        }
    }

    public static function _default_credential_provider(array $args)
    {
        return CredentialProvider::defaultProvider($args);
    }

    public static function _apply_token($value, array &$args)
    {
        if (is_callable($value)) {
            return;
        }

        if ($value instanceof Token) {
            $args['token'] = TokenProvider::fromToken($value);
        } elseif (is_array($value)
            && isset($value['token'])
        ) {
            $args['token'] = TokenProvider::fromToken(
                new Token(
                    $value['token'],
                    isset($value['expires']) ? $value['expires'] : null
                )
            );
        } elseif ($value instanceof CacheInterface) {
            $args['token'] = TokenProvider::defaultProvider($args);
        } else {
            throw new IAE('Token must be an instance of '
                . TokenInterface::class . ', an associative '
                . 'array that contains "token" and an optional "expires" '
                . 'key-value pairs, a token provider function, or false.');
        }
    }

    public static function _default_token_provider(array $args)
    {
        return TokenProvider::defaultProvider($args);
    }

    public static function _apply_csm($value, array &$args, HandlerList $list)
    {
        if ($value === false) {
            $value = new Configuration(
                false,
                \Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_HOST,
                \Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_PORT,
                \Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_CLIENT_ID
            );
            $args['csm'] = $value;
        }

        $list->appendBuild(
            ApiCallMonitoringMiddleware::wrap(
                $args['credentials'],
                $value,
                $args['region'],
                $args['api']->getServiceId()
            ),
            'ApiCallMonitoringMiddleware'
        );

        $list->appendAttempt(
            ApiCallAttemptMonitoringMiddleware::wrap(
                $args['credentials'],
                $value,
                $args['region'],
                $args['api']->getServiceId()
            ),
            'ApiCallAttemptMonitoringMiddleware'
        );
    }

    public static function _apply_api_provider(callable $value, array &$args)
    {
        $api = new Service(
            ApiProvider::resolve(
                $value,
                'api',
                $args['service'],
                $args['version']
            ),
            $value
        );

        if (
            empty($args['config']['signing_name'])
            && isset($api['metadata']['signingName'])
        ) {
            $args['config']['signing_name'] = $api['metadata']['signingName'];
        }

        $args['api'] = $api;
        $args['parser'] = Service::createParser($api);
        $args['error_parser'] = Service::createErrorParser($api->getProtocol(), $api);
    }

    public static function _apply_endpoint_provider($value, array &$args)
    {
        if (!isset($args['endpoint'])) {
            if ($value instanceof \Aws\EndpointV2\EndpointProviderV2) {
                $options = self::getEndpointProviderOptions($args);
                $value = PartitionEndpointProvider::defaultProvider($options)
                    ->getPartition($args['region'], $args['service']);
            }

            $endpointPrefix = isset($args['api']['metadata']['endpointPrefix'])
                ? $args['api']['metadata']['endpointPrefix']
                : $args['service'];

            // Check region is a valid host label when it is being used to
            // generate an endpoint
            if (!self::isValidRegion($args['region'])) {
                throw new InvalidRegionException('Region must be a valid RFC'
                    . ' host label.');
            }
            $serviceEndpoints =
                is_array($value) && isset($value['services'][$args['service']]['endpoints'])
                    ? $value['services'][$args['service']]['endpoints']
                    : null;
            if (isset($serviceEndpoints[$args['region']]['deprecated'])) {
                trigger_error("The service " . $args['service'] . "has "
                    . " deprecated the region " . $args['region'] . ".",
                    E_USER_WARNING
                );
            }

            $args['region'] = \Aws\strip_fips_pseudo_regions($args['region']);

            // Invoke the endpoint provider and throw if it does not resolve.
            $result = EndpointProvider::resolve($value, [
                'service' => $endpointPrefix,
                'region'  => $args['region'],
                'scheme'  => $args['scheme'],
                'options' => self::getEndpointProviderOptions($args),
            ]);

            $args['endpoint'] = $result['endpoint'];

            if (empty($args['config']['signature_version'])) {
                if (
                    isset($args['api'])
                    && $args['api']->getSignatureVersion() == 'bearer'
                ) {
                    $args['config']['signature_version'] = 'bearer';
                } elseif (isset($result['signatureVersion'])) {
                    $args['config']['signature_version'] = $result['signatureVersion'];
                }
            }

            if (
                empty($args['config']['signing_region'])
                && isset($result['signingRegion'])
            ) {
                $args['config']['signing_region'] = $result['signingRegion'];
            }

            if (
                empty($args['config']['signing_name'])
                && isset($result['signingName'])
            ) {
                $args['config']['signing_name'] = $result['signingName'];
            }
        }
    }

    public static function _apply_endpoint_discovery($value, array &$args) {
        $args['endpoint_discovery'] = $value;
    }

    public static function _default_endpoint_discovery_provider(array $args)
    {
        return ConfigurationProvider::defaultProvider($args);
    }

    public static function _apply_use_fips_endpoint($value, array &$args) {
        if ($value instanceof CacheInterface) {
            $value = UseFipsConfigProvider::defaultProvider($args);
        }
        if (is_callable($value)) {
            $value = $value();
        }
        if ($value instanceof PromiseInterface) {
            $value = $value->wait();
        }
        if ($value instanceof UseFipsEndpointConfigurationInterface) {
            $args['config']['use_fips_endpoint'] = $value;
        } else {
            // The Configuration class itself will validate other inputs
            $args['config']['use_fips_endpoint'] = new UseFipsEndpointConfiguration($value);
        }
    }

    public static function _default_use_fips_endpoint(array &$args) {
        return UseFipsConfigProvider::defaultProvider($args);
    }

    public static function _apply_use_dual_stack_endpoint($value, array &$args) {
        if ($value instanceof CacheInterface) {
            $value = UseDualStackConfigProvider::defaultProvider($args);
        }
        if (is_callable($value)) {
            $value = $value();
        }
        if ($value instanceof PromiseInterface) {
            $value = $value->wait();
        }
        if ($value instanceof UseDualStackEndpointConfigurationInterface) {
            $args['config']['use_dual_stack_endpoint'] = $value;
        } else {
            // The Configuration class itself will validate other inputs
            $args['config']['use_dual_stack_endpoint'] =
                new UseDualStackEndpointConfiguration($value, $args['region']);
        }
    }

    public static function _default_use_dual_stack_endpoint(array &$args) {
        return UseDualStackConfigProvider::defaultProvider($args);
    }

    public static function _apply_debug($value, array &$args, HandlerList $list)
    {
        if ($value !== false) {
            $list->interpose(
                new TraceMiddleware(
                    $value === true ? [] : $value,
                    $args['api'])
            );
        }
    }

    public static function _apply_stats($value, array &$args, HandlerList $list)
    {
        // Create an array of stat collectors that are disabled (set to false)
        // by default. If the user has passed in true, enable all stat
        // collectors.
        $defaults = array_fill_keys(
            ['http', 'retries', 'timer'],
            $value === true
        );
        $args['stats'] = is_array($value)
            ? array_replace($defaults, $value)
            : $defaults;

        if ($args['stats']['timer']) {
            $list->prependInit(Middleware::timer(), 'timer');
        }
    }

    public static function _apply_profile($_, array &$args)
    {
        $args['credentials'] = CredentialProvider::ini($args['profile']);
    }

    public static function _apply_validate($value, array &$args, HandlerList $list)
    {
        if ($value === false) {
            return;
        }

        $validator = $value === true
            ? new Validator()
            : new Validator($value);
        $list->appendValidate(
            Middleware::validation($args['api'], $validator),
            'validation'
        );
    }

    public static function _apply_handler($value, array &$args, HandlerList $list)
    {
        $list->setHandler($value);
    }

    public static function _default_handler(array &$args)
    {
        return new WrappedHttpHandler(
            default_http_handler(),
            $args['parser'],
            $args['error_parser'],
            $args['exception_class'],
            $args['stats']['http']
        );
    }

    public static function _apply_http_handler($value, array &$args, HandlerList $list)
    {
        $args['handler'] = new WrappedHttpHandler(
            $value,
            $args['parser'],
            $args['error_parser'],
            $args['exception_class'],
            $args['stats']['http']
        );
    }

    public static function _apply_user_agent($inputUserAgent, array &$args, HandlerList $list)
    {
        //Add SDK version
        $userAgent = ['aws-sdk-php/' . Sdk::VERSION];

        //If on HHVM add the HHVM version
        if (defined('HHVM_VERSION')) {
            $userAgent []= 'HHVM/' . HHVM_VERSION;
        }

        //Add OS version
        $disabledFunctions = explode(',', ini_get('disable_functions'));
        if (function_exists('php_uname')
            && !in_array('php_uname', $disabledFunctions, true)
        ) {
            $osName = "OS/" . php_uname('s') . '#' . php_uname('r');
            if (!empty($osName)) {
                $userAgent []= $osName;
            }
        }

        //Add the language version
        $userAgent []= 'lang/php#' . phpversion();

        //Add exec environment if present
        if ($executionEnvironment = getenv('AWS_EXECUTION_ENV')) {
            $userAgent []= $executionEnvironment;
        }

        //Add endpoint discovery if set
        if (isset($args['endpoint_discovery'])) {
            if (($args['endpoint_discovery'] instanceof \Aws\EndpointDiscovery\Configuration
                && $args['endpoint_discovery']->isEnabled())
            ) {
                $userAgent []= 'cfg/endpoint-discovery';
            } elseif (is_array($args['endpoint_discovery'])
                && isset($args['endpoint_discovery']['enabled'])
                && $args['endpoint_discovery']['enabled']
            ) {
                $userAgent []= 'cfg/endpoint-discovery';
            }
        }

        //Add retry mode if set
        if (isset($args['retries'])) {
            if ($args['retries'] instanceof \Aws\Retry\Configuration) {
                $userAgent []= 'cfg/retry-mode#' . $args["retries"]->getMode();
            } elseif (is_array($args['retries'])
                && isset($args["retries"]["mode"])
            ) {
                $userAgent []= 'cfg/retry-mode#' . $args["retries"]["mode"];
            }
        }
        //Add the input to the end
        if ($inputUserAgent){
            if (!is_array($inputUserAgent)) {
                $inputUserAgent = [$inputUserAgent];
            }
            $inputUserAgent = array_map('strval', $inputUserAgent);
            $userAgent = array_merge($userAgent, $inputUserAgent);
        }

        $args['ua_append'] = $userAgent;

        $list->appendBuild(static function (callable $handler) use ($userAgent) {
            return function (
                CommandInterface $command,
                RequestInterface $request
            ) use ($handler, $userAgent) {
                return $handler(
                    $command,
                    $request->withHeader(
                        'X-Amz-User-Agent',
                        implode(' ', array_merge(
                            $userAgent,
                            $request->getHeader('X-Amz-User-Agent')
                        ))
                    )->withHeader(
                        'User-Agent',
                        implode(' ', array_merge(
                            $userAgent,
                            $request->getHeader('User-Agent')
                        ))
                    )
                );
            };
        });
    }

    public static function _apply_endpoint($value, array &$args, HandlerList $list)
    {
        $args['endpoint'] = $value;
    }

    public static function _apply_idempotency_auto_fill(
        $value,
        array &$args,
        HandlerList $list
    ) {
        $enabled = false;
        $generator = null;

        if (is_bool($value)) {
            $enabled = $value;
        } elseif (is_callable($value)) {
            $enabled = true;
            $generator = $value;
        }

        if ($enabled) {
            $list->prependInit(
                IdempotencyTokenMiddleware::wrap($args['api'], $generator),
                'idempotency_auto_fill'
            );
        }
    }

    public static function _apply_suppress_php_deprecation_warning($suppressWarning, array &$args) {
        if ($suppressWarning) {
            $args['suppress_php_deprecation_warning'] = true;
        } elseif (!empty($_ENV["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"])) {
            $args['suppress_php_deprecation_warning'] =
                $_ENV["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"];
        } elseif (!empty($_SERVER["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"])) {
            $args['suppress_php_deprecation_warning'] =
                $_SERVER["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"];
        } elseif (!empty(getenv("AWS_SUPPRESS_PHP_DEPRECATION_WARNING"))) {
            $args['suppress_php_deprecation_warning']
                = getenv("AWS_SUPPRESS_PHP_DEPRECATION_WARNING");
        }
    }

    public static function _default_endpoint_provider(array $args)
    {
        $service =  isset($args['api']) ? $args['api'] : null;
        $serviceName = isset($service) ? $service->getServiceName() : null;
        $apiVersion = isset($service) ? $service->getApiVersion() : null;

        if (self::isValidService($serviceName)
            && self::isValidApiVersion($serviceName, $apiVersion)
        ) {
            $ruleset = EndpointDefinitionProvider::getEndpointRuleset(
                $service->getServiceName(),
                $service->getApiVersion()
            );
            return new \Aws\EndpointV2\EndpointProviderV2(
                $ruleset,
                EndpointDefinitionProvider::getPartitions()
            );
        }
        $options = self::getEndpointProviderOptions($args);
        return PartitionEndpointProvider::defaultProvider($options)
            ->getPartition($args['region'], $args['service']);
    }

    public static function _default_serializer(array $args)
    {
        return Service::createSerializer(
            $args['api'],
            $args['endpoint']
        );
    }

    public static function _default_signature_provider()
    {
        return SignatureProvider::defaultProvider();
    }

    public static function _default_signature_version(array &$args)
    {
        if (isset($args['config']['signature_version'])) {
            return $args['config']['signature_version'];
        }

        $args['__partition_result'] = isset($args['__partition_result'])
            ? isset($args['__partition_result'])
            : call_user_func(PartitionEndpointProvider::defaultProvider(), [
                'service' => $args['service'],
                'region' => $args['region'],
            ]);

        return isset($args['__partition_result']['signatureVersion'])
            ? $args['__partition_result']['signatureVersion']
            : $args['api']->getSignatureVersion();
    }

    public static function _default_signing_name(array &$args)
    {
        if (isset($args['config']['signing_name'])) {
            return $args['config']['signing_name'];
        }

        $args['__partition_result'] = isset($args['__partition_result'])
            ? isset($args['__partition_result'])
            : call_user_func(PartitionEndpointProvider::defaultProvider(), [
                'service' => $args['service'],
                'region' => $args['region'],
            ]);

        if (isset($args['__partition_result']['signingName'])) {
            return $args['__partition_result']['signingName'];
        }

        if ($signingName = $args['api']->getSigningName()) {
            return $signingName;
        }

        return $args['service'];
    }

    public static function _default_signing_region(array &$args)
    {
        if (isset($args['config']['signing_region'])) {
            return $args['config']['signing_region'];
        }

        $args['__partition_result'] = isset($args['__partition_result'])
            ? isset($args['__partition_result'])
            : call_user_func(PartitionEndpointProvider::defaultProvider(), [
                'service' => $args['service'],
                'region' => $args['region'],
            ]);

        return isset($args['__partition_result']['signingRegion'])
            ? $args['__partition_result']['signingRegion']
            : $args['region'];
    }

    public static function _missing_version(array $args)
    {
        $service = isset($args['service']) ? $args['service'] : '';
        $versions = ApiProvider::defaultProvider()->getVersions($service);
        $versions = implode("\n", array_map(function ($v) {
            return "* \"$v\"";
        }, $versions)) ?: '* (none found)';

        return <<<EOT
A "version" configuration value is required. Specifying a version constraint
ensures that your code will not be affected by a breaking change made to the
service. For example, when using Amazon S3, you can lock your API version to
"2006-03-01".

Your build of the SDK has the following version(s) of "{$service}": {$versions}

You may provide "latest" to the "version" configuration value to utilize the
most recent available API version that your client's API provider can find.
Note: Using 'latest' in a production application is not recommended.

A list of available API versions can be found on each client's API documentation
page: http://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html. If you are
unable to load a specific API version, then you may need to update your copy of
the SDK.
EOT;
    }

    public static function _missing_region(array $args)
    {
        $service = isset($args['service']) ? $args['service'] : '';

        return <<<EOT
A "region" configuration value is required for the "{$service}" service
(e.g., "us-west-2"). A list of available public regions and endpoints can be
found at http://docs.aws.amazon.com/general/latest/gr/rande.html.
EOT;
    }

    /**
     * Extracts client options for the endpoint provider to its own array
     *
     * @param array $args
     * @return array
     */
    private static function getEndpointProviderOptions(array $args)
    {
        $options = [];
        $optionKeys = [
            'sts_regional_endpoints',
            's3_us_east_1_regional_endpoint',
            ];
        $configKeys = [
            'use_dual_stack_endpoint',
            'use_fips_endpoint',
        ];
        foreach ($optionKeys as $key) {
            if (isset($args[$key])) {
                $options[$key] = $args[$key];
            }
        }
        foreach ($configKeys as $key) {
            if (isset($args['config'][$key])) {
                $options[$key] = $args['config'][$key];
            }
        }
        return $options;
    }

    /**
     * Validates a region to be used for endpoint construction
     *
     * @param $region
     * @return bool
     */
    private static function isValidRegion($region)
    {
        return is_valid_hostlabel($region);
    }

    private function _apply_client_context_params(array $args)
    {
        if (isset($args['api'])
           && !empty($args['api']->getClientContextParams()))
        {
            $clientContextParams = $args['api']->getClientContextParams();
            foreach($clientContextParams as $paramName => $paramDefinition) {
                $definition = [
                    'type' => 'value',
                    'valid' => [$paramDefinition['type']],
                    'doc' => isset($paramDefinition['documentation']) ?
                        $paramDefinition['documentation'] : null
                ];
                $this->argDefinitions[$paramName] = $definition;

                if (isset($args[$paramName])) {
                    $fn = self::$typeMap[$paramDefinition['type']];
                    if (!$fn($args[$paramName])) {
                        $this->invalidType($paramName, $args[$paramName]);
                    }
                }
            }
        }
    }

    private static function isValidService($service) {
        if (is_null($service)) {
            return false;
        }
        $services = \Aws\manifest();
        return isset($services[$service]);
    }

    private static function isValidApiVersion($service, $apiVersion) {
        if (is_null($apiVersion)) {
            return false;
        }
        return is_dir(
          __DIR__ . "/data/{$service}/$apiVersion"
        );
    }
}


<?php
namespace Aws;

/**
 * AWS command object.
 */
class Command implements CommandInterface
{
    use HasDataTrait;

    /** @var string */
    private $name;

    /** @var HandlerList */
    private $handlerList;

    /** @var Array */
    private $authSchemes;

    /**
     * Accepts an associative array of command options, including:
     *
     * - @http: (array) Associative array of transfer options.
     *
     * @param string      $name           Name of the command
     * @param array       $args           Arguments to pass to the command
     * @param HandlerList $list           Handler list
     */
    public function __construct($name, array $args = [], HandlerList $list = null)
    {
        $this->name = $name;
        $this->data = $args;
        $this->handlerList = $list ?: new HandlerList();

        if (!isset($this->data['@http'])) {
            $this->data['@http'] = [];
        }
        if (!isset($this->data['@context'])) {
            $this->data['@context'] = [];
        }
    }

    public function __clone()
    {
        $this->handlerList = clone $this->handlerList;
    }

    public function getName()
    {
        return $this->name;
    }

    public function hasParam($name)
    {
        return array_key_exists($name, $this->data);
    }

    public function getHandlerList()
    {
        return $this->handlerList;
    }

    /**
     * For overriding auth schemes on a per endpoint basis when using
     * EndpointV2 provider. Intended for internal use only.
     *
     * @param array $authSchemes
     *
     * @internal
     */
    public function setAuthSchemes(array $authSchemes)
    {
        $this->authSchemes = $authSchemes;
    }

    /**
     * Get auth schemes added to command as required
     * for endpoint resolution
     *
     * @returns array | null
     */
    public function getAuthSchemes()
    {
        return $this->authSchemes;
    }

    /** @deprecated */
    public function get($name)
    {
        return $this[$name];
    }
}


<?php
namespace Aws;

/**
 * A command object encapsulates the input parameters used to control the
 * creation of a HTTP request and processing of a HTTP response.
 *
 * Using the toArray() method will return the input parameters of the command
 * as an associative array.
 */
interface CommandInterface extends \ArrayAccess, \Countable, \IteratorAggregate
{
    /**
     * Converts the command parameters to an array
     *
     * @return array
     */
    public function toArray();

    /**
     * Get the name of the command
     *
     * @return string
     */
    public function getName();

    /**
     * Check if the command has a parameter by name.
     *
     * @param string $name Name of the parameter to check
     *
     * @return bool
     */
    public function hasParam($name);

    /**
     * Get the handler list used to transfer the command.
     *
     * @return HandlerList
     */
    public function getHandlerList();
}


<?php
namespace Aws;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\PromisorInterface;
use GuzzleHttp\Promise\EachPromise;

/**
 * Sends and iterator of commands concurrently using a capped pool size.
 *
 * The pool will read command objects from an iterator until it is cancelled or
 * until the iterator is consumed.
 */
class CommandPool implements PromisorInterface
{
    /** @var EachPromise */
    private $each;

    /**
     * The CommandPool constructor accepts a hash of configuration options:
     *
     * - concurrency: (callable|int) Maximum number of commands to execute
     *   concurrently. Provide a function to resize the pool dynamically. The
     *   function will be provided the current number of pending requests and
     *   is expected to return an integer representing the new pool size limit.
     * - before: (callable) function to invoke before sending each command. The
     *   before function accepts the command and the key of the iterator of the
     *   command. You can mutate the command as needed in the before function
     *   before sending the command.
     * - fulfilled: (callable) Function to invoke when a promise is fulfilled.
     *   The function is provided the result object, id of the iterator that the
     *   result came from, and the aggregate promise that can be resolved/rejected
     *   if you need to short-circuit the pool.
     * - rejected: (callable) Function to invoke when a promise is rejected.
     *   The function is provided an AwsException object, id of the iterator that
     *   the exception came from, and the aggregate promise that can be
     *   resolved/rejected if you need to short-circuit the pool.
     * - preserve_iterator_keys: (bool) Retain the iterator key when generating
     *   the commands.
     *
     * @param AwsClientInterface $client   Client used to execute commands.
     * @param array|\Iterator    $commands Iterable that yields commands.
     * @param array              $config   Associative array of options.
     */
    public function __construct(
        AwsClientInterface $client,
        $commands,
        array $config = []
    ) {
        if (!isset($config['concurrency'])) {
            $config['concurrency'] = 25;
        }

        $before = $this->getBefore($config);
        $mapFn = function ($commands) use ($client, $before, $config) {
            foreach ($commands as $key => $command) {
                if (!($command instanceof CommandInterface)) {
                    throw new \InvalidArgumentException('Each value yielded by '
                        . 'the iterator must be an Aws\CommandInterface.');
                }
                if ($before) {
                    $before($command, $key);
                }
                if (!empty($config['preserve_iterator_keys'])) {
                    yield $key => $client->executeAsync($command);
                } else {
                    yield $client->executeAsync($command);
                }
            }
        };

        $this->each = new EachPromise($mapFn($commands), $config);
    }

    /**
     * @return PromiseInterface
     */
    public function promise()
    {
        return $this->each->promise();
    }

    /**
     * Executes a pool synchronously and aggregates the results of the pool
     * into an indexed array in the same order as the passed in array.
     *
     * @param AwsClientInterface $client   Client used to execute commands.
     * @param mixed              $commands Iterable that yields commands.
     * @param array              $config   Configuration options.
     *
     * @return array
     * @see \Aws\CommandPool::__construct for available configuration options.
     */
    public static function batch(
        AwsClientInterface $client,
        $commands,
        array $config = []
    ) {
        $results = [];
        self::cmpCallback($config, 'fulfilled', $results);
        self::cmpCallback($config, 'rejected', $results);

        return (new self($client, $commands, $config))
            ->promise()
            ->then(static function () use (&$results) {
                ksort($results);
                return $results;
            })
            ->wait();
    }

    /**
     * @return callable
     */
    private function getBefore(array $config)
    {
        if (!isset($config['before'])) {
            return null;
        }

        if (is_callable($config['before'])) {
            return $config['before'];
        }

        throw new \InvalidArgumentException('before must be callable');
    }

    /**
     * Adds an onFulfilled or onRejected callback that aggregates results into
     * an array. If a callback is already present, it is replaced with the
     * composed function.
     *
     * @param array $config
     * @param       $name
     * @param array $results
     */
    private static function cmpCallback(array &$config, $name, array &$results)
    {
        if (!isset($config[$name])) {
            $config[$name] = function ($v, $k) use (&$results) {
                $results[$k] = $v;
            };
        } else {
            $currentFn = $config[$name];
            $config[$name] = function ($v, $k) use (&$results, $currentFn) {
                $currentFn($v, $k);
                $results[$k] = $v;
            };
        }
    }
}


<?php
namespace Aws;

interface ConfigurationProviderInterface
{
    /**
     * Create a default config provider
     *
     * @param array $config
     * @return callable
     */
    public static function defaultProvider(array $config = []);
}


<?php
namespace Aws;

use Doctrine\Common\Cache\Cache;

class DoctrineCacheAdapter implements CacheInterface, Cache
{
    /** @var Cache */
    private $cache;

    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }

    public function get($key)
    {
        return $this->cache->fetch($key);
    }

    public function fetch($key)
    {
        return $this->get($key);
    }

    public function set($key, $value, $ttl = 0)
    {
        return $this->cache->save($key, $value, $ttl);
    }

    public function save($key, $value, $ttl = 0)
    {
        return $this->set($key, $value, $ttl);
    }

    public function remove($key)
    {
        return $this->cache->delete($key);
    }

    public function delete($key)
    {
        return $this->remove($key);
    }

    public function contains($key)
    {
        return $this->cache->contains($key);
    }

    public function getStats()
    {
        return $this->cache->getStats();
    }
}