

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

# 在版本 3 中使用 Amazon S3 分段上 适用于 PHP 的 Amazon SDK 传
<a name="s3-multipart-upload"></a>

单独的 `PutObject` 操作可上传的对象大小上限为 5 GB。但使用分段上传方法（例如，`CreateMultipartUpload`、`UploadPart`、`CompleteMultipartUpload`、`AbortMultipartUpload`），上传对象的大小范围可以在 5 MB 到 5 TB 之间。

以下示例演示如何：
+ 使用将对象上传到 Amazon S3 [ObjectUploader](https://docs.amazonaws.cn/aws-sdk-php/v3/api/class-Aws.S3.ObjectUploader.html)。
+ 使用[MultipartUploader](https://docs.amazonaws.cn/aws-sdk-php/v3/api/class-Aws.S3.MultipartUploader.html)为 Amazon S3 对象创建分段上传。
+ 使用将对象从一个 Amazon S3 位置复制到另一个位置[ObjectCopier](https://docs.amazonaws.cn/aws-sdk-php/v3/api/class-Aws.S3.ObjectCopier.html)。

的所有示例代码都可以在[此 适用于 PHP 的 Amazon SDK 处找到 GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/php/example_code)。

## 凭据
<a name="examplecredentials"></a>

在运行示例代码之前，请配置您的 Amazon 证书，如中所述[Amazon 使用 适用于 PHP 的 Amazon SDK 版本 3 进行身份验证](credentials.md)。然后导入 适用于 PHP 的 Amazon SDK，如中所述[安装 适用于 PHP 的 Amazon SDK 版本 3](getting-started_installation.md)。

## 对象上传程序
<a name="object-uploader"></a>

如果您不确定是 `PutObject` 还是 `MultipartUploader` 最适合该任务，请使用 `ObjectUploader`。`ObjectUploader` 是使用 `PutObject` 还是 `MultipartUploader` 将大型文件上传到 Amazon S3，取决于有效载荷大小。

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

use Aws\Exception\MultipartUploadException;
use Aws\S3\MultipartUploader;
use Aws\S3\ObjectUploader;
use Aws\S3\S3Client;
```

 **示例代码** 

```
// Create an S3Client.
$s3Client = new S3Client([
    'profile' => 'default',
    'region' => 'us-east-2',
    'version' => '2006-03-01'
]);

$bucket = 'your-bucket';
$key = 'my-file.zip';

// Use a stream instead of a file path.
$source = fopen('/path/to/large/file.zip', 'rb');

$uploader = new ObjectUploader(
    $s3Client,
    $bucket,
    $key,
    $source
);

do {
    try {
        $result = $uploader->upload();
        if ($result["@metadata"]["statusCode"] == '200') {
            print('<p>File successfully uploaded to ' . $result["ObjectURL"] . '.</p>');
        }
        print($result);
        // If the SDK chooses a multipart upload, try again if there is an exception.
        // Unlike PutObject calls, multipart upload calls are not automatically retried.
    } catch (MultipartUploadException $e) {
        rewind($source);
        $uploader = new MultipartUploader($s3Client, $source, [
            'state' => $e->getState(),
        ]);
    }
} while (!isset($result));

fclose($source);
```

### 配置
<a name="object-uploader-configuration"></a>

`ObjectUploader` 对象构造函数接受以下参数：

**`$client`**  
用于执行转移的 `Aws\ClientInterface` 对象。它应是 `Aws\S3\S3Client` 的实例。

**`$bucket`**  
（`string`，*必需*）对象要上传到的存储桶名称。

**`$key`**  
（`string`，*必需*）上传对象使用的键。

**`$body`**  
（`mixed`，*必需*）要上传的对象数据。可以是 `StreamInterface`、PHP 流资源或要上传的数据字符串。

**`$acl`**  
(`string`) 上传对象设置的访问控制列表 (ACL)。默认情况下对象是私有的。

**`$options`**  
用于分段上传的配置选项的关联数组。以下配置选项有效：    
**`add_content_md5`**  
(`bool`) 设置为 true 可自动计算上传的 MD5 校验和。  
**`mup_threshold`**  
（`int`，*默认*：`int(16777216)`）文件大小的字节数。如果文件大小超过此限制，则使用分段上传。  
**`before_complete`**  
(`callable`) 在 `CompleteMultipartUpload` 操作之前调用的回调。此回调应具有类似 `function (Aws\Command $command) {...}` 的函数签名。有关可以添加到`CommandInterface`对象的参数，请参阅 [CompleteMultipartUpload API 参考](https://docs.amazonaws.cn/aws-sdk-php/v3/api/api-s3-2006-03-01.html#completemultipartupload)。  
**`before_initiate`**  
(`callable`) 在 `CreateMultipartUpload` 操作之前调用的回调。此回调应具有类似 `function (Aws\Command $command) {...}` 的函数签名。如果文件大小超过 `mup_threshold` 值，SDK 会调用此回调。有关可以添加到`CommandInterface`对象的参数，请参阅 [CreateMultipartUpload API 参考](https://docs.amazonaws.cn/aws-sdk-php/v3/api/api-s3-2006-03-01.html#createmultipartupload)。  
**`before_upload`**  
(`callable`) 在任何 `PutObject` 或 `UploadPart` 操作之前调用的回调。此回调应具有类似 `function (Aws\Command $command) {...}` 的函数签名。如果文件大小小于或等于 `mup_threshold` 值，SDK 会调用此回调。有关您可以应用于`PutObject`请求的参数，请参阅 [PutObject API 参考](https://docs.amazonaws.cn/aws-sdk-php/v3/api/api-s3-2006-03-01.html#putobject)。有关适用于`UploadPart`请求的参数，请参阅 [UploadPart API 参考](https://docs.amazonaws.cn/aws-sdk-php/v3/api/api-s3-2006-03-01.html#uploadpart)。SDK 会忽略任何不适用于 `CommandInterface` 对象所表示操作的参数。  
**`concurrency`**  
（`int`，*默认*：`int(3)`）在分段上传期间允许的并发 `UploadPart` 操作数量上限。  
**`part_size`**  
（`int`，*默认*：`int(5242880)`）进行分段上传时使用的分段大小（以字节为单位）。此值必须在 5 MB（含）和 5 GB（含）之间。  
**`state`**  
(`Aws\Multipart\UploadState`) 表示分段上传状态的对象，用于重新开始上次上传。如果提供了此选项，将忽略 `$bucket` 和 `$key` 参数以及 `part_size` 选项。  
**`params`**  
一个关联数组，用于为每个子命令提供配置选项。例如：  

```
new ObjectUploader($bucket, $key, $body, $acl, ['params' => ['CacheControl' => <some_value>])
```

## MultipartUploader
<a name="multipartuploader"></a>

分段上传可改善较大对象的上传体验。这种方法支持您将对象分成各自独立的部分，既可以按任何顺序上传，也可并行上传。

我们鼓励 Amazon S3 客户针对大于 100 MB 的对象使用分段上传。

## MultipartUploader 对象
<a name="multipartuploader-object"></a>

开发工具包中包含一个特殊的 `MultipartUploader` 对象，可简化分段上传过程。

 **导入** 

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

use Aws\Exception\MultipartUploadException;
use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
```

 **示例代码** 

```
$s3Client = new S3Client([
    'profile' => 'default',
    'region' => 'us-west-2',
    'version' => '2006-03-01'
]);

// Use multipart upload
$source = '/path/to/large/file.zip';
$uploader = new MultipartUploader($s3Client, $source, [
    'bucket' => 'your-bucket',
    'key' => 'my-file.zip',
]);

try {
    $result = $uploader->upload();
    echo "Upload complete: {$result['ObjectURL']}\n";
} catch (MultipartUploadException $e) {
    echo $e->getMessage() . "\n";
}
```

这个上传工具可根据提供的源和配置创建分段数据的生成器，并尝试上传所有分段。如果某些分段上传失败，上传工具将继续上传后面的分段，直到所有源数据均被读取。然后，上传工具重新尝试上传失败的分段，或引发包含有关无法上传的分段的信息的异常。

## 自定义分段上传
<a name="customizing-a-multipart-upload"></a>

您可以设置 `CreateMultipartUpload`、`UploadPart` 和 `CompleteMultipartUpload` 操作的自定义选项，分段上传工具可通过传递给构造函数的回调执行这些操作。

 **导入** 

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

use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
```

 **示例代码** 

```
// Create an S3Client
$s3Client = new S3Client([
    'profile' => 'default',
    'region' => 'us-west-2',
    'version' => '2006-03-01'
]);

// Customizing a multipart upload
$source = '/path/to/large/file.zip';
$uploader = new MultipartUploader($s3Client, $source, [
    'bucket' => 'your-bucket',
    'key' => 'my-file.zip',
    'before_initiate' => function (Command $command) {
        // $command is a CreateMultipartUpload operation
        $command['CacheControl'] = 'max-age=3600';
    },
    'before_upload' => function (Command $command) {
        // $command is an UploadPart operation
        $command['RequestPayer'] = 'requester';
    },
    'before_complete' => function (Command $command) {
        // $command is a CompleteMultipartUpload operation
        $command['RequestPayer'] = 'requester';
    },
]);
```

### 分段上传之间的手动垃圾回收
<a name="manual-garbage-collection-between-part-uploads"></a>

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

```
$uploader = new MultipartUploader($client, $source, [
   'bucket' => 'your-bucket',
   'key' => 'your-key',
   'before_upload' => function(\Aws\Command $command) {
      gc_collect_cycles();
   }
]);
```

## 从错误中恢复
<a name="recovering-from-errors"></a>

如果在分段上传过程中发生错误，将引发 `MultipartUploadException`。可通过此异常访问 `UploadState` 对象，其中包含分段上传的进度信息。可使用 `UploadState` 重新开始未完成的上传。

 **导入** 

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

use Aws\Exception\MultipartUploadException;
use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
```

 **示例代码** 

```
// Create an S3Client
$s3Client = new S3Client([
    'profile' => 'default',
    'region' => 'us-west-2',
    'version' => '2006-03-01'
]);

$source = '/path/to/large/file.zip';
$uploader = new MultipartUploader($s3Client, $source, [
    'bucket' => 'your-bucket',
    'key' => 'my-file.zip',
]);

//Recover from errors
do {
    try {
        $result = $uploader->upload();
    } catch (MultipartUploadException $e) {
        $uploader = new MultipartUploader($s3Client, $source, [
            'state' => $e->getState(),
        ]);
    }
} while (!isset($result));

//Abort a multipart upload if failed
try {
    $result = $uploader->upload();
} catch (MultipartUploadException $e) {
    // State contains the "Bucket", "Key", and "UploadId"
    $params = $e->getState()->getId();
    $result = $s3Client->abortMultipartUpload($params);
}
```

若通过 `UploadState` 继续上传，将尝试上传尚未上传的分段。状态对象会跟踪缺少的分段，即使这些分段是不连续的。上传工具会读取或搜寻提供的源文件，找到仍需上传的特定分段的字节范围。

 `UploadState` 对象是序列化的，因此您也可以在另一进程中重新开始上传。即使不是在处理异常，您也可以通过调用 `$uploader->getState()` 获得 `UploadState` 对象。

**重要**  
作为源传递到 `MultipartUploader` 的流在上传之前不会自动倒回。如果您在与上个示例类似的循环中使用流，而不是文件路径，请重置 `catch` 块中的 `$source` 变量。

 **导入** 

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

use Aws\Exception\MultipartUploadException;
use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
```

 **示例代码** 

```
// Create an S3Client
$s3Client = new S3Client([
    'profile' => 'default',
    'region' => 'us-west-2',
    'version' => '2006-03-01'
]);

//Using stream instead of file path
$source = fopen('/path/to/large/file.zip', 'rb');
$uploader = new MultipartUploader($s3Client, $source, [
    'bucket' => 'your-bucket',
    'key' => 'my-file.zip',
]);

do {
    try {
        $result = $uploader->upload();
    } catch (MultipartUploadException $e) {
        rewind($source);
        $uploader = new MultipartUploader($s3Client, $source, [
            'state' => $e->getState(),
        ]);
    }
} while (!isset($result));
fclose($source);
```

### 中止分段上传
<a name="aborting-a-multipart-upload"></a>

通过检索 `UploadState` 对象中包含的 `UploadId` 并将其传递给 `abortMultipartUpload` 可以中止分段上传。

```
try {
    $result = $uploader->upload();
} catch (MultipartUploadException $e) {
    // State contains the "Bucket", "Key", and "UploadId"
    $params = $e->getState()->getId();
    $result = $s3Client->abortMultipartUpload($params);
}
```

## 异步分段上传
<a name="asynchronous-multipart-uploads"></a>

在 `upload()` 上调用 `MultipartUploader` 是一个阻止请求。如果在异步环境中工作，可获得分段上传的 [Promise](guide_promises.md)。

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

use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
```

 **示例代码** 

```
// Create an S3Client
$s3Client = new S3Client([
    'profile' => 'default',
    'region' => 'us-west-2',
    'version' => '2006-03-01'
]);

$source = '/path/to/large/file.zip';
$uploader = new MultipartUploader($s3Client, $source, [
    'bucket' => 'your-bucket',
    'key' => 'my-file.zip',
]);

$promise = $uploader->promise();
```

### 配置
<a name="asynchronous-multipart-uploads-configuration"></a>

`MultipartUploader` 对象构造函数接受以下参数：

** `$client` **  
用于执行转移的 `Aws\ClientInterface` 对象。它应是 `Aws\S3\S3Client` 的实例。

** `$source` **  
上传的源数据。这可以是路径或 URL（例如，`/path/to/file.jpg`）、资源处理（例如，`fopen('/path/to/file.jpg', 'r)`）或 [PSR-7 流](https://docs.amazonaws.cn/aws-sdk-php/v3/api/class-Psr.Http.Message.StreamInterface.html)的实例。

** `$config` **  
用于分段上传的配置选项的关联数组。  
以下配置选项有效：    
** `acl` **  
(`string`) 上传对象设置的访问控制列表 (ACL)。默认情况下对象是私有的。  
** `before_complete` **  
(`callable`) 在 `CompleteMultipartUpload` 操作之前调用的回调。此回调应具有类似 `function (Aws\Command $command) {...}` 的函数签名。  
** `before_initiate` **  
(`callable`) 在 `CreateMultipartUpload` 操作之前调用的回调。此回调应具有类似 `function (Aws\Command $command) {...}` 的函数签名。  
** `before_upload` **  
(`callable`) 在任何 `UploadPart` 操作之前调用的回调。此回调应具有类似 `function (Aws\Command $command) {...}` 的函数签名。  
** `bucket` **  
（`string`，*必需*）对象要上传到的存储桶名称。  
** `concurrency` **  
（`int`，*默认*：`int(5)`）在分段上传期间允许的并发 `UploadPart` 操作数量上限。  
** `key` **  
（`string`，*必需*）上传对象使用的键。  
** `part_size` **  
（`int`，*默认*：`int(5242880)`）进行分段上传时使用的分段大小（以字节为单位）。它必须在 5 MB（含）和 5 GB（含）之间。  
** `state` **  
(`Aws\Multipart\UploadState`) 表示分段上传状态的对象，用于重新开始上次上传。如果提供了此选项，将忽略 `bucket`、`key` 和 `part_size` 选项。  
**`add_content_md5`**  
(`boolean`) 设置为 true 可自动计算上传的 MD5 校验和。  
**`params`**  
一个关联数组，用于为每个子命令提供配置选项。例如：  

```
new MultipartUploader($client, $source, ['params' => ['CacheControl' => <some_value>]])
```

## 分段副本
<a name="multipart-copies"></a>

 适用于 PHP 的 Amazon SDK 还包括一个`MultipartCopy`对象，该对象的使用方式与类似`MultipartUploader`，但设计用于在 Amazon S3 中复制大小在 5 GB 到 5 TB 之间的对象。

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

use Aws\Exception\MultipartUploadException;
use Aws\S3\MultipartCopy;
use Aws\S3\S3Client;
```

 **示例代码** 

```
// Create an S3Client
$s3Client = new S3Client([
    'profile' => 'default',
    'region' => 'us-west-2',
    'version' => '2006-03-01'
]);

//Copy objects within S3
$copier = new MultipartCopy($s3Client, '/bucket/key?versionId=foo', [
    'bucket' => 'your-bucket',
    'key' => 'my-file.zip',
]);

try {
    $result = $copier->copy();
    echo "Copy complete: {$result['ObjectURL']}\n";
} catch (MultipartUploadException $e) {
    echo $e->getMessage() . "\n";
}
```