S3
S3 ストリームラッパー
S3 ストリームラッパーを使うと、PHP のファイル関数 (file_get_contents()、file_put_contents()、fopen()、 is_file()、scandir()、...) で s3://bucket/key パスを使えます。これは pieceofcake2/aws が登録する AWS SDK for PHP のストリームラッパー (Aws\S3\StreamWrapper) を AsyncAws に移植したもので、 対応範囲と制限も同じです。async-aws/simple-s3 が必要です。
'bootstrap' => true でプラグインを読み込むと、AsyncAws 設定 (他の機能と同様に Aws 設定も使えます) で ストリームラッパーが登録されます。自分で登録することもできます。
App::uses('S3StreamWrapper', 'AsyncAws.Lib');
// With the AsyncAws configuration
S3StreamWrapper::bootstrap();
// Or with a client
S3StreamWrapper::register($this->AsyncAws->createSimpleS3(['region' => 'us-east-1']));async-aws/simple-s3 がインストールされていない場合、ブートストラップは警告をログに記録し、 ストリームラッパーを登録しません。登録したら、次のように使えます。
file_put_contents('s3://my-bucket/reports/today.csv', $csv);
echo file_get_contents('s3://my-bucket/reports/today.csv');
if (is_file('s3://my-bucket/reports/today.csv')) {
echo filesize('s3://my-bucket/reports/today.csv');
}
// "today.csv", ...
foreach (scandir('s3://my-bucket/reports') as $name) {
echo $name;
}S3 オペレーションのオプションは、ストリームコンテキストで渡せます。
$context = stream_context_create([
's3' => [
'ACL' => 'private',
'ContentType' => 'text/csv',
'CacheControl' => 'max-age=3600',
'Metadata' => ['source' => 'reports'],
'ServerSideEncryption' => 'AES256',
],
]);
file_put_contents('s3://my-bucket/reports/today.csv', $csv, 0, $context);オプションは AsyncAws のオペレーション (PutObject、GetObject、CopyObject、...) に渡され、 オペレーションは知っているオプションを使います。たとえば、読み込みでは VersionId や Range、 unlink() では VersionId です。次のオプションはストリームラッパー自身が使います。
| オプション | 説明 |
|---|---|
seekable | true にすると、読み込みストリームをシーク可能にします (下記を参照)。 |
delimiter | opendir()/scandir() の区切り文字。デフォルト: /。空文字列にすると、プレフィックス以下のすべてのキーを一覧にします。 |
listFilter | opendir() 用の callable。各エントリー (Key、Size、LastModified、... または Prefix) を受け取り、一覧に含めるかどうかを返します。 |
PartSize | マルチパートアップロードのパートサイズ (MB 単位、デフォルト: 64)。async-aws/simple-s3 3.x では、1 パートより大きなボディはパートに分けてアップロードされます。2.x (PHP 8.1 でインストールされます) は、パートサイズにかかわらず 64 MB までのボディを PutObject でアップロードします。 |
次の関数をサポートしています。
| 関数 | 方法 |
|---|---|
r での fopen() | GetObject。ボディはストリーミングされます。 |
w、x での fopen() | データは php://temp バッファーに書き込まれ、ストリームがフラッシュ (fflush()) されるか閉じられたときにアップロードされます。64 MB より大きなボディはパートに分けてアップロードされます。x はオブジェクトが存在する場合 (HeadObject) に失敗します。 |
a での fopen() | オブジェクトがバッファーにダウンロードされ (オブジェクトが存在しない場合、バッファーは空で始まります)、その結果がアップロードされます。 |
fread()、fwrite()、feof()、ftell()、fseek()、fflush()、fclose()、fstat() | ストリームまたはそのバッファーに対して動作します。 |
file_exists()、is_file()、is_dir()、filesize()、filemtime()、stat()、... | HeadObject。キーがオブジェクトでない場合、オブジェクトを持つプレフィックス (ListObjectsV2) はディレクトリです。バケットと s3:// はディレクトリです。ファイルとディレクトリのモードは 0777 です。 |
unlink() | DeleteObject。 |
rename() | CopyObject (メタデータ付き) と DeleteObject。バケット内でもバケット間でも使えます。 |
copy() | オブジェクトを読み込んで書き込みます。 |
mkdir() | s3://bucket はクライアントのリージョンにバケットを作成します。s3://bucket/dir は空の dir/ オブジェクトを作成します。 |
rmdir() | s3://bucket はバケットを削除します。s3://bucket/dir は、そのプレフィックスを持つものが他にない場合に dir/ オブジェクトを削除します。 |
opendir()、readdir()、rewinddir()、closedir()、scandir() | 区切り文字を指定した ListObjectsV2 で、ページごとに取得します。dir/ オブジェクトはスキップされます。 |
制限
次の制限は AWS SDK for PHP のストリームラッパーのもので、このストリームラッパーもそれを引き継いでいます。
- サポートされるモードは
r、w、a、xだけです (r+、w+、c、... はサポートされません)。 そのため、ストリームは読み込みか書き込みのどちらかだけを行います。 - 読み込みストリーム (
r) は、seekableコンテキストオプションがtrueでない限りシークできません。trueの場合は、読み込んだバイトがphp://tempバッファーに保持され、ストリームを前後にシークできます。 - 書き込みは、書き込んでいる間ではなく、ストリームがフラッシュされるか閉じられたときにアップロードされます。
fflush()のたびにバッファー全体が再度アップロードされます。アップロードのエラーは警告を発生させますが、fclose()とfile_put_contents()はそれを報告できません。重要な場合はfflush()を呼び出し、その結果を確認してください。 aはオブジェクト全体をダウンロードし、ストリームが閉じられたときに再度アップロードします。 2 つのプロセスが同時に追記すると、互いの変更を上書きします。touch()、chmod()、chown()、flock()(およびLOCK_EXを指定したfile_put_contents())、ftruncate()はサポートされません。- ディレクトリはキーのプレフィックスでエミュレートされます。
mkdir()はrecursiveを無視します。 プレフィックスに対するrmdir()はそのdir/オブジェクトだけを削除し、プレフィックスに他のオブジェクトがある場合は失敗します。 プレフィックスにオブジェクトがまったくない場合はtrueを返します。 ACLコンテキストオプションのないmkdir()は、AWS SDK と同様にモードに応じた ACL を送信します。07xx(デフォルトの0777を含む) ではpublic-read、06xxではauthenticated-read、それ以外ではprivateです。 オブジェクト所有権の設定が "Bucket owner enforced" のバケット (新しいバケットのデフォルト) はbucket-owner-full-controlACL しか受け付けないため、そのようなバケットではこれをACLコンテキストオプションで渡してください。rename()は 1 回の CopyObject リクエストでオブジェクトをコピーするため、5 GB までのオブジェクトに限られます (AWS SDK は 5 GB を超えるとマルチパートコピーに切り替えます)。アトミックではなく、コピーの後にコピー元が削除されます。url_stat()の結果とreaddir()が一覧にしたエントリーはプロセス内にキャッシュされ (最大 1000 パス)、 ストリームラッパーがパスを変更するとクリアされます。他のプロセスがオブジェクトを変更する場合はS3StreamWrapper::clearCache()を呼び出してください。PHP のclearstatcache()ではこのキャッシュはクリアされません。- 読み込みストリームは AsyncAws を通じてボディを受け取ります。AsyncAws は受け取ったバイトのコピーを
php://tempストリームに保持します (2 MB まではメモリ、それを超えると一時ファイル)。 - エラーは
E_USER_WARNINGを発生させ、関数はfalseを返します。file_exists()、is_file()などは、 存在しないパスを警告なしで報告します。 - ストリームラッパーは
STREAM_IS_URLで登録されるため、allow_url_fopenが必要で、 パスをインクルードすること (include 's3://...') はできません。
次の点は AWS SDK と異なります。
opendir()はディレクトリを開いたときに最初の ListObjectsV2 リクエストを送信するため、バケットが存在しないとopendir()とscandir()は警告とともに失敗します。後のページでエラーが起きると、readdir()は例外を投げる代わりに 警告とともにfalseを返します。- プレフィックスに対する
rmdir()は、プレフィックスにdir/オブジェクトと他のオブジェクトがある場合にも失敗します (その場合、AWS SDK はdir/オブジェクトを削除します)。 rename()は、ACL(またはacl) コンテキストオプションが設定されている場合にだけ ACL を送信します。 AWS SDK は常に ACL (デフォルトではprivate) を送信しますが、ACL が無効なバケットはbucket-owner-full-controlしか受け付けません。aは、存在しないオブジェクト (404) だけを新しいオブジェクトとして扱います。その他のエラーではfopen()が失敗します。fstat()はファイルモード0100777を返します。async-aws/s3のクライアント (S3StreamWrapper::register(new S3Client(...))) でのアップロードは、 AWS SDK と同様に PutObject を使い、5 GB までです。async-aws/simple-s3のクライアント (デフォルト) では、 64 MB (PartSize) より大きなボディはパートに分けてアップロードされます。ContentTypeが設定されていない場合、 キーのコンテンツタイプは CakePHP の MIME タイプの一覧を使って拡張子から推測されます。
CakePHP の File クラスと Folder クラス
CakePHP 2 の File クラスと Folder クラスは、このストリームラッパーでも AWS SDK のものでも、 s3:// パスでは動作しません。Folder::isAbsolute() は、ストリームラッパーのプロトコルが英字だけの場合にしか それを認識しないため、s3://bucket/dir は相対パスとして扱われます。 英字だけのプロトコル、たとえば S3StreamWrapper::register($client, 'awss') を使えば、Folder::read()、find()、 new Folder($path, true) は動作し、File は既存のオブジェクトの読み込み、書き込み、追記、削除ができます。 File::create() が touch() を呼び出すため、File::write() と File::create() ではオブジェクトを作成できません。 オブジェクトの作成には file_put_contents() を使ってください。
S3 ストレージ
S3Storage は同じ s3://bucket/key パスを受け取り、 async-aws/simple-s3 を使って一般的なファイル操作を提供します。
App::uses('S3Storage', 'AsyncAws.Lib');
$storage = new S3Storage();
$storage->write('s3://my-bucket/reports/today.csv', $csv, ['ContentType' => 'text/csv']);
file_put_contents(TMP . 'today.csv', $storage->read('s3://my-bucket/reports/today.csv'));| メソッド | 説明 |
|---|---|
write($path, $contents, $options = []) | 文字列またはストリームをアップロードします。大きなオブジェクトはパートに分けてアップロードされます。$options は SimpleS3Client::upload() のもので、たとえば ContentType、CacheControl、ACL、Metadata です。 |
read($path) | 内容を返します。オブジェクトが存在しない場合は AsyncAws\S3\Exception\NoSuchKeyException を投げます。 |
readStream($path) | 内容のストリーム (resource) を返します。 |
exists($path) | オブジェクトが存在するかどうかを返します。 |
delete($path) | オブジェクトを削除します。 |
url($path) | オブジェクトの URL を返します。 |
presignedUrl($path, $expires = null) | 署名付きのダウンロード URL を返します。$expires は DateTimeImmutable です。 |
client() | その他の操作のために SimpleS3Client を返します。 |
new S3Storage() は AsyncAws 設定からクライアントを作成します。別のクライアントを使うには SimpleS3Client を渡します: new S3Storage($this->AsyncAws->createSimpleS3($args))。
