移行ガイド

このガイドでは、最新のAWS SDK for PHP を使うよう既存のコードを移行する方法、 および旧SDK Version 1 との違いを解説します。

はじめに

PHPの言語やコミュニティはここ数年で著しい成長を遂げました。 AWS SDK for PHPの登場以降、PHPには大きな改版が2度 (PHP 5.3、PHP 5.4) あった他、 PHPコミュニティの多くが、 PHP Framework Interop Group の勧告のもとに統合されています。 そこで、PHPコミュニティで使われている、より現代的なパターンに沿って、 SDKにも大きな変更を施すことにしました。

今回のリリースでは、顧客から寄せられる多くの要望に応じるべく、SDKを白紙の状態から記述し直しました。 新しいSDKは Guzzle HTTPクライアントフレームワーク を基盤としているため、 処理性能が向上し、イベント駆動のカスタマイズも可能になっています。 さらに、高レベルの抽象化を施した結果、よく必要になるタスクのプログラミングが容易になりました。 SDKはPHP 5.3.3以降で動作し、名前空間やオートロードに関するPSR-0規約に従っています。

サポート対象のサービス

AWS SDK for PHPには、 Version 1にあったAWSサービスすべての他、 Amazon Route 53、Amazon Glacier、AWS Direct Connectなどが加わっています。 対応済みのサービス一覧が AWS SDK for PHPのウェブサイト に載っています。 最新の情報や変更事項については、 AWS SDK for PHP 2 GitHubリポジトリ を参照してください。

新しい特徴

  • PHP 5.3の名前空間
  • PSR-0、PSR-1、PSR-2規約 に準拠
  • Guzzle を基盤とし、その各機能を活用
  • 永続接続管理 (直列要求/並列要求とも)
  • (Symfony2 EventDispatcher を介した) イベントフックにより、 イベント駆動の独自動作を実装可能
  • 要求/応答エンティティボディーを php://temp ストリームに格納してメモリ消費量を削減
  • 一時的にネットワーク障害が起こり、cURLの処理に失敗したとき、べき乗打ち切り待機法に基づき自動的に再試行
  • 回線越しのログや応答キャッシュを扱うプラグイン
  • 所定の状態になるまでリソースに対してポーリングを行う「Waiter」オブジェクト
  • ページ分割された応答に対して容易に反復処理ができる、リソースイテレータオブジェクト
  • 個々のサービスに対して定義した一連の例外
  • 使いやすいインタフェイスを備える、モデル化した応答
  • サービスパラメータオプションに用いる一連の定数 (列挙子)
  • 柔軟な要求バッチ処理システム
  • 設定が容易で「依存性の注入」パターンにも対応したサービスビルダ/コンテナ
  • 完全なユニットテストスイート (包括的なコードカバレージを含む)
  • Composer によるSDKのインストール/オートロード (PSR-0規約に準拠)
  • Phingbuild.xml: 開発ツールのインストール、テストの実行、 .phar ファイルの生成
  • 高速Amazon DynamoDBバッチPutItem/DeleteItemシステム
  • Amazon Simple Storage Service (Amazon S3) やAmazon Glacierで用いるマルチパート型のアップロードシステム (一時停止/続行が可能)
  • DynamoDB Session Handlerを再設計し、高度な書き込み処理とガベージコレクション機能を実装
  • マルチリージョン機能の改善

旧版との違い

アーキテクチャ

新しいSDKは Guzzle を基盤とし、その機能や規約を引き継いでいます。 AWSサービスクライアントはいずれも、サービス記述ファイルを使ってオペレーションを定義することにより、Guzzleクライアントを拡張したものです。 SDKは、デザインパターンの採用、イベントディスパッチ処理、依存性の注入など、 より頑健で適応性に優れたオブジェクト指向アーキテクチャを備えています。 その結果、旧SDKのクラスやメソッドの多くが変更されました。

プロジェクトの依存対象

新しいSDKは Version 1と違って、依存対象をあらかじめすべてリポジトリに収容してはいません。 Composer が依存関係を適切に解決し、オートロードするようになっています。 但し、pharをダウンロードしてSDKをインストールした場合は、依存関係が解決済みになっています。

