Category: PHP


Automating the Deployment of Encrypted Web Services with the AWS SDK for PHP

by Joseph Fontes | on | in PHP | | Comments

Having worked in the web hosting space, one of the areas I find so fun about AWS is the ease of automating tasks that have historically been quite disjointed.  The process of supporting a customer request to register a domain, create or update DNS entries, configure the load balancer, deploy servers, etc., had me working across a multitude of systems, interfaces, and APIs.  Now, with the release of AWS Certificate Manager (ACM) in addition to existing AWS services, AWS provides all the tools and capabilities needed to support the provisioning of these services within customer accounts.

In this three-part series of posts, we’ll review how to use the AWS SDK for PHP to automate web service deployment, domain registration, DNS administration, and SSL certificate generation and assignment.  Using the examples outlined in these posts, as well as other features and functions of the AWS SDK for PHP, you’ll learn how to programmatically create a process for automatically purchasing a domain and then deploying an HTTPS (SSL-secured) web service on AWS by using either Amazon EC2 or AWS Elastic Beanstalk.

The examples in this post focus on using Amazon Route 53 to automate the registration of domain names and DNS administration.  Next, we’ll showcase how to use ACM to create and manage SSL certificates.  In subsequent posts, we will show how to automate the setup of encrypted HTTPS web services with the new domain and newly created certificates on Elastic Beanstalk.  Then, we’ll show how to automate deployments to EC2 and Elastic Load Balancing.  Once complete, we’ll have two web application stacks.  We’ll run the www.dev-null.link site from Elastic Beanstalk, and use EC2 and Elastic Load Balancing to run the second web application stack.  The following diagrams illustrate the final designs.

   

Amazon Route53 Domain Registration

The first task in building the web infrastructure is to identify and register an available domain name.  We can use the AWS SDK for PHP to check domain name availability.  We will use a method called checkDomainAvailability, which is part of the Route 53 Domains client.  We can automate the process of testing domains until we have a name that meets our application’s needs and is also available for registration.  The example below loops through an array of domain names, listing their current status for registration.

$route53Client = $sdk->createRoute53Domains();

$domainNames = [ "test.com", "dev.com", "dev-null.link", "null38.link" ];

foreach($domainNames as $domainNameElement) {
        $route53CheckDomainAvailData = [ 'DomainName' => $domainNameElement ];
        $route53CheckDomainResults = $route53Client->checkDomainAvailability($route53CheckDomainAvailData);
        print "Domain $domainNameElement is ".$route53CheckDomainResults['Availability']."n";
}

You can view the results of the check below.

