AWS Developer Blog

Version 3 Preview of the AWS SDK

We’re excited to introduce you to the preview release of Version 3 of the AWS SDK for PHP! As of today, the preview release of Version 3 (V3) is available on GitHub and via Composer.

Two years ago, we released Version 2 (V2) of the SDK. Since then, thousands of developers and companies have adopted it. We are sincerely grateful to all of our users and contributors. We have been constantly collecting your feedback and ideas, and continually watching the evolution of PHP, AWS, and the Guzzle library.

Earlier this year, we felt we could make significant improvements to the SDK, but only if we could break a few things. Since receiving a unanimously positive response to our blog post about updating to the latest version of Guzzle a few months ago, we’ve been working hard on V3, and we’re ready to share it with you.

What’s new?

The new version of the SDK provides a number of important benefits to AWS customers. It is smaller and faster, with improved performance for both serial and concurrent requests. It has several new features based on its use of the new Guzzle 5 library (which also includes the new features from Guzzle 4). The SDK will also, starting from V3, follow the official SemVer spec, so you can have complete confidence when setting version constraints in your projects’ composer.json files.

Let’s take a quick look at some of the new features.

Asynchronous requests

With V3, you can perform asynchronous operations, which allow you to more easily send requests concurrently. To achieve this, the SDK returns future result objects when you specify the @future parameter, which block only when they are accessed. For managing more robust asynchronous workflows, you can retrieve a promise from the future result, to perform logic once the result becomes available or an exception is thrown.

<?php

// Upload a file to your bucket in Amazon S3.
// Use '@future' to make the operation complete asynchronously.
$result = $s3Client->putObject([
    'Bucket' => 'your-bucket',
    'Key'    => 'docs/file.pdf',
    'Body'   => fopen('/path/to/file.pdf', 'r'),
    '@future' => true,
]);

After creating a result using the @future attribute, you now have a future result object. You can use the data stored in the future in a blocking (or synchronous) manner by just using the result as normal (i.e., like a PHP array).

// Wait until the response has been received before accessing its data.
echo $result['ObjectURL'];

If you want to allow your requests to complete asynchronously, then you should use the promise API of the future result object. To retrieve the promise, you must use the then() method of the future result, and provide a callback to be completed when the promise is fulfilled. Promises allow you to more easily compose pipelines when dealing with asynchronous results. For example, we could use promises to save the Amazon S3 object’s URL to an item in an Amazon DynamoDB table, once the upload is complete.

// Note: $result is the result of the preceding example's PutObject operation.
$result->then(
    function ($s3Result) use ($ddbClient) {
        $ddbResult = $ddbClient->putItem([
            'TableName' => 'your-table',
            'Item' => [
                'topic' => ['S' => 'docs'],
                'time'  => ['N' => (string) time()],
                'url'   => ['S' => $s3Result['ObjectURL']],
            ],
            '@future' => true,
        ]);

        // Don't break promise chains; return a value. In this case, we are returning
        // another promise, so the PutItem operation can complete asynchronously too.
        return $ddbResult->promise();
    }
)->then(
    function ($result) {
        echo "SUCCESS!n";
        return $result;
    },
    function ($error) {
        echo "FAILED. " . $error->getMessage() . "n";
        // Forward the rejection by re-throwing it.
        throw $error;
    }
);

The SDK uses the React/Promise library to provide the promise functionality, allowing for additional features such as joining and mapping promises.

JMESPath querying of results

The result object also has a new search() method that allows you to query the result data using JMESPath, a query language for JSON (or PHP arrays, in our case).

<?php

$result = $ec2Client->describeInstances();

print_r($result->search('Reservations[].Instances[].InstanceId'));

Example output:

Array
(
    [0] => i-xxxxxxxx
    [1] => i-yyyyyyyy
    [2] => i-zzzzzzzz
)

Swappable and custom HTTP adapters

In V3, cURL is no longer required, but is still used by the default HTTP adapter. However, you can use other HTTP adapters, like the one shipped with Guzzle that uses PHP’s HTTP stream wrapper. You can also write custom adapters, which opens up the possibility of creating an adapter that integrates with a non-blocking event loop like ReactPHP.

Paginators