名前空間

SDKのディレクトリ構成や名前空間は、 PSR-0規約 に従って 編成されているため、自然とモジュール構成になっています。 Aws\Common 名前空間にはSDKの中核コードが収容されており、各サービスクライアントは それぞれ独立した名前空間 (例: Aws\DynamoDb) に入っています。

コーディング規約

SDKはPHP Framework Interop Groupが制定したPSR規約に準拠しています。 すぐに気づくように、メソッド名はすべて小文字で始まるキャメルケース方式に なりました (例えば put_object ではなく putObject)。

必要なリージョン

リージョン はクライアントのインスタンスを生成するために必要です (Amazon CloudFrontのように、サービスに単一のエンドポイントがある場合を除く)。 どのAWSリージョンを選択するかによって、処理性能やコストに影響があるかも知れません。

クライアントファクトリ

ファクトリメソッドはサービスクライアントのインスタンスを生成し、署名のセットアップ、 べき乗待機法に基づく再試行、例外ハンドラなどの処理を行います。 クライアントファクトリには少なくとも、アクセスキー、秘密鍵、リージョンを指定する必要がありますが、 他にもクライアントの動作を変更するための設定が多数あります。

$dynamodb = Aws\DynamoDb\DynamoDbClient::factory(array(
    'key'    => 'your-aws-access-key-id',
    'secret' => 'your-aws-secret-access-key',
    'region' => 'us-west-2',
));

設定

グローバル設定ファイルを使って、サービスビルダ経由で自動的に、証明書をクライアントに注入することができます。 サービスビルダは、サービスクライアントに対する「依存性の注入」コンテナとして振る舞います。 (注: SDK第1版と違い、設定ファイルを自動的にロードすることはありません)

$aws = Aws\Common\Aws::factory('/path/to/custom/config.php');
$s3 = $aws->get('s3');

サービスクライアントのインスタンスを生成する方法としては、この技法が望ましいでしょう。 config.php は次のような記述になります。

<?php
return array(
    'includes' => array('_aws'),
    'services' => array(
        'default_settings' => array(
            'params' => array(
                'key'    => 'your-aws-access-key-id',
                'secret' => 'your-aws-secret-access-key',
                'region' => 'us-west-2'
            )
        )
    )
);

'includes' => array('_aws') という記述により、SDKに付属するデフォルトの設定ファイルを取り込みます。 これでサービスクライアントのセットアップが終わり、 サービスビルダの get() メソッドで名前による検索ができるようになります。

サービスオペレーション

新版のSDKでもオペレーションの実行方法はほとんど同じですが、大きな違いが2つあります。 まず、オペレーション名は小文字で始まるキャメルケース方式になります。 次に、オプションを渡すためには常に、単一の配列パラメータを使います。 Amazon S3の PutObject オペレーションの例を、新旧のSDKを対比して示します。

// Previous SDK - PutObject operation
$s3->create_object('bucket-name', 'object-key.txt', array(
    'body' => 'lorem ipsum'
));
// New SDK - PutObject operation
$result = $s3->putObject(array(
    'Bucket' => 'bucket-name',
    'Key'    => 'object-key.txt',
    'Body'   => 'lorem ipsum'
));

新しいSDKの putObject() メソッドは、実際にはクライアント側に存在しません。 クライアントの __call() マジックメソッドを使って実装されており、 コマンドのインスタンスを生成し、コマンドを実行し、結果を取得する、という一連の処理のショートカットとして働きます。

Command オブジェクトは、AWSに対する呼び出しの要求と応答をカプセル化しています。 Command オブジェクトから getResult() メソッドで (先の例を参照) パース済みの結果を取得し、 あるいは getResponse() メソッドで応答に関するデータ (状態コード、生の応答など) を取得することができます。

Command オブジェクトは、実行前にコマンドを操作する場合、あるいはいくつかのコマンドを並列実行する場合にも有用です。 同じ PutObject オペレーションを、コマンドの構文で実行する例を示します。

$command = $s3->getCommand('PutObject', array(
    'Bucket' => 'bucket-name',
    'Key'    => 'object-key.txt',
    'Body'   => 'lorem ipsum'
));
$result = $command->getResult();

