

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 适用于 PHP 的 AWS SDK 版本 3 中的命令对象
<a name="guide_commands"></a>

 适用于 PHP 的 AWS SDK 使用[命令模式](http://en.wikipedia.org/wiki/Command_pattern)封装稍后用于传输 HTTP 请求的参数和处理程序。

## 隐式使用命令
<a name="implicit-use-of-commands"></a>

如果您检查任何客户端类，就会发现对应于 API 操作的方法实际上并不存在。它们是使用 `__call()` 魔术方法实施。这些虚拟方法实际上是封装开发工具包对命令对象的使用的快捷方式。

您通常不需要直接与命令对象交互。当您调用类似于 `Aws\S3\S3Client::putObject()` 的方法时，开发工具包实际上会根据所提供的参数创建 `Aws\CommandInterface` 对象，执行命令，并返回填充的 `Aws\ResultInterface` 对象（或针对错误引发异常）。调用客户端的任意 `Async` 方法（例如 `Aws\S3\S3Client::putObjectAsync()`）时，会发生类似流程：客户端根据所提供的参数创建一个命令，序列化 HTTP 请求，发起请求，并返回 Promise。

以下示例在功能上等效。

```
$s3Client = new Aws\S3\S3Client([
    'version' => '2006-03-01',
    'region'  => 'us-standard'
]);

$params = [
    'Bucket' => 'amzn-s3-demo-bucket',
    'Key'    => 'baz',
    'Body'   => 'bar'
];

// Using operation methods creates a command implicitly
$result = $s3Client->putObject($params);

// Using commands explicitly
$command = $s3Client->getCommand('PutObject', $params);
$result = $s3Client->execute($command);
```

## 命令参数
<a name="command-parameters"></a>

所有命令均支持几个特殊参数，这些参数不属于服务的 API，但可以控制开发工具包的行为。

### `@http`
<a name="http"></a>

使用此参数可以微调基础 HTTP 处理程序执行请求的方式。可包含在 `@http` 参数中的选项与您在使用[“http”客户端选项](guide_configuration.md#config-http)对客户端进行实例化时可设置的选项相同。

```
// Configures the command to be delayed by 500 milliseconds
$command['@http'] = [
    'delay' => 500,
];
```

### `@retries`
<a name="retries"></a>

与[“retries”客户端选项](guide_configuration.md#config-retries)类似，`@retries` 控制一个命令在被视为已失败之前可以重试的次数。将其设置为 `0` 可禁用重试。

```
// Disable retries
$command['@retries'] = 0;
```

**注意**  
如果您已对客户端禁用重试，则无法选择性地对传递给该客户端的各个命令启用重试。

## 创建命令对象
<a name="creating-command-objects"></a>

您可以使用客户端的 `getCommand()` 方法创建命令。它不会立即执行或传输 HTTP 请求，而是仅在传递到客户端的 `execute()` 方法时才执行。这样一来，您有机会在执行命令之前修改命令对象。

```
$command = $s3Client->getCommand('ListObjects');
$command['MaxKeys'] = 50;
$command['Prefix'] = 'foo/baz/';
$result = $s3Client->execute($command);

// You can also modify parameters
$command = $s3Client->getCommand('ListObjects', [
    'MaxKeys' => 50,
    'Prefix'  => 'foo/baz/',
]);
$command['MaxKeys'] = 100;
$result = $s3Client->execute($command);
```

## 命令 `HandlerList`
<a name="command-handlerlist"></a>

从客户端创建命令后，该命令将得到客户端 `Aws\HandlerList` 对象的一个克隆。该命令将得到客户端处理程序列表的一个**克隆**，以允许命令使用不影响客户端执行的其他命令的自定义中间件和处理程序。

这意味着您可以对每条命令使用不同的 HTTP 客户端（例如 `Aws\MockHandler`），并通过中间件对每条命令添加自定义行为。以下示例使用 `MockHandler` 创建模拟结果，而不是发送实际 HTTP 请求。

```
use Aws\Result;
use Aws\MockHandler;

// Create a mock handler
$mock = new MockHandler();
// Enqueue a mock result to the handler
$mock->append(new Result(['foo' => 'bar']));
// Create a "ListObjects" command
$command = $s3Client->getCommand('ListObjects');
// Associate the mock handler with the command
$command->getHandlerList()->setHandler($mock);
// Executing the command will use the mock handler, which returns the
// mocked result object
$result = $client->execute($command);

echo $result['foo']; // Outputs 'bar'
```

除了更改命令所使用的处理程序外，您还可以将自定义中间件注入该命令。以下示例使用 `tap` 中间件，其作用是充当处理程序列表中的观察者。

```
use Aws\CommandInterface;
use Aws\Middleware;
use Psr\Http\Message\RequestInterface;

$command = $s3Client->getCommand('ListObjects');
$list = $command->getHandlerList();

// Create a middleware that just dumps the command and request that is
// about to be sent
$middleware = Middleware::tap(
    function (CommandInterface $command, RequestInterface $request) {
        var_dump($command->toArray());
        var_dump($request);
    }
);

// Append the middleware to the "sign" step of the handler list. The sign
// step is the last step before transferring an HTTP request.
$list->append('sign', $middleware);

// Now transfer the command and see the var_dump data
$s3Client->execute($command);
```

## `CommandPool`
<a name="command-pool"></a>

`Aws\CommandPool` 使您能够使用生成 `Aws\CommandInterface` 对象的迭代器并发执行命令。`CommandPool` 确保并发执行的命令数量保持恒定，同时迭代池中的命令（当命令完成时，会执行更多命令，以确保恒定的池大小）。

下面是使用 `CommandPool` 发送几条命令的简单示例。

```
use Aws\S3\S3Client;
use Aws\CommandPool;

// Create the client
$client = new S3Client([
    'region'  => 'us-standard',
    'version' => '2006-03-01'
]);

$bucket = 'amzn-s3-demo-bucket';
$commands = [
    $client->getCommand('HeadObject', ['Bucket' => $bucket, 'Key' => 'a']),
    $client->getCommand('HeadObject', ['Bucket' => $bucket, 'Key' => 'b']),
    $client->getCommand('HeadObject', ['Bucket' => $bucket, 'Key' => 'c'])
];

$pool = new CommandPool($client, $commands);

// Initiate the pool transfers
$promise = $pool->promise();

// Force the pool to complete synchronously
$promise->wait();
```

该示例对于 `CommandPool` 显得非常苍白无力。我们来试试更复杂的示例。假设您要将磁盘上的文件上传到 Amazon S3 存储桶。要获取磁盘文件的列表，我们可以使用 PHP 的 `DirectoryIterator`。此迭代器生成 `SplFileInfo` 对象。`CommandPool` 接受生成 `Aws\CommandInterface` 对象的迭代器，因此我们将映射 `SplFileInfo` 对象以返回 `Aws\CommandInterface` 对象。

```
<?php
require 'vendor/autoload.php';

use Aws\Exception\AwsException;
use Aws\S3\S3Client;
use Aws\CommandPool;
use Aws\CommandInterface;
use Aws\ResultInterface;
use GuzzleHttp\Promise\PromiseInterface;

// Create the client
$client = new S3Client([
    'region'  => 'us-standard',
    'version' => '2006-03-01'
]);

$fromDir = '/path/to/dir';
$toBucket = 'amzn-s3-demo-bucket';

// Create an iterator that yields files from a directory
$files = new DirectoryIterator($fromDir);

// Create a generator that converts the SplFileInfo objects into
// Aws\CommandInterface objects. This generator accepts the iterator that
// yields files and the name of the bucket to upload the files to.
$commandGenerator = function (\Iterator $files, $bucket) use ($client) {
    foreach ($files as $file) {
        // Skip "." and ".." files
        if ($file->isDot()) {
            continue;
        }
        $filename = $file->getPath() . '/' . $file->getFilename();
        // Yield a command that is executed by the pool
        yield $client->getCommand('PutObject', [
            'Bucket' => $bucket,
            'Key'    => $file->getBaseName(),
            'Body'   => fopen($filename, 'r')
        ]);
    }
};

// Now create the generator using the files iterator
$commands = $commandGenerator($files, $toBucket);

// Create a pool and provide an optional array of configuration
$pool = new CommandPool($client, $commands, [
    // Only send 5 files at a time (this is set to 25 by default)
    'concurrency' => 5,
    // Invoke this function before executing each command
    'before' => function (CommandInterface $cmd, $iterKey) {
        echo "About to send {$iterKey}: "
            . print_r($cmd->toArray(), true) . "\n";
    },
    // Invoke this function for each successful transfer
    'fulfilled' => function (
        ResultInterface $result,
        $iterKey,
        PromiseInterface $aggregatePromise
    ) {
        echo "Completed {$iterKey}: {$result}\n";
    },
    // Invoke this function for each failed transfer
    'rejected' => function (
        AwsException $reason,
        $iterKey,
        PromiseInterface $aggregatePromise
    ) {
        echo "Failed {$iterKey}: {$reason}\n";
    },
]);

// Initiate the pool transfers
$promise = $pool->promise();

// Force the pool to complete synchronously
$promise->wait();

// Or you can chain the calls off of the pool
$promise->then(function() { echo "Done\n"; });
```

### `CommandPool` 配置
<a name="commandpool-configuration"></a>

`Aws\CommandPool` 构造函数接受各种配置选项。

**concurrency（可调用\$1整数）**  
并发执行的命令的最大数量。提供一个函数来动态调整池大小。该函数将获得当前待处理请求数，并且预计会返回一个表示新池大小限制的整数。

**before（可调用）**  
发送每个命令之前要调用的函数。`before` 函数接受命令和该命令的迭代器密钥。您可以在发送命令之前根据需要更改 `before` 函数中的命令。

**fulfilled（可调用）**  
执行 Promise 时要调用的函数。此函数将获得结果对象、生成该结果的迭代器的 ID，以及可解析或拒绝的聚合 Promise（如果您需要让池短路）。

**rejected（可调用）**  
拒绝 Promise 时要调用的函数。此函数将获得 `Aws\Exception` 对象、生成该异常的迭代器的 ID，以及可解析或拒绝的聚合 Promise（如果您需要让池短路）。

### 命令之间的手动垃圾回收
<a name="manual-garbage-collection-between-commands"></a>

如果您达到大型命令池的内存限制，这可能是由于在达到您的内存限制时开发工具包生成的、但尚未由 [PHP 垃圾回收器](https://www.php.net/manual/en/features.gc.php)收集的循环引用导致的。在命令之间手动调用收集算法可允许在达到该限制之前收集循环。以下示例将创建一个 `CommandPool`，该池将在发送每个命令之前使用一个回调来调用收集算法。请注意，调用垃圾回收器会降低性能，最佳用法将取决于您的使用案例和环境。

```
$pool = new CommandPool($client, $commands, [
    'concurrency' => 25,
    'before' => function (CommandInterface $cmd, $iterKey) {
        gc_collect_cycles();
    }
]);
```