

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 第 3 適用於 PHP 的 AWS SDK 版中的命令物件
<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>

如同[「重試」用戶端選項](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` 物件。該命令會獲得用戶端處理器清單的一個**複製**，允許命令使用不影響用戶端所執行其他命令的自訂中介軟體和處理器。

這表示您可以對每個命令 (例如 `Aws\MockHandler`) 使用不同的 HTTP 用戶端，並透過中介軟體為每個命令新增自訂行為。以下範例使用 `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` 建構函式接受各種組態選項。

**並行 (可呼叫\$1int)**  
同時執行的最大命令數。提供一個動態調整集區規模的函數。此函數提供目前等待中的請求數量，並且預計會傳回一個代表新集區規模限制的整數。

**之前 (可呼叫)**  
在傳送每個命令之前呼叫的函數。`before` 函數接受該命令之反覆運算器的命令和金鑰。傳送命令之前，可以視需要在 `before` 函數中改變命令。

**已履行 (可呼叫)**  
promise 履行時呼叫的函數。該函數提供結果物件，產生結果的反覆運算器 ID，以及如果需要將集區短路時可以解決或拒絕的彙總 promise。

**拒絕 (可呼叫)**  
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();
    }
]);
```