あるいは、 Command オブジェクトの set() メソッドを連結して記述する方式も可能です。

$result = $s3->getCommand('PutObject')
    ->set('Bucket', 'bucket-name')
    ->set('Key', 'object-key.txt')
    ->set('Body', 'lorem ipsum')
    ->getResult();

応答

応答の形式が変わりました。応答は CFResponse オブジェクトのインスタンスではなくなりました。 新しいSDKの Command オブジェクトには (前節で説明したように) 要求と応答がカプセル化されているので、 ここから結果を取得できます。

// Previous SDK
// Execute the operation and get the CFResponse object
$response = $s3->list_tables();
// Get the parsed response body as a SimpleXMLElement
$result = $response->body;

// New SDK
// Executes the operation and gets the response in an array-like object
$result = $s3->listTables();

書き方はよく似ていますが、旧SDKにおける応答オブジェクトとの間には、根本的な違いがいくつかあります。

新しいSDKでは、パース済みの応答 (すなわち結果) を、Guzzleの Model オブジェクトとして表します (旧版では CFSimpleXML オブジェクト)。 このModelオブジェクトは配列と同じように振る舞うので扱いやすくなっています。 さらに、マッピング、フィルタリングなど、有用な組み込み機能もあります。 結果の中身も、新しいSDKでは違って見えるでしょう。 SDKは応答をマーシャル化 (直列化) してModelの形にし、サービス記述に基づき、より扱いやすい構造に変換します。 APIの資料では、すべてのオペレーションについて、応答の詳細を説明しています。

例外

新しいSDKは、エラーや不正な応答があると、例外を投げて通知するようになりました。

旧SDKでは、オペレーションが成功したかどうか、 CFResponse::isOK() メソッドで調べるようになっていました。 新しいSDKでは、オペレーションの結果が成功 でない 場合、例外を投げます。 したがって、例外が発生しなければ成功とみなしてよいことになりますが、アプリケーションコードには try...catch ロジックを記述して、エラーを適切に処理できるようにしなければなりません。 新しいSDKで、Amazon DynamoDBに対して DescribeTable を呼び出したときの 応答を処理する例を示します。

$tableName = 'my-table';
try {
    $result = $dynamoDb->describeTable(array('TableName' => $tableName));

    printf('The provisioned throughput for table "%s" is %d RCUs and %d WCUs.',
        $tableName,
        $result->getPath('Table/ProvisionedThroughput/ReadCapacityUnits'),
        $result->getPath('Table/ProvisionedThroughput/WriteCapacityUnits')
    );
} catch (Aws\DynamoDb\Exception\DynamoDbException $e) {
    echo "Error describing table {$tableName}";
}

コマンドから逆にGuzzle応答オブジェクトを取得することもできます。 これは、状態コード、ヘッダの追加データ、生の応答ボディーなどを取得したい場合に有用です。

$command = $dynamoDb->getCommand('DescribeTable', array('TableName' => $tableName));
$statusCode = $command->getResponse()->getStatusCode();

さらに、例外が発生した場合、ここから応答オブジェクトや状態コードを取得することも可能です。

try {
    $command = $dynamoDb->getCommand('DescribeTable', array(
        'TableName' => $tableName
    ));
    $statusCode = $command->getResponse()->getStatusCode();
} catch (Aws\DynamoDb\Exception\DynamoDbException $e) {
    $statusCode = $e->getResponse()->getStatusCode();
}

イテレータ

SDKにはイテレータクラスも付属しているので、リスト型や記述型のオペレーション結果について、容易に横断的な処理ができます。 複数の要求を実行し、トークンやマーカを追跡する処理は、イテレータクラスが適切に行うので、ループの形で記述する必要がありません。 イテレータを対象とするforeach構文で記述すればよいのです。

$objects = $s3->getIterator('ListObjects', array(
    'Bucket' => 'my-bucket-name'
));

foreach ($objects as $object) {
    echo $object['Key'] . PHP_EOL;
}

新旧のSDKに基づくコード例の比較

例1 - Amazon S3のListPartsオペレーション

