

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

# 适用于 PHP 的 Amazon SDK 版本 3 中的亚马逊 S3 流包装器
<a name="s3-stream-wrapper"></a>

Amazon S3 Stream Wrapper 支持您使用内置 PHP 函数从 Amazon S3 存储和检索数据，这些函数包括 `file_get_contents`、`fopen`、`copy`、`rename`、`unlink`、`mkdir` 和 `rmdir`。

您需要注册 Amazon S3 Stream Wrapper 才可使用。

```
$client = new Aws\S3\S3Client([/** options **/]);

// Register the stream wrapper from an S3Client object
$client->registerStreamWrapper();
```

这样您就可以使用 `s3://` 协议来访问 Amazon S3 中存储的存储桶和对象。Amazon S3 Stream Wrapper 接受包含存储桶名称的字符串，后跟正斜杠和可选的对象键或前缀：`s3://<bucket>[/<key-or-prefix>]`。

**注意**  
Stream Wrapper 可处理您至少拥有读取权限的对象和存储桶。这就意味着，您的用户应有权针对任何需要交互的存储桶执行 `ListBucket`，并针对任何需要交互的对象执行 `GetObject`。对于不具有此级别权限的使用情形，建议您直接使用 Amazon S3 客户端操作。

## 下载数据
<a name="downloading-data"></a>

您可以使用 `file_get_contents` 获取对象内容。但使用此功能要小心，它会将对象的全部内容载入内存。

```
// Download the body of the "key" object in the "bucket" bucket
$data = file_get_contents('s3://bucket/key');
```

如果处理较大文件或需要从 Amazon S3 流式传输数据，请使用 `fopen()`。

```
// Open a stream in read-only mode
if ($stream = fopen('s3://bucket/key', 'r')) {
    // While the stream is still open
    while (!feof($stream)) {
        // Read 1,024 bytes from the stream
        echo fread($stream, 1024);
    }
    // Be sure to close the stream resource when you're done with it
    fclose($stream);
}
```

**注意**  
只有调用 `fflush` 才会返回文件写入错误。如果调用了未刷新的 `fclose`，不会返回这些错误。如果 `fclose` 关闭了流，它的返回值将为 `true`；无论其内部 `fflush` 是否会响应任何错误。如果调用 `file_put_contents` 也不会返回这些错误，这是由于 PHP 的实施方式导致的。

### 打开可搜寻的流
<a name="opening-seekable-streams"></a>

