Laravel 6.x（またはLumen 6.x）でDynamoDBに接続する

#SDKのインストール
[AWS SDK for PHP](https://aws.amazon.com/jp/sdk-for-php/)をLaravelに取り込みましょう。
プロジェクトルートで下記コマンドを実行します。

```bash
$ composer require aws/aws-sdk-php
```
これでvendor配下にSDKがインストールされます。

# DynamoDBに接続する
AWS SDK for PHPでつなぐにはこのようにします。

**下記の例ではクレデンシャルを含めていますが、この方法は推奨されていないので注意してください**
[ハードコードされた認証情報の使用](https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/guide_credentials_hardcoded.html)

```php
use Aws\Sdk;

$awsSdk = new Sdk([
    'credentials' => [
        'key' => アクセスキー,
        'secret' => シークレットキー,
    ],
    'endpoint' => 'エンドポイント',
    'region'   => 'リージョン',
    'version'  => 'バージョン'
]);
$dynamo = $awsSdk->createDynamoDb();
```

以上。



# Laravelらしくしよう
と、まあこれじゃあちょっとLaravelらしくないよね、ってことでDIしてみます。new禁止。

### envに登録
dotEnv便利ですよね。

```php:.env
AWS_DYNAMO_KEY=アクセスキー
AWS_DYNAMO_SECRET=シークレットキー
AWS_DYNAMO_ENDPOINT=エンドポイント
AWS_DYNAMO_REGION=リージョン
AWS_DYNAMO_VERION=バージョン
```

### ServiceProviderを記述
LaravelでDIといえばServiceProviderですよね。

```php:app/Providers/DynamoDbServiceProvider.php
<?php
declare(strict_types=1);

namespace App\Providers;

use Aws\DynamoDb\DynamoDbClient;
use Aws\Sdk;
use Illuminate\Support\ServiceProvider;

class DynamoServiceProvider extends ServiceProvider
{
    /**
     * Dynamo AWS-SDK接続
     */
    public function register()
    {
        $this->app->singleton(DynamoDbClient::class, function () {
            $awsSdk = new Sdk([
                'credentials' => [
                    'key' => env('AWS_DYNAMO_KEY'),
                    'secret' => env('AWS_DYNAMO_SECRET'),
                ],
                'endpoint' => env('AWS_DYNAMO_ENDPOINT'),
                'region' => env('AWS_DYNAMO_REGION'),
                'version' => env('AWS_DYNAMO_VERSION')
            ]);
            return $awsSdk->createDynamoDb();
        });
    }
}
```

Laravelなら config/app.phpのproviders に登録。

```php:config/app.php
    'providers' => [
                App\Providers\DynamoServiceProvider::class,
```

Lumenならbootstrap/app.phpに登録します。

```php:bootstrap/app.php
    $app->register(App\Providers\DynamoServiceProvider::class);
```

### コンストラクタインジェクション
ここまでできたら、お好きな場所でインジェクションできます。Facadeにしてもいいかもですね。

```php
    /**
     * @var DynamoDbClient
     */
    private $client;

    /**
     * constructor.
     * @param DynamoDbClient $client
     */
    public function __construct(DynamoDbClient $client)
    {
        $this->client = $client;
    }
```

# Eloquentっぽくしよう
Dynamoにアクセスするのに毎回

```php
$this->client->putItem($paramertes);
```
とかって書くのめんどいですよね。
普段使い慣れているEloquentやQueryBuilderっぽく、find()とかsave()とかdestory()とか使いたいです。

じゃあ、EloqentModelに変わる基底クラスを作っちゃいましょう。

### DynamoModelを作る
たとえばこんな感じでしょうか。

```php
<?php
declare(strict_types=1);

namespace App\Models\Dynamo;

use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Exception\DynamoDbException;
use Aws\DynamoDb\Marshaler;
use Aws\Result;
use Illuminate\Support\Str;

/**
 * Class DynamoModel
 * @package App\Models\Dynamo
 */
class DynamoModel
{
    /**
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * @var string|null
     */
    protected $table = null;

    /**
     * @var DynamoDbClient
     */
    private $client;

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

    /**
     * DynamoModel constructor.
     * @param DynamoDbClient $client
     * @param Marshaler $marshaler
     */
    public function __construct(DynamoDbClient $client, Marshaler $marshaler)
    {
        $this->client = $client;
        $this->marshaler = $marshaler;
    }

    /**
     * @return string
     */
    public function getTable(): string
    {
        $tableName = $this->table ?? Str::snake(Str::pluralStudly(class_basename($this)));
        return env('APP_ENV', 'local') . '_' . $tableName;
    }

    /**
     * DynamoにINSERT
     * @param array $attributes
     * @return Result
     */
    public function save(array $attributes): Result
    {
        $marshalItem = $this->marshaler->marshalItem($attributes);
        $dynamoParameters = [
            'TableName' => $this->getTable(),
            'Item' => $marshalItem
        ];

        try {
            return $this->client->putItem($dynamoParameters);
        } catch (DynamoDbException $exception) {
            throw $exception;
        }
    }

    /**
     * Dynamoからレコード削除
     * @param array $ids
     */
    public function destroy(array $ids): void
    {
        foreach ($ids as $id) {
            $deleteParameter = [$this->primaryKey => $id];
            $marshalItem = $this->marshaler->marshalItem($deleteParameter);
            $dynamoParameters = [
                'TableName' => $this->getTable(),
                'Key' => $marshalItem
            ];
            try {
                $this->client->deleteItem($dynamoParameters);
            } catch (DynamoDbException $exception) {
                throw $exception;
            }
        }
    }

    /**
     * Dynamoから１件レコードを取得
     * @param int $id
     * @return array|null
     */
    public function find(int $id): ?array
    {
        $idParameter = [$this->primaryKey => $id];
        $marshalItem = $this->marshaler->marshalItem($idParameter);
        $dynamoParameters = [
            'TableName' => $this->getTable(),
            'Key' => $marshalItem
        ];
        try {
            $awsResult = $this->client->getItem($dynamoParameters)->get('Item');
            if (is_null($awsResult)) {
                return null;
            }
            return $this->marshaler->unmarshalItem($awsResult);
        } catch (DynamoDbException $exception) {
            throw $exception;
        }
    }
}
```

あとはこれを継承したModelをつかえば、EloquentみたいにDynamoにアクセスできるようになります。
必要に応じで、findOrFail()でもupdateOrCreate()でもはやしていけばいいのかなーと思います。