SDK第1版に基づく例

<?php

require '/path/to/sdk.class.php';
require '/path/to/config.inc.php';

$s3 = new AmazonS3();

$response = $s3->list_parts('my-bucket-name', 'my-object-key', 'my-upload-id', array(
    'max-parts' => 10
));

if ($response->isOK())
{
    // Loop through and display the part numbers
    foreach ($response->body->Part as $part) {
        echo "{$part->PartNumber}\n";
    }
}
else
{
    echo "Error during S3 ListParts operation.\n";
}

SDK第2版に基づく例

<?php

require '/path/to/vendor/autoload.php';

use Aws\Common\Aws;
use Aws\S3\Exception\S3Exception;

$aws = Aws::factory('/path/to/config.php');
$s3 = $aws->get('s3');

try {
    $result = $s3->listParts(array(
        'Bucket'   => 'my-bucket-name',
        'Key'      => 'my-object-key',
        'UploadId' => 'my-upload-id',
        'MaxParts' => 10
    ));

    // Loop through and display the part numbers
    foreach ($result['Part'] as $part) {
        echo "{$part[PartNumber]}\n";
    }
} catch (S3Exception $e) {
    echo "Error during S3 ListParts operation.\n";
}

例2 - Amazon DynamoDBのScanオペレーション

SDK第1版に基づく例

<?php

require '/path/to/sdk.class.php';
require '/path/to/config.inc.php';

$dynamo_db = new AmazonDynamoDB();

$start_key = null;
$people = array();

// Perform as many Scan operations as needed to acquire all the names of people
// that are 16 or older
do
{
    // Setup the parameters for the DynamoDB Scan operation
    $params = array(
        'TableName'       => 'people',
        'AttributesToGet' => array('id', 'age', 'name'),
        'ScanFilter'      => array(
            'age' => array(
                'ComparisonOperator' =>
                    AmazonDynamoDB::CONDITION_GREATER_THAN_OR_EQUAL,
                'AttributeValueList' => array(
                    array(AmazonDynamoDB::TYPE_NUMBER => '16')
                )
            ),
        )
    );

    // Add the exclusive start key parameter if needed
    if ($start_key)
    {
        $params['ExclusiveStartKey'] = array(
            'HashKeyElement' => array(
                AmazonDynamoDB::TYPE_STRING => $start_key
            )
        );

        $start_key = null;
    }

    // Perform the Scan operation and get the response
    $response = $dynamo_db->scan($params);

    // If the response succeeded, get the results
    if ($response->isOK())
    {
        foreach ($response->body->Items as $item)
        {
            $people[] = (string) $item->name->{AmazonDynamoDB::TYPE_STRING};
        }

        // Get the last evaluated key if it is provided
        if ($response->body->LastEvaluatedKey)
        {
            $start_key = (string) $response->body
                ->LastEvaluatedKey
                ->HashKeyElement
                ->{AmazonDynamoDB::TYPE_STRING};
        }
    }
    else
    {
        // Throw an exception if the response was not OK (200-level)
        throw new DynamoDB_Exception('DynamoDB Scan operation failed.');
    }
}
while ($start_key);

print_r($people);

SDK第2版に基づく例

<?php

require '/path/to/vendor/autoload.php';

use Aws\Common\Aws;
use Aws\DynamoDb\Enum\ComparisonOperator;
use Aws\DynamoDb\Enum\Type;

$aws = Aws::factory('/path/to/config.php');
$dynamodb = $aws->get('dynamodb');

// Create a ScanIterator and setup the parameters for the DynamoDB Scan operation
$scan = $dynamodb->getIterator('Scan', array(
    'TableName'       => 'people',
    'AttributesToGet' => array('id', 'age', 'name'),
    'ScanFilter'      => array(
        'age' => array(
            'ComparisonOperator' => ComparisonOperator::GE,
            'AttributeValueList' => array(
                array(Type::NUMBER => '16')
            )
        ),
    )
));

// Perform as many Scan operations as needed to acquire all the names of people
// that are 16 or older
$people = array();
foreach ($scan as $item) {
    $people[] = $item['name'][Type::STRING];
}

print_r($people);