AWS SDK for PHP(バージョン3)を使ってオブジェクトをアップロード・ダウンロードする

AWS SDK for PHP(バージョン3)を使って、S3にファイルをアップロード・ダウンロードする方法を紹介します。公式ドキュメントを見てもいいのですが、実装してハマったところなどがあったので、その辺りの情報も合わせてまとめておきたいと思います。

なお、PHPのバージョンは7.0.2を使用しました。また、サンプルのスクリプトは、コマンドライン上でキックして走らせることを想定しています。

準備: バケット・ユーザ・ポリシーを作成

AWS SDK for PHPを使う前に、S3にアクセスできるユーザの作成や、ポリシーの作成などを実施します。この辺りがわかる方は、この準備関連は飛ばしていただければと思います。

S3のバケットを作成

今回のサンプルでオブジェクトを保存する、S3のバケットを作成します。この記事では、「remotestance-test」という名前のバケットを作成していますが、この名前は適当に設定してください。

バケット作成手順

  1. S3のコンソールで「Create Bucket」をクリックします。
  2. バケット名を入力し、「Create」をクリックします。

バケット作成

S3にアクセスするユーザを作成

rootアカウントを使うのはセキュリティ的によろしくないので、今回のテストに使うためだけのユーザを作成します。なお、ユーザは「dummy」という名前で作成します。

ユーザ作成手順

  1. IAMコンソールで「Create New User」をクリックします。
  2. 「Enter User Names:」に「dummy」と入力し、「Generate an access key for each user」にチェックが入っていることを確認し、「Create」をクリックします。
  3. 「Download Credentials」をクリックし、「credentials.csv」をダウンロードします。
  4. 「close」で作成画面を閉じます。

ユーザ作成1

ユーザ作成2

ユーザ作成3

ポリシーの作成

作成したユーザに、作成したバケットにアクセスするためのポリシーを追加します。

なお、下記例ではバケット「remotestance-test」に対してアクセスできるように設定しています。この「remotestance-test」の部分は、作成されたバケット名に変更してください。

ポリシー設定手順

  1. 「dummy」アカウントの「Permissions」タブを開き、「Inline Policies」の「click here」をクリックします。
  2. 「Custom Policy」をチェックし、「Select」をクリックします。
  3. 「Policy Name」を適当に入れ、「Policy Document」を下記のように入力します。

ポリシー設定1

