Amazon DynamoDBでphpのセッションを管理する

# はじめに
AWS DynamoDBでphpのセッションを管理します。
マルチAZの場合、複数のEC2でセッション情報を同期する必要があります。

その場合、候補に上がるのが、 DynamoDBかElastiCacheになるはずです。


**1. IAMポリシーの作成**
EC2インスタンスにアタッチしているロールに、このポリシーを割り当てます。

```json:php-sessions
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:Scan",
                "dynamodb:BatchWriteItem"
            ],
            "Resource": "arn:aws:dynamodb:ap-northeast-1:{AWSのアカウントID}:table/{DynamoDBのテーブル名}"
        }
    ]
}
```
**2. DynamoDBの作成**
DynamoDBを作成します。

|**項目**|**値**|
|---|---|
|テーブル名|任意|
|プライマリキー|id (文字列)|

テーブル作成後、「概要->TTLの管理」から、 TTLを有効化にします。

|**項目**|**値**|
|---|---|
|TTL属性|expired (任意)|

**3. EC2インスタンスにphpのSDKをインストールする**
EC2インスタンスにcomposerを利用して、phpのSDKをインストールします。

```shell
:スクリプト
# composer設定
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
    >&2 echo 'ERROR: Invalid installer signature'
    rm composer-setup.php
    exit 1
fi

php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php

sudo mv composer.phar /usr/local/bin/composer
sudo yum install -y --enablerepo=remi,remi-php70 php-xml
sudo rm composer*
composer require aws/aws-sdk-php
```

**4. phpからの利用方法**
セッション管理用のクラスを作成します。
シングルトンパターンを利用します。

```php:SessionDynamoDB.php
<?php

# 任意のディレクトリに配置したautoload.phpを呼び出す
require_once(dirname(__FILE__) . '/autoload.php');

use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\SessionHandler;
use Aws\DynamoDb\Exception\DynamoDbException;

interface SessionDynamoDBInterface
{
    //インスタンスを生成する。
    public static function getInstance();

    //ハンドラーを登録する。
    public function start();
}

class SessionDynamoDB implements SessionDynamoDBInterface
{

    private static $singleton;
    private $client;
    private $sessionHandler;

    /**
     * DynamoDBクライアント生成
     */
    private function __construct()
    {
        try {
            $this->client = DynamoDbClient::factory([
                'region' => 'ap-northeast-1',
                'version' => 'latest',
            ]);
            
            $this->sessionHandler = SessionHandler::fromClient($this->client, [
                'table_name'               => '{DynamoDBのテーブル名}',
                'hash_key'                 => 'id',
                'session_lifetime'         => 3600,
                'consistent_read'          => true,
                'locking'                  => false,
                'batch_config'             => [],
                'max_lock_wait_time'       => 10,
                'min_lock_retry_microtime' => 5000,
                'max_lock_retry_microtime' => 50000,
            ]);
            $this->sessionHandler->register();

            // Start the session
            session_start();

            // // Close the session (optional, but recommended)
            session_write_close();

        } catch(DynamoDbException $e){
            throw new DynamoDbException ($e->getMessage());
        }
    }

    /**
     * インスタンスを生成する。
     *
     * @return object self::$singleton
     */
    public static function getInstance(): object
    {
        if (!isset(self::$singleton)) {
            self::$singleton = new SessionDynamoDB();
        }
        return self::$singleton;
    }

    /**
     * このインスタンスの複製を許可しないようにする
     *
     * @throws RuntimeException
     */
    public final function __clone()
    {
        throw new RuntimeException ('Clone is not allowed against ' . get_class($this));
    }

    /**
     * ハンドラーを登録する。
     *
     */
    public function start(): void
    {
        $this->sessionHandler->register();
        
        // Start the session
        session_start();
    }

}
```

クラスからセッションを利用します。

```php:php実行ファイル
# 任意のディレクトリに配置したSessionDynamoDB.phpを呼び出す
require_once(dirname(__FILE__) . '/SessionDynamoDB.php');

$session = SessionDynamoDB::getInstance();
$session->start();

# 冒頭に上記の記述をすれば、あとは普通に$_SESSIONを利用するだけです。
var_dump($_SESSION);

```