aws-sdk-phpでS3にファイルをアップロードするハンズオン
概要
PHP から Amazon S3 にファイルをアップロードする方法を、公式の aws-sdk-php を使って最小構成から解説します。単純な putObject だけでなく、大きなファイル向けの MultipartUploader、署名付きURL(Presigned URL)によるブラウザ直アップロード、よくあるエラーの切り分けまで、実務でそのまま使える範囲をハンズオン形式でまとめました。
この記事のゴールは次の4つです。
- SDK をインストールして
S3Clientを初期化する - ローカルファイルを
putObjectでアップロードする - 大きなファイルを
MultipartUploaderで分割アップロードする - 署名付きURLでアップロード/ダウンロードする
環境
- PHP 8.1 以上
- Composer
- aws-sdk-php 3.x
- AWS アカウント(S3 バケット作成済み)
- 認証情報(
~/.aws/credentialsのプロファイル、または IAM ロール)
composer require aws/aws-sdk-php
本記事ではリージョン ap-northeast-1、バケット名 example-upload-bucket を仮の値として進めます。実際の値に置き換えてください。
認証情報の扱い
SDK は以下の順で認証情報を探します(Credential Provider Chain)。
- 明示的に渡したキー
- 環境変数
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY ~/.aws/credentialsのプロファイル- ECS / EC2 / Lambda のロール
本番環境ではコードや環境変数にキーを直書きせず、IAM ロールを使うのが基本です。ローカル開発では名前付きプロファイルが扱いやすいでしょう。本記事のコードはキーを一切ハードコードしません。
必要な IAM 権限は最小で以下です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::example-upload-bucket/*"
}
]
}
S3Client の初期化
<?php
require 'vendor/autoload.php';
use Aws\S3\S3Client;
$s3 = new S3Client([
'region' => 'ap-northeast-1',
'version' => 'latest',
]);
version は latest で問題ありません。region は必須で、バケットのリージョンと一致させます。不一致だと PermanentRedirect エラーになります。
基本のアップロード(putObject)
ローカルの ./sample.pdf をアップロードする最小例です。
<?php
use Aws\Exception\AwsException;
$bucket = 'example-upload-bucket';
$key = 'uploads/sample.pdf';
try {
$result = $s3->putObject([
'Bucket' => $bucket,
'Key' => $key,
'SourceFile' => './sample.pdf',
'ContentType' => 'application/pdf',
]);
echo "Uploaded: " . $result['ObjectURL'] . PHP_EOL;
} catch (AwsException $e) {
echo "Error: " . $e->getAwsErrorCode() . PHP_EOL;
echo $e->getAwsErrorMessage() . PHP_EOL;
}
ポイントは次の通りです。
SourceFileはファイルパスを渡します。メモリ上のデータを送るときはBodyを使います。ContentTypeは明示するのが安全です。省略するとダウンロード時に意図しない挙動になることがあります。putObjectは内部で全量をメモリに載せうるため、巨大ファイルでは後述のMultipartUploaderを使います。
メモリ上のデータやストリームを渡す例です。
// 文字列をそのまま
$s3->putObject([
'Bucket' => $bucket,
'Key' => 'uploads/note.txt',
'Body' => "hello s3\n",
'ContentType' => 'text/plain',
]);
// ストリーム
$stream = fopen('./large.zip', 'rb');
$s3->putObject([
'Bucket' => $bucket,
'Key' => 'uploads/large.zip',
'Body' => $stream,
'ContentType' => 'application/zip',
]);
大きなファイルの分割アップロード(MultipartUploader)
数百MB〜GB級のファイルは MultipartUploader を使います。途中失敗時の中断やパート単位のリトライができ、メモリ消費も抑えられます。
<?php
use Aws\S3\MultipartUploader;
use Aws\Exception\MultipartUploadException;
$source = './big-video.mp4';
$uploader = new MultipartUploader($s3, $source, [
'bucket' => 'example-upload-bucket',
'key' => 'uploads/big-video.mp4',
'part_size' => 10 * 1024 * 1024, // 10MB(最小5MB)
]);
try {
$result = $uploader->upload();
echo "Done: " . $result['ObjectURL'] . PHP_EOL;
} catch (MultipartUploadException $e) {
// 直前の進捗から再開
$uploader = new MultipartUploader($s3, $source, [
'state' => $e->getState(),
]);
$result = $uploader->upload();
}
part_size は 5MB 以上、パート数は最大 10,000 です。巨大ファイルでパート数が上限に当たる場合は part_size を上げます。
署名付きURL(Presigned URL)
サーバーを経由せず、ブラウザやクライアントから直接 S3 にアップロード/ダウンロードさせたいときに使います。サーバーは期限付きの許可証だけを発行します。
ダウンロード用です。
$cmd = $s3->getCommand('GetObject', [
'Bucket' => $bucket,
'Key' => 'uploads/sample.pdf',
]);
$url = (string) $s3->createPresignedRequest($cmd, '+10 minutes')->getUri();
アップロード用(PUT)です。
$cmd = $s3->getCommand('PutObject', [
'Bucket' => $bucket,
'Key' => 'uploads/from-browser.png',
'ContentType' => 'image/png',
]);
$uploadUrl = (string) $s3->createPresignedRequest($cmd, '+15 minutes')->getUri();
クライアント側はこの URL に対して PUT でファイルを送るだけです。
await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": "image/png" },
body: file,
});
署名時に指定したヘッダ(ContentType など)は、クライアント側のリクエストヘッダと一致させる必要があります。一致しないと SignatureDoesNotMatch になります。
確認方法
aws s3 ls s3://example-upload-bucket/uploads/ --region ap-northeast-1 aws s3api head-object \ --bucket example-upload-bucket \ --key uploads/sample.pdf \ --region ap-northeast-1
SDK からも確認できます。
$head = $s3->headObject(['Bucket' => $bucket, 'Key' => $key]); echo $head['ContentLength'] . " bytes, " . $head['ContentType'] . PHP_EOL;
よくあるエラーと切り分け
| エラーコード | 主な原因 | 対処 |
|---|---|---|
AccessDenied |
IAM 権限不足 / バケットポリシー | 権限と Resource ARN を確認 |
PermanentRedirect |
region 不一致 | バケットのリージョンに合わせる |
SignatureDoesNotMatch |
ヘッダ不一致 / 時刻ずれ | ヘッダ一致・時刻同期 |
NoSuchBucket |
バケット名のタイプミス | 名前・リージョンを再確認 |
EntityTooLarge |
単発 PUT 5GB 超 | MultipartUploader に切替 |
RequestTimeTooSkewed |
端末の時刻ずれ | NTP 同期 |
SDK のエラーは AwsException で捕捉し、getAwsErrorCode() でコードを確認するのが切り分けの第一歩です。
注意点
- キーの直書きはしない。IAM ロールまたはプロファイルを使う。
ContentTypeを明示する。- 単発 PUT は 5GB が上限。超える・不安定回線なら
MultipartUploader。 - 署名付きURLの有効期限は用途に足りる最小にする。
- 失敗したマルチパートアップロードは課金対象のまま残るため、S3 のライフサイクルルールで「未完了マルチパートを N 日後に削除」を設定しておくと安全です。
まとめ
- 小さいファイルは
putObject、大きいファイルはMultipartUploader。 - クライアント直アップロード/配布は署名付きURLでサーバー負荷と転送を減らせます。
- 認証情報は Provider Chain に任せ、キーを持たない設計にします。
- エラーは
getAwsErrorCode()を起点に、region・権限・ヘッダ・時刻の4点で切り分けます。
この4パターンを押さえておけば、PHP アプリから S3 を扱うほとんどのケースに対応できます。