ポリシー設定2

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::remotestance-test/*"
            ]
        }
    ]
}

これで、テスト用に作成したユーザ「dummy」が、「remotestance-test」バケットにアクセスできるようになりました。正確には、オブジェクトのダウンロード(s3:GetObject)と、アップロード(S3:PutObject)ができるようになりました。

credentialsの設定

次は、PHPを実行してAWSにアクセスしたときに、作成したユーザ「dummy」の権限で操作できるようにします。この作業はPHPを実行するマシンの、./.aws/credentialsを新規作成、または編集して行います。

$ vim ~/.aws/credentials

このファイルの中身に、「dummy」というプロファイルを追加します。なお、この記事では「dummy」と名前を付けましたが、AWSのコンソール上で作成した名前と一致させなくても問題ありません。

[dummy]
aws_access_key_id = ACCESS_KEY_ID
aws_secret_access_key = SECRET_ACCESS_KEY

上記プロファイルの「ACCESS_KEY_ID」と「SECRET_ACCESS_KEY」は、ユーザ作成時にダウンロードした、CSVファイルに記述されている内容を入力します。

$ cat ~/Downloads/credentials.csv
User Name,Access Key Id,Secret Access Key
"dummy",ACCESS_KEY_ID,SECRET_ACCESS_KEY

準備: AWS SDK for PHPを入手

PHPからS3にアクセスするために、AWS SDK for PHPを導入します。そのために、ドキュメントの「Installing via Phar」を元にして、phar形式のファイルを入手します。

ドキュメントを開き、「download the packaged phar」のリンク先を保存します。そして、それを適当な場所に保存します。

wgetを使って、以下のように手に入れてもいいでしょう。

$ wget http://docs.aws.amazon.com/aws-sdk-php/v3/download/aws.phar /path/to/aws.phar

phar形式のファイルを入手したら、以下のようにS3接続用のクライアントを作成できるようになります。

<?php
require '/path/to/aws.phar';

// S3に接続するためのクライアントを用意します。
$sdk = new Aws\Sdk([
    'profile' => 'dummy',
    'version' => 'latest',
    'region'  => 'ap-northeast-1'
]);
$client = $sdk->createS3();

これで、準備が完了しました!

オブジェクトのアップロード・ダウンロード

S3にオブジェクトをアップロードするときは、putObjectというメソッドを使います。また、ダウンロードはgetObjectメソッドを使います。

以下のコードは、putObjectメソッドを使い、バケット「remotestance-test」のキー「test/test1.txt」に、「Test」という内容のオブジェクトを保存しています。

また、putObjectで保存できたことを確認するため、getObjectでオブジェクトを取得し直し、その中身を表示しています。

<?php
require '/path/to/aws.phar';

// S3に接続するためのクライアントを用意します。
$sdk = new Aws\Sdk([
    'profile' => 'dummy',
    'version' => 'latest',
    'region'  => 'ap-northeast-1'
]);
$client = $sdk->createS3();

$bucket_name = 'remotestance-test';
$key_name = 'test/test1.txt';

$result = $client->putObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name,
    'Body' => 'Test'
]);

$obj = $client->getObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name
]);

print('Body: ' . $obj['Body'] . "\n");

このファイル(名前をsample1.phpとします)を実行すると、「Body: Test」と表示されます。

php sample1.php
Body: Test

ContentTypeに関する注意点とその解決方法

注意点

これは、実際に私がハマったことです。。。上記コードはContentTypeを指定していません。そのため、オブジェクトは「application/octet-stream」で作成されます。

そうなると、例えばCloudFrontで画像を配信する場合に、MIMEタイプが「image/png」ではないから画像が表示されないなどのトラブルが起こりえます。

解決方法

この問題は、putObjectでContentTypeを指定すれば解決します。また、この記事のうしろの方で扱う、SourceFileを指定してもContentTypeを設定することができました。

putObjectメソッドで、以下のようにContentTypeを指定することができます。

$result = $client->putObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name,
    'Body' => 'Test',
    'ContentType' => 'text/plain'  // ContentTypeを明示的に指定します。
]);

なお、ContentTypeはS3のコンソール上や、getObjectのレスポンスなどから確認することができます。

$obj = $client->getObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name
]);

print('ContentType: ' . $obj['ContentType'] . "\n");  // 「ContentType: text/plain」と表示されます。

画像をアップロード・ダウンロード

先ほどは、テキスト形式のオブジェクトをアップロード・ダウンロードしました。これと同様に、画像をアップロード・ダウンロードすることも可能です。

なお、下記コードではContentTypeの判別に、mime_content_type関数を利用しています。この関数の引数に、調べたいファイルのパスを指定すると、image/pngなどの文字列を取得することができます。

<?php
require '/path/to/aws.phar';

// S3に接続するためのクライアントを用意します。
$sdk = new Aws\Sdk([
    'profile' => 'dummy',
    'version' => 'latest',
    'region'  => 'ap-northeast-1'
]);
$client = $sdk->createS3();

$bucket_name = 'remotestance-test';
$key_name = 'test/image1.png';
$filepath = './image1.png';
$mimetype = mime_content_type($filepath);

// 画像のデータを取得します。
$data = file_get_contents($filepath);

$result = $client->putObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name,
    'Body' => $data,
    'ContentType' => $mimetype
]);

$topath = './out/image1.png';
$obj = $client->getObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name
]);

// 画像をファイルに書き込みます。
$fp = fopen($topath, 'w') or die('fopenに失敗しました。');
fwrite($fp, $obj['Body']);

putObjectにSourceFileを指定

先ほどのコードで画像をアップロードするときは、$data = file_get_contents($filepath);で画像データを取得し、putObjectメソッドの引数でBodyに指定していました。この部分は、putObjectの引数でSourceFileを指定すると省略することができます。

<?php
require '/path/to/aws.phar';

// S3に接続するためのクライアントを用意します。
$sdk = new Aws\Sdk([
    'profile' => 'dummy',
    'version' => 'latest',
    'region'  => 'ap-northeast-1'
]);
$client = $sdk->createS3();

$bucket_name = 'remotestance-test';
$key_name = 'test/image2.png';
$filepath = './image2.png';
$mimetype = mime_content_type($filepath);

// SourceFileを指定して、画像をアップロードします。
$result = $client->putObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name,
    // 'ContentType' => $mimetype,
    'SourceFile' => $filepath
]);

ちなみに、このコードを試した結果、SourceFileを指定した場合は、ContentTypeを指定しなくても、アップロードされたオブジェクトのContentTypeは「image/png」になっていました。ただ、ContentTypeの指定漏れを防ぐためにも、省略しないほうが無難だと思います。(普段からContentTypeを省略していると、SourceFileを指定しない場合にContentTypeを指定し忘れる危険性があるかと思います。)

getObjectにSaveAsを指定

getObjectにSaveAsを指定すると、取得したオブジェクトを、SaveAsで指定したパスに保存することができます。

$topath = './out/image1.png';
$obj = $client->getObject([
    'Bucket' => $bucket_name,
    'Key' => $key_name,
    'SaveAs' => $topath
]);

getObjectで指定したオブジェクトが存在しない場合

getObjectメソッドを呼んだ時に、キーで指定したオブジェクトが存在しない場合は、S3Exceptionが投げられます。その、キャッチした例外オブジェクトのgetStatusCodeを呼ぶと、ステータスコードを取得できます。

<?php
require '/path/to/aws.phar';
use Aws\S3\Exception\S3Exception;

// S3に接続するためのクライアントを用意します。
$sdk = new Aws\Sdk([
    'profile' => 'dummy',
    'version' => 'latest',
    'region'  => 'ap-northeast-1'
]);
$client = $sdk->createS3();

$bucket_name = 'remotestance-test';
$key_name = 'test/notfound.png';

try {
    $result = $client->getObject([
        'Bucket' => $bucket_name,
        'Key' => $key_name
    ]);
} catch (S3Exception $e) {
    $status_code = $e->getStatusCode();
    echo "status_code: " . $status_code . "\n";
    echo "type: " . gettype($status_code) . "\n";
}

上記コードを実行し、test/notfound.pngが存在しない場合は、以下のように表示されます。

status_code: 403
type: integer

ポリシーによってはステータスコードが403ではなく、404になっている場合があります。これについては、次に説明します。

403ではなく404になってほしい場合

この記事の準備では、以下のようにポリシーを設定しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::remotestance-test/*"
            ]
        }
    ]
}

このポリシーだと、オブジェクトが存在しない場合のステーコードは403 Forbiddenになっていました。なぜ、404ではなく403かというと、GetObjectだけだと、バケットにオブジェクトが存在するかがわからないからだと思われます。

なので、ListBucketを追加して、バケットのオブジェクトを一覧できるようポリシーを変更してあげます。そうすれば、403ではなく404が返ってくるようになりました。

ListBucketの追加は、以下のようにポリシーを変更すればできます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::remotestance-test/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::remotestance-test"
            ]
        }
    ]
}

また、以下のようにまとめて書くこともできます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::remotestance-test",
                "arn:aws:s3:::remotestance-test/*"
            ]
        }
    ]
}

この記事が役に立った場合、シェアしていただけると励みになります!!