aws-sdk-phpでS3にファイルをアップロードするハンズオン

概要

PHP から Amazon S3 にファイルをアップロードする方法を、公式の aws-sdk-php を使って最小構成から解説します。単純な putObject だけでなく、大きなファイル向けの MultipartUploader、署名付きURL(Presigned URL)によるブラウザ直アップロード、よくあるエラーの切り分けまで、実務でそのまま使える範囲をハンズオン形式でまとめました。

この記事のゴールは次の4つです。

  1. SDK をインストールして S3Client を初期化する
  2. ローカルファイルを putObject でアップロードする
  3. 大きなファイルを MultipartUploader で分割アップロードする
  4. 署名付き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)。

  1. 明示的に渡したキー
  2. 環境変数 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
  3. ~/.aws/credentials のプロファイル
  4. 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 を扱うほとんどのケースに対応できます。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。