AWS LambdaでPHP動かして DynamoDBを操作する

## はじめに
この記事は、[駆け出しエンジニアのための Advent Calendar 2018](https://adventar.org/calendars/3327?fbclid=IwAR0Cw8grQRQs4hQi62VJCj2Y9djqufH56V3avyuJCN8D8C9ofDbXIKsYuDM)の15日目の記事です

## re:Invent2018

先日ラスベガスで開催された、AWS re:Invent2018にて、
AWS Lambdaに、`Custom Runtime`という機能が追加され、
Lambdaのサポート外の言語もLambda上で実行可能となりました。

今回は、PHPをLambda上で動かすために、STACKERYより提供されているこちらの[PHPの Custom Runtime](https://github.com/stackery/php-lambda-layer)を使用し、
Lambda上のPHPからDynamodbを操作するためにAWSが提供している[AWS SDK for PHP](https://aws.amazon.com/jp/sdk-for-php/)を使います。


## 環境

- [AWS CLI](https://docs.aws.amazon.com/ja_jp/streams/latest/dev/kinesis-tutorial-cli-installation.html)
デプロイ用

- [AWS SAM CLI](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
デプロイ用

- [Composer](https://qiita.com/kakijin/items/02364adacf36410f449e)
`AWS SDK for PHP`をインストールするために使用します。

インストールはリンクのURLを参考にしてください

## 開発準備

- 処理対象となる、DynamoDBを作成する

- 以下IAMポリシーがあるロールを作成する
 - AmazonDynamoDBFullAccess
 - AWSLambdaDynamoDBExecutionRole

- デプロイ用にファイルを上げるS3バケットを作成する

- 以下の構成でディレクトリを作成

```
|-- src
|   `-- php
`-- template.yml　//AWS SAM CLI のデプロイ時に必要な構成ファイル
```

- AWS SDK for PHPのインストール

```
$ php composer require aws/aws-sdk-php
```

- php.iniの編集

今回使用しているPHPのCustom Runtime上ではデフォルトで、`json.so`の機能が使えません。
`AWS SDK for PHP`では内部で`json.so`を使用しているため有効化する必要があります。

PHPのCustom Runtime上で、`json.so`を有効化するために、php.iniファイルを作成し、
以下のように`extension` を追加します。

```ini:php.ini
extension=json.so
```
こちらについては以下のカスタムランタイムのGithubのREADMEにて解説があります。
https://github.com/stackery/php-lambda-layer


## プログラムの作成

データ登録処理

```php:post.php
<?php
require 'vendor/autoload.php';

use Aws\Sdk;
use Aws\DynamoDb\Marshaler;
use Aws\DynamoDb\Exception\DynamoDbException;

$tableName = 'php_test'; //対象のテーブル名
$name = $_GET["name"]; //GETでリクエストされたデータ(name)を取得

$sdk = new Sdk([
    'region'   => 'ap-northeast-1', //使用するregion情報
    'version'  => 'latest'
]);

$dynamodb = $sdk->createDynamoDb();
$marshaler = new Marshaler();

$uid = uniqid(); //登録用にuuidを作成

$item = $marshaler->marshalJson('{
        "id": "' . $uid . '",
        "name": "'. $name .'"
    }
');

$params = [
    'TableName' => $tableName,
    'Item' => $item
];

try {
    $result = $dynamodb->putItem($params);
    echo "Added item success id:". $uid;
} catch (DynamoDbException $e) {
    echo "Unable to add item:\n";
    echo $e->getMessage() . "\n";
}
```

データ取得処理

```php:getphp
<?php
require 'vendor/autoload.php';

use Aws\Sdk;
use Aws\DynamoDb\Marshaler;
use Aws\DynamoDb\Exception\DynamoDbException;

$tableName = 'php_test'; //対象のテーブル名
$id = $_GET["id"]; //GETでリクエストされたデータ(id)を取得

$sdk = new Sdk([
    'region'   => 'ap-northeast-1', //使用するregion情報
    'version'  => 'latest'
]);

$dynamodb = $sdk->createDynamoDb();
$marshaler = new Marshaler();

$key = $marshaler->marshalJson('{
        "id": "' . $id . '"
    }
');

$params = [
    'TableName' => $tableName,
    'Key' => $key
];

try {
    $result = $dynamodb->getItem($params);
    print_r($result["Item"]);
} catch (DynamoDbException $e) {
    echo "Unable to get item:\n";
    echo $e->getMessage() . "\n";
}

```

最終的なディレクト構成　以下ようになります

```
|-- src
|   `-- php
|       |-- vendor //AWS SDK for PHP
|       |-- composer.json //Composer file
|       |-- composer.lock //Composer file
|       |-- get.php //データ取得用処理
|       |-- php.ini // php.ini
|       `-- post.php //データ登録用処理
`-- template.yml
```

AWS SAM CLI 用のデプロイファイル

```yml:template.yml
AWSTemplateFormatVersion: 2010-09-09
Description: My PHP Application
Transform: AWS::Serverless-2016-10-31
Resources:
  phppost:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub test-phppost
      Description: PHP Post
      Role: [作成したロール情報]
      CodeUri: src/php
      Runtime: provided
      Handler: post.php
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Layers:
        - !Sub arn:aws:lambda:[region]:887080169480:layer:php71:5
      Events:
        api:
          Type: Api
          Properties:
            Path: /post
            Method: ANY

  phpget:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub test-phpget
      Description: PHP Get
      Role: [作成したロール情報]
      CodeUri: src/php
      Runtime: provided
      Handler: get.php
      MemorySize: 3008
      Timeout: 30
      Tracing: Active
      Layers:
        - !Sub arn:aws:lambda:[region]:887080169480:layer:php71:5
      Events:
        api:
          Type: Api
          Properties:
            Path: /get
            Method: ANY
```

## デプロイ

パッケージしてS３バケットへアップロード

```
$ sam package --template-file template.yml --output-template-file serverless-output.yaml --s3-bucket [作成したS３バケット]
```

デプロイ

```
sam deploy --template-file serverless-output.yaml --stack-name custom-runtime-php71  --capabilities CAPABILITY_IAM
```

## 動作検証

API　Gatewayも同時に作成されているので、自動的に作成されたURLを確認し動作検証する

- 登録処理
　
登録する内容をURLパラメータ `name` をリクエストする

```
https://xxxxxxx.execute-api.[resion].amazonaws.com/Prod/post?name=tanaka
```
↓
レスポンスとして、発行されたIDが返却される

```
Added item success id:5c14940af36b0
```

- データ取得処理

発行されたIDを取得用のキーとするためURLパラメータ `id` をリクエストする

```
https://xxxxxx.execute-api.[resion].amazonaws.com/Prod/get?id=5c14940af36b0
```
↓
以下のようにDynamoDBから取得したデータが表示される

```
Array ( [id] => Array ( [S] => 5c1524cec4aa7 ) [name] => Array ( [S] => tanaka ) )
```

DynamoDBを確認するとリクエストしたデータが登録されているはずです

## 参考
https://dev.classmethod.jp/cloud/aws/php-vpc-lambda-alb-rds/
https://dev.classmethod.jp/cloud/aws/lambda-custom-runtime-php-extension
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GettingStarted.PHP.03.html