There are two domain names available for registration.  In this example, we’ll register the domain dev-null.link.  This name contains the “.link” top-level domain (TLD), and the “dev-null” second-level domain. Now, register the domain by using the registerDomain method.  The registration has several required fields that we need to complete. These requirements are specific to each top level domain. For this example, we can use the following data ( provided in this Github Gist):
$route53DomainRegData = [
    'AdminContact' => [
        'AddressLine1' => $address1,
        'AddressLine2' => $address2,
        'City' => $city,
        'ContactType' => 'PERSON',
        'CountryCode' => 'US',
.....

$route53Client = $sdk->createRoute53Domains();
$route53CreateDomRes = $r53Client->registerDomain($route53DomainRegData);

print_r($route53DomainRegData);

Notice that the PhoneNumber data element must be in the format of  “+1.1231231212” to be valid.

We can now register the domain as follows.
[user@dev1 scripts]# php aws-route53-register-domain.php 
...
            [statusCode] => 200 
            [effectiveUri] => https://route53domains.us-east-1.amazonaws.com 
...

While we wait for the registration process to finish, we can check on the status of the domain.  First, use the listOperations method to print the list of current operations, and then enter the operation number into the getOperationDetail method.  Let’s look at all of the pending operations with the code.

$route53ListOperationsResults = $route53Client->listOperations();
print_r($route53ListOperationsResults);

$route53OperDetails = [ 'OperationId' => $operationId ];
$route53OperResults = $route53Client->getOperationDetail($route53OperDetails);
print_r($route53OperResults);

Result

[user@dev1 scripts]#
[user@dev1 scripts]# php aws-route53-list-operations.php
…
[Status] => IN_PROGRESS
                    [Type] => REGISTER_DOMAIN
…

AWS Certificate Manager

With ACM, we no longer have to worry about certificate expirations, securing the certificate private keys, copying self-signed CA certificates to clients, making sure servers all have the right certificate, or even the cost of a managed SSL certificate.  AWS provides managed SSL certificates at no cost.  Also, AWS handles the responsibility of renewing the certificate and placing it on the devices used to terminate SSL connections.  ACM can be used across managed AWS services such as ELB, Amazon CloudFront, and Elastic Beanstalk.

ACM in Action

Let’s go through how to create multiple certificates to secure connections to different websites.  We must first request a new certificate with the corresponding public and private keys.  The requestCertificate method automates this process.

The following example shows how to generate the certificate for our first domain.

$acmClient = $sdk->createAcm();

$acmRequestCertData = [ 'DomainName' => "www.dev-null.link",
    'DomainValidationOptions' => [
        [
            'DomainName' => "www.dev-null.link",
            'ValidationDomain' => "dev-null.link",
        ],
    ],
    'IdempotencyToken' => 'TOKENSTRINGDEVNULL01',
    'SubjectAlternativeNames' => ['dev-null.link', 'images.dev-null.link'],
];

$acmRequestCertResults = $acmClient->requestCertificate($acmRequestCertData);
print_r($acmRequestCertResults);

Result

[user@dev1 scripts]# php aws-acm-requestCertificate.php
...
    [CertificateArn] => arn:aws:acm:us-east-1:ACCOUNTID:certificate/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
    [@metadata] => Array
        (
            [statusCode] => 200
...
 

SubjectAlternativeNames identifies other DNS entries that the certificate should cover.  In this example, they are all subdomains of dev-null.link but can also include other domain names that could be used synonymously with our requested domain.  You can repeat this call for any additional certificates you need.  Be sure to update the value of IdempotencyToken for each certificate request created.  You should save the value of CertificateArn because you’ll need to use it later.

We want to secure the primary hostname www.dev-null.link.  ACM requires validation for the domain from an email address that is tied to the registration information.  Domain validation requests are sent to multiple locations.  These validation emails are sent to domain email addresses in the following order: admin@dev-null.link, administrator@dev-null.link, hostmaster@dev-null.link, postmaster@dev-null.link, and webmaster@dev-null.link.  In addition, a validation request is also sent to the email contacts for the Administrative, Technical, and Domain Registrant.  The following figure shows a copy of the received email.

When you click the link in the email, you’re taken to the page shown below.

Next, click I Approve.  A confirmation page appears that you can save for your records.

Let’s now use the listCertificates and describeCertificate methods to show all of the certificates we’ve generated.

$acmListCertResults = $acmClient->listCertificates();
print_r($acmListCertResults);

Result

[user@dev1 scripts]# php aws-acm-list.php
...
    [CertificateSummaryList] => Array
...
                    [CertificateArn] => arn:aws:acm:us-east-1:ACCOUNTID:certificate/CERTIFICATE-ID
                    [DomainName] => www.dev-null.link
...
                    [CertificateArn] => arn:aws:acm:us-east-1:ACCOUNTID:certificate/CERTIFICATE-ID
                    [DomainName] => api.dev-null.link
...
 
You can view details about the certificates by calling the describeCertificates method with the CertificateARN received from the 
previous listCertificates call.
$certificateArn = $acmListCertResults['CertificateSummaryList'][0]['CertificateArn'];

$acmDescribeCertData = [ 'CertificateArn' => $certificateArn ];
$acmDescribeCertResults = $acmClient->describeCertificate($acmDescribeCertData);

print_r($acmDescribeCertResults);

You can view the full output here with abbreviated output shown below.

[Certificate] => ...
Array
(
        [CertificateArn] => CERTIFICATE-ARN
        [DomainName] => www.dev-null.link
        [SubjectAlternativeNames] => Array
        (
                [0] => www.dev-null.link
                [1] => dev-null.link
                [2] => images.dev-null.link
        )
...

Finally, view the full certificate with the certificate chain.

$acmGetCertificateData = [ 'CertificateArn' => $certificateArn ];

$acmGetCertificateResults = $acmClient->getCertificate($acmGetCertificateData);

print_r($acmGetCertificateResults);
Result
[Certificate] => -----BEGIN CERTIFICATE-----
…
-----END CERTIFICATE-----

 [CertificateChain] => -----BEGIN CERTIFICATE-----
…
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
…
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
…
-----END CERTIFICATE-----
    [@metadata] => Array
        (
            [statusCode] => 200
…
This code will return the public certificate and the chain of trust leading to the public CA that is signing the certificate allowing web browsers to trust the website being visited.
To get more information about the certificate and the request process, we can use the describeCertificate method.  This method takes the certificateArn value as input and produces information about the referenced certificate.  This information includes the list of email addresses that received validation emails, the encryption algorithm used, the certificate creation and expiration dates, and the full list of host names covered by the certificate.  Looking forward, we can delete certificates and resend the validation email with the available ACM methods.
Now that we’ve covered the setup, configuration, and deployment of ACM and DNS, we now have a registered domain available for use.  In the next post, we’ll review how to use the domain, dev-null.link, for our website.  We’ll deploy an HTTPS website that is secured with SSL/TLS by using the AWS SDK with ELB, Amazon EC2, and Elastic Beanstalk.  We’ll also cover creating and deleting Amazon Route 53 resource records, and how to assign our ACM certificate to newly created load balancers.

 

 

Concurrency in Version 3 of the AWS SDK for PHP

by Jonathan Eskew | on | in PHP | | Comments

From executing API commands in the background to grouping commands and waiters into concurrent pools, version 3 of the AWS SDK for PHP lets you write asynchronous code that blocks only when you need it to. In this blog post, I’ll show you how to take advantage of some of the SDK’s concurrency abstractions, including promises, command pools, and waiters.

Promises

The AWS SDK for PHP makes extensive use of promises internally, relying on the Guzzle Promises library. Each operation defined on an API supported by the SDK has an asynchronous counterpart that can be invoked by tacking Async to the name of the operation. An asynchronous operation will immediately return a promise that will be fulfilled with the result of the operation or rejected with an error:

$s3Client = new AwsS3S3Client([ 
    ‘region’ => ‘us-west-2’,
    ‘version’ => ‘2006-03-01’,
]);

// This call will block until a response is obtained
$buckets = $s3Client->listBuckets();

// This call will not block
$promise = $s3Client->listBucketsAsync();

// You can provide callbacks to invoke when the request finishes
$promise->then(
    function (AwsResultInterface $buckets) {
        echo ‘My buckets are: ‘
             . implode(‘, ‘, $buckets->search(‘Buckets[].Name’));
    },
    function ($reason) {
        echo "The promise was rejected with {$reason}";
    }
);

The Guzzle promises returned by asynchronous operations have a wait method that you can call if you need to block until an operation is completed. In fact, synchronous operations like $s3Client->listBuckets() are calling asynchronous operations under the hood and then waiting on the result.

Where promises really shine, though, is in groupings. Let’s say you need to upload ten files to an Amazon S3 bucket. Rather than simply loop over the files and upload them one by one, you can create ten promises and then wait for those ten requests to be complete:

$filesToUpload = [
    ‘/path/to/file/1.ext’,
    ‘/path/to/file/2.ext’,
    …
    ‘/path/to/file/10.ext’,
];
$promises = [];
foreach ($filesToUpload as $path) {
    $promises []= $s3Client->putObjectAsync([
        ‘Bucket’ => $bucketName,
        ‘Key’ => basename($path),
        ‘SourceFile’ => $path,
    ]);
}
// Construct a promise that will be fulfilled when all
// of its constituent promises are fulfilled
$allPromise = GuzzleHttpPromiseall($promises);
$allPromise->wait();

Rather than taking ten times as long as uploading a single file, the asynchronous code above will perform the uploads concurrently. For more information about promises, see the AWS SDK for PHP User Guide.

Waiters

Some AWS operations are naturally asynchronous (for example, those in which a successful response means that a process has been started, but is not necessarily complete). Provisioning Amazon EC2 instances or S3 buckets are gppd examples. If you were starting a project that required three S3 buckets and an Amazon ElastiCache cluster, you might start out by provisioning those resources programmatically:

$sdk = new AwsSdk([‘region’ => ‘us-west-2’, ‘version’ => ‘latest’]);
$elasticacheClient = $sdk->get(‘elasticache’);
$s3Client = $sdk->get(‘s3’);
$promises = [];
for ($i = 0; $i < 3; $i++) {
    $promises []= $s3Client->createBucket([
        ‘Bucket’ => “my-bucket-$i”,
    ]);
}
$cacheClusterId = uniqid(‘cache’);
$promises []= $elasticacheClient->createCacheCluster([
    ‘CacheClusterId’ => $cacheClusterId,
]);
$metaPromise = GuzzleHttpPromiseall($promises);
$metaPromise->wait();

Waiting on the $metaPromise will block only until all of the requests sent by the createBucket and createCacheCluster operations have been completed.You would need to use a waiter to block until those resources are available. For example, you can wait on a single bucket with an S3Client’s waitUntil method:

$s3Client->waitUntil(‘BucketExists’, [‘Bucket’ => $bucketName]);
Like operations, waiters can also return promises, allowing you to compose meta-waiters from individual waiters:
$waiterPromises = [];
for ($i = 0; $i < 3; $i++) {
    // Create a waiter
    $waiter = $s3Client->getWaiter(‘BucketExists’, [
        ‘Bucket’ => “my-bucket-$i”,
    ]);
    // Initiate the waiter and retrieve a promise.
    $waiterPromises []= $waiter->promise();
}
$waiterPromises []= $elasticacheClient
    ->getWaiter(‘CacheClusterAvailable’, [
        ‘CacheClusterId’ => $cacheClusterId,
    ])
    ->promise();
// Composer a higher-level promise from the individual waiter promises
$metaWaiterPromise = GuzzleHttpPromiseall($waiterPromises);
// Block until all waiters have completed
$metaWaiterPromise->wait();

Command Pools

The SDK also allows you to use command pools to fine-tune the way in which a series of operations are performed concurrentl. Command pools are created with a client object and an iterable list of commands, which can be created by calling getCommand with an operation name on any SDK client:

// Create an S3Client
$s3Client = new AwsS3S3Client([
    ‘region’ => ‘us-west-2’,
    ‘version’ => ‘latest’,
]);

// Create a list of commands
$commands = [
    $s3Client->getCommand(‘ListObjects’, [‘Bucket’ => ‘bucket1’]),
    $s3Client->getCommand(‘ListObjects’, [‘Bucket’ => ‘bucket2’]),
    $s3Client->getCommand(‘ListObjects’, [‘Bucket’ => ‘bucket3’]),
];

// Create a command pool
$pool = new AwsCommandPool($s3Client, $commands);

// Begin asynchronous execution of the commands
$promise = $pool->promise();

// Force the pool to complete synchronously
$promise->wait();

How is this different from gathering promises from individual operations, such as by calling $s3Client->listObjectsAsync(…)? One key difference is that no action is taken until you call $pool->promise(), whereas requests are dispatched immediately when you call $s3Client->listObjectsAsync(…). The CommandPool defers the initiation of calls until you explicitly tell it to do so. In addition, by default, a command pool will limit concurrency to 25 operations at a time by default, This simultaneous operation limit can be tuned according to your project’s needs. For a more complex example see CommandPool in the AWS SDK for PHP User Guide.

With promises, waiters, and command pools, version 3 of the AWS SDK for PHP makes it easy to write asynchronous or concurrent code! We welcome your feedback.

Automating the Deployment of AWS Config with the AWS SDK for PHP

by Jonathan Eskew | on | in PHP | | Comments

My colleague Joseph Fontes, an AWS Solutions Architect, wrote the guest post below to discuss automation strategies for AWS Config.


There are times when you need to automate the deployment of services either in your account or within external accounts.  When I recently had to enable AWS Config support in remote accounts, I approached this task the way many others do….by opening the AWS SDK for PHP Reference Guide!

To complete this task, we will need three AWS Config methods: putConfigurationRecorder(), putDeliveryChannel(), and startConfigurationRecorder().  Before making the call to putDeliveryChannel(), we need to create our Amazon S3 bucket destination and identify an AWS SNS topic. 

Let’s instantiate the clients we will need for this exercise.  The client creation will be slightly different for those familiar with creating clients in version 2 of the AWS SDK for PHP.

$s3Client = $sdk->createS3();
$iamClient = $sdk->createIam();
$confClient = $sdk->createConfigService();
$snsClient = $sdk->createSns();

Let’s next create an S3 bucket and destination for our AWS Config logs.  Remember that S3 buckets must be globally unique. We cannot simply use a name like “logs.”  For this reason, our naming convention will use our account number:

$accountID = "XXXXXXXXXXXX";
$s3Bucket = $accountID."-config";
$role = "AWS-Config-Role-IAM";

$s3Res = $s3Client->listBuckets([]);

$s3ResA = $s3Res->toArray();

if(bucketExists($sdk,$s3Bucket,$s3ResA) == 0) {
    $s3Data = [
        'ACL' => 'private',
        'Bucket' => $s3Bucket, 
        'LocationConstraint' => $region
    ];
    $s3Res = $s3Client->createBucket($s3Data);
    $s3ResA = $s3Res->toArray();
    print_r($s3ResA);
    print "Waiting for bucket to become available...";
    $s3Client->waitUntil('BucketExists', [
        'Bucket' => $s3Bucket
    ]);
}

In the preceding example, I have written a function to test if the bucket exists.  This is a completely optional step. The full code will be made available for download.

The call for createBucket() with Amazon S3 followed by the waitUntil() method delays script execution until the S3 bucket is available for use. 

We now need to create the IAM role AWS Config uses to access the S3 bucket.  We need an assume role policy to reference during the call.  I have created a text file, policy.json, with the following contents:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "config.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Create a local copy of the policy.json file.

Next, we need to create an additional policy file that gives the AWS Config service permissions to perform required tasks in your account. Download the following file and place it in your running directory:

config-policy.json

We can now create the IAM role for the AWS Config service:

$config_topic = "ConfigTopicSNS";
$policy_info = file_get_contents('config-policy.json');
$replace_sns = ":".$AccountID.":".$config_topic;
$replS3Bucket = "XXXXXXXXXXXXXXXXXXXXXXXXXXX";
$replSNS = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYY";
$replS3Bucket2 = "QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ";
$policy_info = str_replace($replS3Bucket,$s3Bucket,$policy_info);
$policy_info = str_replace($replSNS,$replace_sns,$policy_info);
$policy_info = str_replace($replS3Bucket2,$s3Bucket,$policy_info);

$iamData = [
    'RoleName' => $role,
    'AssumeRolePolicyDocument' => file_get_contents('policy.json')
];

$iamRes = $iamClient->createRole($iamData);
$iamResA = $iamRes->toArray();

$roleARN = $iamResA['Role']['Arn'];
$iamData = [
    'PolicyDocument' => $policy_info,
    'PolicyName' => 'NEWConfigPolicy',
    'Description' => "config policy via sdk"
];
$iamRes = $iamClient->createPolicy($iamData);
$iamResA = $iamRes->toArray();

$confPolicyArn = $iamResA['Policy']['Arn'];

$iamData = [
    'PolicyArn' => $ConfPolicyArn,
    'RoleName' => $Role
];
$iamRes = $iamClient->attachRolePolicy($iamData);
$iamResA = $iamRes->toArray();

This portion imports the trust policy defined by the local file, policy.json, along with the permissions policy identified by the local file, config-policy.json.  The permissions policy is modified upon read to ensure proper identifiers are used in the script.

Let’s create the SNS topic.

$snsTopic = 'ConfigTopicSNS';

$snsData = ['Name' => $config_topic];
$snsRes = $snsClient->createTopic($snsData);
$snsResA = $snsRes->toArray();

$snsTopicARN = $snsResA['TopicArn'];

We now have to call the putConfigurationRecorder() method.  This creates a new configuration recorder that will identify the changes we want to record.  In this example, we want to record all changes.  Readers can be more prescriptive in their recordings by identifying resources types.  You’ll find a list of supported resource types here:

http://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources

$confData = [
    'ConfigurationRecorder' => [
        'name' => 'default',
        'recordingGroup' => [
            'allSupported' => true,
        ],
        'roleARN' => $roleARN,
    ],
];

$confClient->putConfigurationRecorder($confData);

Now that we know what we are going to record, we have to identify where we will send the recordings.  The following shows the putDeliveryChannel() method, which needs the S3 bucket (created earlier) to store the recordings and the SNS topic, which will alert to configuration changes.

$confData = [
    'DeliveryChannel' => [
        'name' => 'default',
        's3BucketName' => $s3Bucket,
        'snsTopicARN' => $snsTopicARN,
    ],
];

$confClient->putDeliveryChannel($confData);

Finally, now that we have our recording configuration and methods for delivery defined, we have to start recording the changes:

$confData = ['ConfigurationRecorderName' => 'default' ];

$confClient->startConfigurationRecorder($confData);

Now that changes to infrastructure within this region of our account are being recorded with notifications sent, we can be notified of and record infrastructure updates.  We can then use additional SNS subscriptions to process the list of items in our infrastructure that have changed, review them for root cause analysis in the event of service issues, use centralized logging along with event correlation to look for system anomalies, and so on.

You can review the processing of Amazon SNS notifications here:

http://blogs.aws.amazon.com/php/post/Tx15276Q7B4NUO0/Receiving-Amazon-SNS-Messages-in-PHP


 

 

Receiving Amazon SNS Messages in PHP

by Jonathan Eskew | on | in PHP | | Comments

A little over a year ago, we announced a new feature in the AWS SDK for PHP that allowed customers to validate inbound SNS messages. In the latest version of the SDK, this functionality is now available in its own package, one that does not depend on the SDK, so it’s simpler to listen to SNS topics with lightweight web services. In this blog post, I’ll show you how to use the Amazon SNS message validator for PHP to parse messages in a single-file web application.

About SNS

Amazon Simple Notification Service (Amazon SNS) is a fast and fully managed push messaging service that can deliver messages over email, SMS, mobile push, and HTTP/HTTPS endpoints.

With Amazon SNS, you can set up topics to publish custom messages to subscribed endpoints. SNS messages are used by many of the other AWS services to communicate information asynchronously about your AWS resources. Some examples include:

  • Configuring Amazon Glacier to notify you when a retrieval job is complete.
  • Configuring AWS CloudTrail to notify you when a new log file has been written.
  • Configuring Amazon Elastic Transcoder to notify you when a transcoding job changes status (e.g., from “Progressing” to “Complete”)

Receiving SNS Messages and Verifying Their Signature

Using the SNS Message Validator’s Message class, you can easily parse raw POST data from SNS:

<?php

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

$message = AwsSnsMessage::fromRawPostData();
echo $message->get('Message');

Amazon SNS sends different types of messages, including SubscriptionConfirmation, Notification, and UnsubscribeConfirmation. The formats of these messages are described in the Appendix: Message and JSON Formats section of the Amazon SNS Developer Guide.

Messages from Amazon SNS are signed. As a best practice, you should use the MessageValidator class to verify the signature and ensure a message was sent from Amazon SNS.

<?php

use AwsSnsMessage;
use AwsSnsMessageValidator;

$message = Message::fromRawPostData();

// Validate the message
$validator = new MessageValidator();
$validator->validate($message);

Instances of AwsSnsMessageValidator have two methods for validating messages, both of which take an instance of AwsSnsMessage as their only argument. validate (shown above) will throw an AwsSnsExceptionInvalidSnsMessageException. isValid will return a Boolean — true for valid messages and false for invalid ones.

Confirming a Subscription to a Topic

In order for an HTTP(S) endpoint to receive messages, it must first be subscribed to an SNS topic. Subscriptions are confirmed over HTTP(S), so you’ll need to create and deploy an endpoint before you set up a subscription. SubscriptionConfirmation messages provide a URL you can use to confirm the subscription.

$message = AwsSnsMessage::fromRawPostData();

// Validate the message
$validator = new MessageValidator();
if ($validator->isValid($message)) {
    file_get_contents($message->get('SubscribeURL'));
}

Handling Notifications

Let’s put it all together and add some extra code for handling both notifications and subscription control messages.

<?php

if ('POST' !== $_SERVER['REQUEST_METHOD']) {
    http_response_code(405);
    die;
}

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

try {
    $message = AwsSnsMessage::fromRawPostData();
    $validator = new AwsSnsMessageValidator();
    $validator->validate($message);

    if (in_array($message['Type'], ['SubscriptionConfirmation', 'UnsubscribeConfirmation']) {
        file_get_contents($message['SubscribeURL']);
    }

    $log = new SplFileObject('../messages.log', 'a');
    $log->fwrite($message['Message'] . "n");
} catch (Exception $e) {
    http_response_code(404);
    die;
}

Conclusion

As you can see, receiving, verifying, and handling Amazon SNS messages is simple. Setting up your application to receive SNS messages will allow you to create applications that can handle asynchronous communication from AWS services and other parts of your application.

AWS Workshop and Hackathon at PNWPHP

by Jeremy Lindblom | on | in PHP | | Comments

In September, the Pacific Northwest PHP Conference (PNWPHP) is happening in Seattle. It’s just down the street from us, and we decided to partner with the them to host an AWS Workshop and Hackathon on September 10th, 2015.

The workshop portion will serve as kind of AWS bootcamp for PHP developers, and will include a few presentations about AWS services and architecture, the AWS SDK for PHP, and running PHP applications on AWS. You can see a full list of the presentations and speakers on the PNWPHP website.

The hackathon portion will allow people to team up and create something using AWS services and the SDK. Like most hackathons, there will be food and prizes involved. Hackathon participants will also receive AWS credits through the AWS Activate program to cover the costs of the services they will be using during the hackathon.

Tickets for AWS Workshop and Hackathon are sold separately from the main PNWPHP conference, so whether you end up attending the main conference or not, you still have the opportunity to join us at our workshop/hackathon. In fact, you can use the discount code "AWSHACK" to get your AWS Workshop and Hackathon ticket for a 50% discount. Head to the PNWPHP registration page to get your ticket.

Whether you are a Seattle native, or you are in town for PNWPHP, we hope to see you at our special AWS Workshop and Hackathon.

Reduce Composer Issues on Elastic Beanstalk

by Jeremy Lindblom | on | in PHP | | Comments

During the past couple of months, we’ve had a few reports from customers where they have experienced PHP application deployment failures on AWS Elastic Beanstalk related to parsing exceptions being thrown by Composer. In case you have recently run into the issue yourself, we would like to briefly describe why it is happening and how you can circumvent the issue.

The issue

The issue occurs when a project or its dependencies expresses its requirements using newer Composer syntax features like the carat (^) operator. For users of the AWS SDK for PHP, the error looks something like this:

[RuntimeException] Could not load package aws/aws-sdk-php in http://packagist.org:
[UnexpectedValueException] Could not parse version constraint ^5.3: Invalid version string "^5.3"

We also observed the issue with some versions of the Laravel framework and a few other libraries. The issue comes up when you are using older Elastic Beanstalk stacks with your applications. The older stacks have an old version of Composer included on the underlying Amazon Machine Image (AMI) that does not support some of the latest Composer features like the carat (^) and new OR (||) syntax.

The solution

There are 3 different ways to solve this issue.

  1. Upgrade your application to use the latest Elastic Beanstalk solution stack. The latest solution stacks for PHP have a more recent version of Composer that supports the new syntax features.
  2. Use Elastic Beanstalk configuration files (.ebextension). You can create a file ending in .config inside your .ebextension directory that allows you to perform a Compose self-update command before installing your dependencies. For example, name the file 01composer.config and add the following configuration:

    commands:
      01updateComposer:
        command: export COMPOSER_HOME=/root && /usr/bin/composer.phar self-update
    
    option_settings:
      - namespace: aws:elasticbeanstalk:application:environment
        option_name: COMPOSER_HOME
        value: /root
  3. Install your dependencies locally. One way to avoid issues with Composer during deployment is to bypass the whole Composer workflow entirely by creating deployments of your application with the dependencies pre-installed.

The conclusion

We hope that this short blog post will be helpful if you happen to run into this issue. If this article does not solve your problem or you are running into other issues, please contact AWS Support or ask for help on the Elastic Beanstalk forum.

AWS SDK for PHP Office Hour

by Jeremy Lindblom | on | in PHP | | Comments

The AWS SDKs and Tools team invites you to the first-ever online office hour hosted by the maintainers of the AWS SDK for PHP. It will be held via Google Hangouts at 10:30-11:30am PDT (UTC -7:00) on Monday 6/29. If you don’t have one already, you will need to create an account with Google to join the video chat.

This first office hour will be driven by customer questions. We expect to focus on questions about the SDK, but any questions related to PHP development on AWS are welcome. We’re excited to meet you and help you be successful in developing PHP applications on AWS!

Please register for the event, add it to your calendar, and join the office hour next Monday.

Updated Framework Modules for V3

by Jeremy Lindblom | on | in PHP | | Comments

Last month, we announced that Version 3 of the AWS SDK for PHP was generally available. We’ve now updated all of our framework-specific modules with releases that support Version 3 (V3) of the SDK. Take a look!

We’ve also updated our AWS Resource APIs for PHP library which we previewed in December. Now that V3 of the SDK is stable, we will be working on adding features and documentation to this library over the coming weeks.

As always, we appreciate your feedback on any of our open source packages. Check out these updates and let us know what you think. :-)

P.S. We’d like to give a special thanks to Graham Campbell and Michaël Gallego for their contributions to the Laravel and ZF2 packages, respectively.

HaPHPy 20th Birthday to PHP

by Jeremy Lindblom | on | in PHP | | Comments

Twenty years ago, Rasmus Lerdorf announced version 1.0 of PHP. It’s now two decades later, and PHP has evolved so much and is still going strong. The AWS SDK for PHP team would like to say thank you to everyone who has contributed to the PHP language and community over these past twenty years, and wish PHP a very HaPHPy birthday.

Join in the celebration today by reflecting on the history of PHP, following the #20yearsofphp hashtag, and checking out some of these other blog posts from people in the PHP community:

Version 3 of the AWS SDK for PHP

by Jeremy Lindblom | on | in PHP | | Comments

Last October, we announced the Developer Preview of Version 3 of the AWS SDK for PHP. We even presented about it at AWS re:Invent last November. We are grateful for your early feedback and support. Since last fall, we’ve been hard at work on improving, testing, and documenting Version 3 to get it ready for a stable release. We’re excited to announce that Version 3 of the AWS SDK for PHP is now generally available via Composer and on GitHub.

Version 3 of the SDK (V3) represents a significant effort to improve the capabilities of the SDK, incorporate over two years of customer feedback, upgrade our dependencies, improve performance, and adopt the latest PHP standards.

What we’re excited about

We’ve made many improvements to V3, even since our blog post about the Developer Preview (check out that post if you haven’t already). There are also some things that have changed or have been removed since Version 2 of the SDK (V2). We encourage you to take a look at our V3 Migration Guide for all the details about what has changed.

V3 has less code and better performance than V2 and is using the latest version of the Guzzle HTTP library. It also has some exciting new features and improvements.

Asynchronous requests and promises

V3 allows you to execute operations asynchronously. This not only means that it is easier to do concurrent requests, it’s also easier to create asynchronous and cooperative workflows. We use Promises, the basic building block of our asynchronous features, all throughout the SDK’s core. We also use them to implement the higher-level abstractions SDK, including Command Pools, Paginators, Waiters, and service-specific features like the S3 MultipartUploader. That means that almost every feature of the SDK can be used in an asynchronous way.

To execute an operation asynchronously, you simply add "Async" as a suffix to your method call.

// The SYNCHRONOUS (normal) way:

// Executing an operation returns a Result object.
$result = $s3Client->putObject([
    'Bucket' => 'your-bucket',
    'Key'    => 'docs/file.pdf',
    'Body'   => fopen('/path/to/file.pdf', 'r'),
]);

// You can access the result data from the Result object.
echo $result['ObjectURL'];

// The ASYNCHRONOUS way:

// Executing an operation asynchronously returns a Promise object.
$promise = $s3Client->putObjectAsync([
    'Bucket' => 'your-bucket',
    'Key'    => 'docs/file.pdf',
    'Body'   => fopen('/path/to/file.pdf', 'r'),
]);

// Wait for the operation to complete to get the Result object.
$result = $promise->wait();

// Then you can access the result data like normal.
echo $result['ObjectURL'];

The true power of using asynchronous requests is being able to create asynchronous workflows. For example, if you wanted to create a DynamoDB table, wait until it is ACTIVE (using Waiters), and then write some data to it, you can use the then() method of the Promise object to chain those actions together.

$client->createTableAsync([
    'TableName' => $table,
    // Other params...
])->then(function () use ($client, $table) {
    return $client->getWaiter('TableExists', [
        'TableName' => $table,
    ])->promise();
})->then(function () use ($client, $table) {
    return $client->putItemAsync([
        'TableName' => $table,
        'Item' => [
            // Item attributes...
        ]
    ]);
})->wait();

Please take a look at our detailed guide on promises for more information.

PSR-7 compliance and decoupling of the HTTP layer

The PHP-FIG has recently announced the acceptance of PSR-7, a "PHP Standard Recommendation" that defines interfaces for HTTP messages (e.g., Request and Response objects). We have adopted these interfaces for how we represent HTTP requests within the SDK, and it has allowed us to decouple the SDK from Guzzle such that V3 will work with both Guzzle 5 and Guzzle 6. It’s also possible to write your own HTTP handler for the SDK that does not use Guzzle.

The SDK defaults to using Guzzle 6 to perform HTTP requests. Guzzle 6 comes with a number of improvements, including support for asynchronous requests, PSR-7 compliance, and swappable HTTP adapters (including a PHP stream wrapper implementation that can be used on systems where cURL is not available).

JMESPath querying of results and paginators

In V3, the Result object has a new method: search(). With this method you can query data in Result objects using JMESPath expressions. JMESPath is a query language for JSON, or, in our case, PHP arrays.

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

JMESPath expressions can also be applied to Paginators in the same way. This will return a new Iterator that yields the result of the expression on every page of data.

$results = $s3->getPaginator('ListObjects', [
    'Bucket' => 'my-bucket',
]);
foreach ($results->search('Contents[].Key') as $key) {
    echo $key . "n";
}

Time to code

We hope you will enjoy using Version 3 of the AWS SDK for PHP. Here are the links you need to get started: