Laravel+AWS-SDK-PHPによるDynamoDBのデータ取得

# 背景
LaravelでDynamoDB使うにあたり、ORMのlaravel-dynamodbなどもあるが、DynamoDBでORM利用するのが少し違和感あった為にSDK使ってやってみた際のメモ（そもそもPHPのWEBフレームワークでやるなという話ではあるが、せっかくSDKが提供されているので一度使ってみようという感じ）
 
# 環境
・Windows 10
・HomeStead 9.5
・PHP 7.4
・Laravel 6.0

# １．準備
## AWS-SDK-PHPのインストール

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

## DynamoDBの環境作成
AWS提供のローカルDynamoDBを利用。Vagrantを使っていたので、Dockerと共存が面倒なことになるのでローカルに直接インストールしました。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html

※GUIツール（結構便利）
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.settingup.html

## DynamoDB起動
```cmd
java -Djava.library.path=C:\localsandbox\DynamoDB\DynamoDBLocal_lib -jar C:\localsandbox\DynamoDB\DynamoDBLocal.jar -port 8001 -sharedDb
```
※HomesteadのVirtualBoxと初期ポート8000が被るので、8001で起動させる


## テスト用テーブルの作成
テーブル名：SensorLog
パーティションキー：device_id
ソートキー：receive_dt

```
aws dynamodb create-table --table-name SensorLog --attribute-definitions AttributeName=device_id,AttributeType=S AttributeName=receive_dt,AttributeType=N --key-schema AttributeName=device_id,KeyType=HASH AttributeName=receive_dt,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 --endpoint-url http://localhost:8001 
```
※キーのない項目はテーブル作成時に指定できないので、テストデータ作成時に適当に作る。
※テストデータはGUIで適当に作成

## .envに情報設定
```
DYNAMODB_REGION=ap-northeast-1
#↓vagrantのlocalhost:xxxxだとつながらないのでローカルIPを設定
DYNAMODB_LOCAL_ENDPOINT=http://192.168.11.99:8001
DYNAMODB_DEBUG=true
```

# ２．データ取得

・パーティションキーで絞ってデータ検索した結果の最新を取得する。
・パーティションキー＋ソートキー（範囲指定）による複合条件検索の結果リストを取得する

```php
use Aws\DynamoDb\Exception\DynamoDbException;
use Aws\DynamoDb\Marshaler;
use Aws\Sdk as AwsSdk;

class SensorLog
{
    /**
     * @var DynamoDbClient
     */
    private $client;

    /**
     * @var Marshaler
     */
    private $marshaler;

    /**
     * DynamoModel constructor.
     */
    public function __construct()
    {
        // ローカルDBであっても"credentials"定義しないとエラーが吐かれるので注意
        $sdk = new AwsSdk([
            'credentials' => [
                'key' => env('DYNAMODB_KEY'),
                'secret' => env('DYNAMODB_SECRET'),
            ],
            'endpoint'   => env("DYNAMODB_LOCAL_ENDPOINT"),
            'region'   => env("DYNAMODB_REGION"),
            'version'  => 'latest'
        ]);
        
        $this->client = $sdk->createDynamoDb();
        $this->marshaler = new Marshaler();
    }

    /**
     * パーティションキー指定による最新データを取得する
     */
    public function getFirst($id)
    {
        $tableName = 'SensorLog';

        // 検索条件設定：device_idを検索条件にする
        $key_value = $id;
        $jsonStr = '{'.'":device_id":'.'"'.$key_value.'"'.'}';
        $eav = $this->marshaler->marshalJson($jsonStr);
    
        // Limit:1 取得件数を1件に制限
        // ScanIndexForward:false ソートキー(receive_dt)を降順指定
        $params = [
            'TableName' => $tableName,
            'KeyConditionExpression' => '#id = :device_id',
            'ExpressionAttributeNames'=> [ '#id' => 'device_id' ],
            'Limit' => 1,
            'ScanIndexForward' => false,
            'ExpressionAttributeValues'=> $eav,
        ];
    
        try {
            $result = $this->client->query($params);
            $data = array();
            foreach ($result['Items'] as $recdData) {
                $data[] =  $this->marshaler->unmarshalItem($recdData);
            }
            return $data;
        } catch (DynamoDbException $e) {
            throw $e;
        }
    }

    /**
     * パーティションキー＋対象日付のデータリストを取得する
     */
    public function getDataByDateTime(int $id, string $dt)
    {
        $tableName = 'SensorLog';

        // 対象日付の最小時間と最大時間を設定する
        // receive_dt:YYYYMMDDHHMMSSの値は数値型で格納されている
        $from_dt = $dt."000000";
        $to_dt = $dt."235959";

        // バインド変数を設定(device_id,from_dt,to_dt)
        $key_value = $id;
        $jsonStr = '{' . '":device_id":' . '"' .$key_value.'",":from_dt":'.$from_dt.',":to_dt":'.$to_dt.'}';
        $eav = $this->marshaler->marshalJson($jsonStr);
    
        // device_idとreceive_dtによる範囲検索を行う
        // ScanIndexForward:false ソートキー(receive_dt)を降順指定
        $params = [
            'TableName' => $tableName,
            'KeyConditionExpression' => '#id = :device_id and receive_dt between :from_dt and :to_dt',
            'ExpressionAttributeNames'=> [ '#id' => 'device_id' ],
            'ScanIndexForward' => false,
            'ExpressionAttributeValues'=> $eav,
        ];
    
        try {
            $result = $this->client->query($params);
            $data = array();
            foreach ($result['Items'] as $recdData) {
                $data[] =  $this->marshaler->unmarshalItem($recdData);
            }
            return $data;
        } catch (DynamoDbException $e) {
            throw $e;
        }
    }
}
```

## 呼び出し
・コントローラーで呼び出し

```php
//１．指定IDの最新データを1件取得
$sensorLog = $sensor->getFirst($id);
//２．2020-08-06のデータを検索して結果リストを取得
$sensorLogList = $sensor->getDataByDateTime($id, "20200806");
dump($sensorLogList);
```

・結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604715/76880494-a861-c46a-f05b-35ff23c3d3f4.png)

# 参考
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GettingStarted.PHP.04.html