Paginators are a new feature in V3, that come as an addition to Iterators from V2. Paginators are similar to Iterators, except that they yield Result objects, instead of items within a result. This is nice, because it handles the tokens/markers for you, getting multiple pages of results, but gives you the flexibility to extract whatever data you want.

// List all "directories" and "files" in the bucket.
$paginator = $s3->getPaginator('ListObjects', [
    'Bucket' => 'my-bucket',
    'Delimiter' => '/'
]);
foreach ($paginator as $result) {
    $jmespathExpr = '[CommonPrefixes[].Prefix, Contents[].Key][]';
    foreach ($result->search($jmespathExpr) as $item) {
        echo $item . "n";
    }
}

Example output:

Array
(
    [0] => dir1/
    [1] => dir2/
    [2] => file1
    [3] => file2
    ...
)

New event system

Version 3 features a new and improved event system. Command objects now have their own event emitter that is decoupled from the HTTP request events. There is also a new request "progress" event that can be used for tracking upload and download progress.

use GuzzleHttpEventProgressEvent;

$s3->getHttpClient()->getEmitter()->on('progress', function (ProgressEvent $e) {
    echo 'Uploaded ' . $e->uploaded . ' of ' . $e->uploadSize . "n";
});

$s3->putObject([
   'Bucket' => $bucket,
   'Key'    => 'docs/file.pdf',
   'Body'   => fopen('/path/to/file.pdf', 'r'),
]);

Example output:

Uploaded 0 of 5299866
Uploaded 16384 of 5299866
Uploaded 32768 of 5299866
...
Uploaded 5275648 of 5299866
Uploaded 5292032 of 5299866
Uploaded 5299866 of 5299866

New client options

For V3, we changed some of the options you provide when instantiating a client, but we added a few new options that may help you work with services more easily.

  • "debug" – Set to true to print out debug information as requests are being made. You’ll see how the Command and Request objects are affected during each event, and an adapter-specific wire log of the request.
  • "retries" – Set the maximum number of retries the client will perform on failed and throttled requests. The default has always been 3, but now it is easy to configure.

These options can be set when instantiating client.

<?php

$s3 = (new AwsSdk)->getS3([
    // Exist in Version 2 and 3
    'profile'  => 'my-credential-profile',
    'region'   => 'us-east-1',
    'version'  => 'latest',

    // New in Version 3
    'debug'    => true,
    'retries'  => 5,
]);

What has changed?

To make all of these improvements for V3, we needed to make some backward-incompatible changes. However, the changes from Version 2 to Version 3 are much fewer than the changes from Version 1 to Version 2. In fact, much of the way you use the SDK will remain the same. For example, the following code for writing an item to an Amazon DynamoDB table looks exactly the same in both V2 and V3 of the SDK.

$result = $dynamoDbClient->putItem([
    'TableName' => 'Contacts',
    'Item'      => [
        'FirstName' => ['S' => 'Jeremy'],
        'LastName'  => ['S' => 'Lindblom'],
        'Birthday'  => ['M' => [
            'Month' => ['N' => '11'],
            'Date'  => ['N' => '24'],
        ],
    ],
]);

There are two important changes though that you should be aware of upfront:

  1. V3 requires PHP 5.5 or higher and requires the use of Guzzle 5.
  2. You must now specify the API version (via the "version" client option) when you instantiate a client. This is important, because it allows you to lock-in to the API versions of the services you are using. This helps us and you maintain backward compatibility between future SDK releases, because you will be in charge of API versions you are using. Your code will never be impacted by new service API versions until you update your version setting. If this is not a concern for you, you can default to the latest API version by setting 'version' to 'latest' (this is essentially the default behavior of V2).

What next?

We hope you are excited for Version 3 of the SDK!

We look forward to your feedback as we continue to work towards a stable release. Please reach out to us in the comments, on GitHub, or via Twitter (@awsforphp). We plan to publish more blog posts in the near future to explain some of the new features in more detail. We have already published the API docs for V3, but we’ll be working on improving all the documentation for V3, including creating detailed migration and user guides. We’ll also be speaking about V3 in our session at AWS re:Invent.

We will continue updating and making regular releases for V2 on the "master" branch of the SDK’s GitHub repository. Our work on V3 will happen on a separate "v3" branch until we are ready for a stable release.

Version 3 can be installed via Composer using version 3.0.0-beta.1, or you can download the aws.phar or aws.zip on GitHub.