以“r”模式打开的流仅允许读取流中的数据，默认情况下不可搜寻。这样数据才能够真正以流式处理的方式从 Amazon S3 下载，之前读取的字节不需要缓冲到内存中。如果需要对流进行搜寻，可以将 `seekable` 传递到函数的[流上下文选项](http://www.php.net/manual/en/function.stream-context-create.php)。

```
$context = stream_context_create([
    's3' => ['seekable' => true]
]);

if ($stream = fopen('s3://bucket/key', 'r', false, $context)) {
    // Read bytes from the stream
    fread($stream, 1024);
    // Seek back to the beginning of the stream
    fseek($stream, 0);
    // Read the same bytes that were previously read
    fread($stream, 1024);
    fclose($stream);
}
```

“打开可搜寻的流”支持您搜寻之前读取的字节。您不可跳转到尚未从远程服务器读取的字节。要允许重新调用之前读取的数据，需使用流装饰器将数据缓冲到 PHP 临时流中。如果缓存数据量超过 2 MB，临时流中的数据会从内存传输到磁盘中。在使用 `seekable` 流上下文设置从 Amazon S3 下载大文件时，请留意这一点。

## 上传数据
<a name="uploading-data"></a>

您可以使用 `file_put_contents()` 来将数据上传到 Amazon S3。

```
file_put_contents('s3://bucket/key', 'Hello!');
```

您可以结合使用 `fopen()` 和“w”、“x”或“a”流访问模式来流式传输数据，从而上传较大的文件。Amazon S3 Stream Wrapper **不**支持同时读取和写入流（例如“r\+”、“w\+”等）。这是因为 HTTP 协议不允许同时读取和写入。

```
$stream = fopen('s3://bucket/key', 'w');
fwrite($stream, 'Hello!');
fclose($stream);
```

**注意**  
Amazon S3 需要在发送请求负载之前指定内容长度标题。因此，在 `PutObject` 操作中上传的数据会使用 PHP 临时流进行内部缓冲，直到流刷新或关闭。

**注意**  
只有调用 `fflush` 才会返回文件写入错误。如果调用了未刷新的 `fclose`，不会返回这些错误。如果 `fclose` 关闭了流，它的返回值将为 `true`；无论其内部 `fflush` 是否会响应任何错误。如果调用 `file_put_contents` 也不会返回这些错误，这是由于 PHP 的实施方式导致的。

## fopen 模式
<a name="fopen-modes"></a>

PHP 的 [fopen()](http://php.net/manual/en/function.fopen.php) 函数需要您指定 `$mode` 选项。mode 选项指定数据是否可在流中进行读写，以及打开流时文件是否必须存在。

对于以 Amazon S3 对象为目标的流，Amazon S3 Stream Wrapper 支持以下模式。


****  

|  |  | 
| --- |--- |
| r | 只读流，对象必须已存在。 | 
| w | 只可写入的流。如果对象已存在，则将覆盖该对象。 | 
| a | 只可写入的流。如果对象已存在，则会将它下载到临时流中，并且对流的任何写入操作将追加到之前上传的数据后面。 | 
| x | 只可写入的流。如果对象已存在，将引发错误。 | 

## 其他对象函数
<a name="other-object-functions"></a>

Stream Wrapper 允许多种不同的内置 PHP 函数与 Amazon S3 这样的自定义系统配合使用。Amazon S3 Stream Wrapper 允许您针对存储在 Amazon S3 中的对象执行以下函数。


****  

|  |  | 
| --- |--- |
| unlink() | 从存储桶中删除一个对象。<pre>// Delete an object from a bucket<br />unlink('s3://bucket/key');</pre><br />您可以传递 `DeleteObject` 操作的任何可用选项，修改对象的删除方式（例如指定具体的对象版本）。<pre>// Delete a specific version of an object from a bucket<br />unlink('s3://bucket/key', stream_context_create([<br />    's3' => ['VersionId' => '123']<br />]);</pre> | 
| filesize() | 获取对象的大小。<pre>// Get the Content-Length of an object<br />$size = filesize('s3://bucket/key', );</pre> | 
| is\_file() | 检查 URL 是否为文件。<pre>if (is_file('s3://bucket/key')) {<br />    echo 'It is a file!';<br />}</pre> | 
| file\_exists() | 检查某个对象是否存在。<pre>if (file_exists('s3://bucket/key')) {<br />    echo 'It exists!';<br />}</pre> | 
| filetype() | 检查 URL 是否对应于文件或存储桶 (dir)。 | 
| file() | 将对象内容加载到一些行中。您可以传递 `GetObject` 操作的任何可用选项，修改文件的下载方式。 | 
| filemtime() | 获取对象的最新修改日期。 | 
| rename() | 复制对象并删除原始版本，从而对对象重命名。您可以将 `CopyObject` 和 `DeleteObject` 操作的可用选项传递到流上下文参数，修改复制和删除对象的方式。 | 

**注意**  
虽然 `copy` 一般与 Amazon S3 Stream Wrapper 配合使用，由于 PHP 中 `copy` 函数的内部原因，某些错误可能不会正确报告。我们建议您改用 [awss3 ObjectCopier](https://docs.amazonaws.cn/aws-sdk-php/v3/api/class-Aws.S3.ObjectCopier.html) 的实例。

## 使用存储桶和文件夹
<a name="working-with-buckets"></a>

### 使用 `mkdir()` 来使用存储桶
<a name="work-with-buckets-create"></a>

您可以创建和浏览 Amazon S3 存储桶，这与 PHP 允许您在文件系统上创建和遍历目录的方式类似。

以下是创建存储桶的示例。

```
mkdir('s3://amzn-s3-demo-bucket');
```

**注意**  
2023 年 4 月，Amazon S3 自动启用了 S3 屏蔽公共访问权限，并对所有新创建的存储桶禁用了访问控制列表。此更改还会影响`StreamWrapper`的`mkdir`函数如何使用权限和 ACLs. 更多信息可参见这篇[新增内容一 Amazon 文](https://www.amazonaws.cn/about-aws/whats-new/2022/12/amazon-s3-automatically-enable-block-public-access-disable-access-control-lists-buckets-april-2023/)。

您可以使用[CreateBucket](https://docs.amazonaws.cn/aws-sdk-php/v3/api/class-Aws.S3.S3Client.html#_createBucket)操作可用的参数将流上下文选项传递给该`mkdir()`方法，以修改存储桶的创建方式。

```
// Create a bucket in the EU (Ireland) Region
mkdir('s3://amzn-s3-demo-bucket', 0500, true,
        stream_context_create([
            's3' => ['LocationConstraint' => 'eu-west-1']
        ]));
```

您可以使用 `rmdir()` 函数删除存储桶。

```
// Delete a bucket
rmdir('s3://amzn-s3-demo-bucket);
```

**注意**  
只有空存储桶才可删除。

### 使用 `mkdir()` 来使用文件夹
<a name="work-with-folders"></a>

创建存储桶后，您可以使用 `mkdir()` 来创建对象，这些对象可用作文件夹，就像在文件系统中一样。

以下代码段将名为“my-folder”的文件夹对象添加到名为“amzn-s3-demo-bucket”的现有存储桶中。使用正斜杠 (`/`) 字符将文件夹对象名称与存储桶名称和任何其他文件夹名称分开。

```
mkdir('s3://amzn-s3-demo-bucket/my-folder')
```

关于 2023 年 4 月之后权限变更的[上一个注释](#work-with-buckets-create)也会在您创建文件夹对象时发挥作用。[这篇博客文章](https://www.amazonaws.cn/blogs/aws/heads-up-amazon-s3-security-changes-are-coming-in-april-of-2023/)包含有关如何在需要时调整权限的信息。

使用 `rmdir()` 函数删除空文件夹对象，如下面的代码段所示。

```
rmdir('s3://amzn-s3-demo-bucket/my-folder')
```



### 列出存储桶的内容。
<a name="listing-the-contents-of-a-bucket"></a>

可将 [opendir()](http://www.php.net/manual/en/function.opendir.php)、[readdir()](http://www.php.net/manual/en/function.readdir.php)、[rewinddir()](http://www.php.net/manual/en/function.rewinddir.php) 和 [closedir()](http://php.net/manual/en/function.closedir.php) PHP 函数与 Amazon S3 Stream Wrapper 配合使用，遍历存储桶的内容。您可以将[ListObjects](https://docs.amazonaws.cn/aws-sdk-php/v3/api/class-Aws.S3.S3Client.html#_listObjects)操作可用的参数作为自定义流上下文选项传递给`opendir()`函数，以修改对象的列出方式。

```
$dir = "s3://bucket/";

if (is_dir($dir) && ($dh = opendir($dir))) {
    while (($file = readdir($dh)) !== false) {
        echo "filename: {$file} : filetype: " . filetype($dir . $file) . "\n";
    }
    closedir($dh);
}
```

您可以使用 PHP 递归列出存储桶中的每个对象和前缀。[RecursiveDirectoryIterator](http://php.net/manual/en/class.recursivedirectoryiterator.php)

```
$dir = 's3://bucket';
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));

foreach ($iterator as $file) {
    echo $file->getType() . ': ' . $file . "\n";
}
```

还可以使用 `Aws\recursive_dir_iterator($path, $context = null)` 函数以递归方式列出存储桶内容，这样可产生较少的 HTTP 请求。

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

$iter = Aws\recursive_dir_iterator('s3://bucket/key');
foreach ($iter as $filename) {
    echo $filename . "\n";
}
```

## 流上下文选项
<a name="id1"></a>

您可以通过传递自定义流上下文选项，自定义 Stream Wrapper 使用的客户端，或用于缓存之前加载的存储桶和键信息的缓存。

Stream Wrapper 针对每个操作支持以下流上下文选项。

** `client` **  
`Aws\AwsClientInterface` 对象，用于执行命令。

** `cache` **  
`Aws\CacheInterface` 的实例可用于缓存之前获得的文件统计数据。默认情况下，Stream Wrapper 使用内存中 LRU 缓存。