How to connect DynamoDB with CakePHP4
Tags: dynamodb, cakephp, aws, awssdk
Published: 2024-07-10 16:08:33

## Introduction

When using DynamoDB, I struggled with connecting CakePHP and DynamoDB due to outdated articles and the need to create many files. Therefore, I wrote this article as a memo.

## What We'll Do in This Article
We will connect to DynamoDB using CakePHP4 and retrieve table information.

## Prerequisites
- Using EC2
    - OS Image: Amazon Linux 2 Kernel 5.10
- CakePHP Version: 4.2.12
- nginx: nginx/1.22.0
- PHP Version: PHP 7.4.33 (cli)
- PHP-FPM: PHP 7.4.33 (fpm-fcgi)
- AWS SDK for PHP Version: 3.257

## Preparation on AWS Side

### Creating a Table in DynamoDB

Create an appropriate table from AWS Console Home > DynamoDB > Tables > Create Table.

### Installing AWS SDK

While operations with AWS were done using the web console, the AWS SDK is necessary to use various services on AWS from a program.

The AWS SDK is a development kit (a set of APIs and libraries necessary for development) provided for different programming languages to operate AWS services from a program.

This time, we'll use the AWS SDK for PHP.
Install it using composer. ([Install Composer](https://getcomposer.org/download/))

```sh
cd project_base_directory
composer require aws/aws-sdk-php
```

If the aws directory is created within /vendor, the installation is successful.

### Creating an IAM Role

Like entering a password to log in from the console, authentication information is needed when accessing resources on AWS from a program.

Obtain the AWS access key as authentication information.

There are three ways to obtain the access key depending on the user type and authentication method. (Reference: [Obtaining AWS Access Keys](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SettingUp.DynamoWebService.html))

If accessing AWS as an IAM user, there are two ways:
- Obtain the access key directly from the IAM user (long-term credentials) ← AWS not recommended
- Create an IAM role and link it to the EC2 instance. Automatically obtain temporary credentials each time you access (short-term credentials)

This time, we adopted the method of sending requests to AWS using temporary credentials by utilizing an IAM role.

Create an IAM role with a policy that allows access to DynamoDB.

- IAM > Roles > Create Role
- Trusted Entity Type
    - AWS Service
- Use Case
    - EC2
- Permission Policy
    - AmazonDynamoDBFullAccess
- Role Name
    - role_test

Link the created IAM role to the EC2 instance.

- EC2 > Select Instance > Actions > Security > Modify IAM Role
- Select role_test and "Update IAM Role"

## Preparation on the PHP Side

### Instantiate Client Object

To operate the database on DynamoDB, instantiate the client object (this time DynamoDB object).

The client object contains methods for executing the service's API.

To instantiate, pass the configuration options as an associative array to the constructor (a method executed when the class is generated).

Although you can directly instantiate the client object as shown below, this time, we instantiate the Sdk class → then instantiate DynamoDB.

Example:
```php
//Create an S3Client
$s3 = new Aws\S3\S3Client([
    'version' => 'latest',
    'region' => 'us-east-2'
]);
```

By passing the associative array of options to the Sdk class, it can be used as a common set of configuration options for multiple clients.

This time, I used only DynamoDB, but if you want to use other AWS services like S3 at the same time, it's convenient as you don't need to write the configuration options each time.

Although only `region` and `version` values are set in the associative array of options, the access key is also referenced when the client object is generated, as the EC2 instance linked to the IAM role created earlier obtains temporary credentials.

This time, I created and described files under the plugin directory.

#### plugins/aws/Client/aws.php

```php
<?php
namespace aws\Client;

use Aws;
use Aws\DynamoDb;
use Cake\Core\Configure;

/**
 * Common AWS Class
 */
class AwsClient
{
    private $_config = null;
    private $_sdk = null;
    private $_dynamodb = null;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->_sdk = new Aws\Sdk([
            'region' => 'ap-northeast-1',
            'version' => 'latest',
        ]);
    }

    /**
     * Get DynamoDB Client
     *
     * @return $this->dynamodb DynamoDbClient
     */
    public function getDynamoDbClient()
    {
        if ($this->_dynamodb === null) {
            $this->_dynamodb = $this->_sdk->createDynamoDb();
        }

        return $this->_dynamodb;
    }
}
```

#### plugins/aws/DynamoDb.php

```php
<?php

namespace aws;

use aws\Client\AwsClient;
use Util\StrictComparison;

/**
 * DynamoDB Interface
 */
class DynamoDb
{
    private $_client = null;
    /**
     * Constructor
     *
     * @param \Aws\Client\AwsClient $awsClient AwsClient Object
     */
    public function __construct(AwsClient $awsClient = null)
    {
        if (StrictComparison::isNull($awsClient)) {
            $awsClient = new AwsClient();
        }
        $this->_client = $awsClient->getDynamoDbClient();
    }

    /**
     * Get record by key name
     * 
     * @param string $tableName Table name
     * @param array $key Primary key
     * @return object Matching record
    */
    public function getItemByKey(string $tableName, array $key)
    {
        return $this->_client->getItem([
            'Key' => $key,
            'TableName' => $tableName,
        ]);
    }
}
```

### Retrieve Table Information

With the necessary processes to connect to DynamoDB written, finally, instantiate the DynamoDB class in the Controller and retrieve the record by the specified key value.

#### src/Controller/DynamoDbController.php

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

namespace App\Controller;

use Cake\Controller\Controller;
use aws\DynamoDb;

class DynamoDbController extends Controller
{
    public $tableName = 'created_table_name';
    public $key = [
        'key_name' => [
            'S' => 'value_to_search'
        ],
    ];

    public function index()
    {
        $dynamoDb = new DynamoDb();
        $result = $dynamoDb->getItemByKey($this->tableName, $this->key);
        // Pass the value of $result to the template so that the obtained content can be used on the view side.
        $this->set(compact('result'));
    }
}
```

You should be able to confirm that the target record is obtained by checking the result with var_dump, etc.

## Summary

Using this procedure, it seems possible to connect not only to DynamoDB but also to other AWS services.

Personally, it was very educational as I had only used Composer for loading packages, and now I know it is also necessary to use it when loading class files.

## References
- Creating IAM Roles
    - [Using temporary credentials with AWS resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html)
- Setting Namespace
    - [PHP Autoload (autoload)](https://qiita.com/atwata/items/5ba72d3d881a81227c2a)
- Instantiating Client Object
    - [Creating a Client](https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/getting-started_basic-usage.html)
- Retrieving Table Information
  - [Read items from a DynamoDB table using the AWS SDK](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.ReadItem.html)