A lightweight, no-frills ODM (Object Document Mapper) for DynamoDB.
NOTE: This library only works with the 2.x version of the AWS SDK for PHP. The changes in the 3.x version of the SDK make it much easier to work with, and this library provides less value than before (which is a good thing, less code!). It is unclear as to whether or not a port to the 3.x SDK will be useful.
Amazon provides an SDK that connects to DynamoDB. Why would you want to use an ODM on top of it?
- Allows developers to define their data model in the codebase
- Facilitates readable code by wrapping complex data structures with an OO API
- Adds logical extension points via Symfony's EventDispatcher component
- Optionally enforces entity integrity
- Facilitates password hashing, data encryption, random string generation, etc.
DynamoDB ODM can be installed with Composer by adding it as a dependency to your project's composer.json file.
{
"require": {
"cpliakas/dynamo-db-odm": "*"
}
}
Please refer to Composer's documentation for more detailed installation and usage instructions.
Entities are classes that extend Cpliakas\DynamoDb\ODM\Entity
and model
different types of documents. Metadata, such as the table name and hash /
range key attributes, are defined in static properties and accessed through the
static methods defined in Cpliakas\DynamoDb\ODM\EntityInterface
.
namespace Acme\Entity;
use Aws\DynamoDb\Enum\Type;
use Cpliakas\DynamoDb\ODM\Entity;
class Book extends Entity
{
// The DynanoDB table name
protected static $table = 'books';
// The attribute containing the hash key
protected static $hashKeyAttribute = 'isbn';
// Optionally set the $rangeKeyAttribute static if appropriate
// Optionally enforce entity integrity
protected static $enforceEntityIntegrity = true;
// Optionally map attributes to data types
protected static $dataTypeMappings = array(
'isbn' => Type::STRING,
);
// Optionally add attribute setters and getters to taste
public function setIsbn($isbn)
{
$this->setAttribute('isbn', $isbn);
return $this;
}
public function getIsbn()
{
return $this->getAttribute('isbn');
}
}
NOTE: Other ODMs use annotations to define metadata. This pattern can improve DX for applications with a large number of entities and improve performance when proper caching is implemented. However, this library intentionally chooses to use statics to define metadata since it is a lighter-weight solution for the applications this project is intended to be used in.
The document manager is responsible for instantiating entity classes and reading / writing documents to DynamoDB.
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
use Cpliakas\DynamoDb\ODM\DocumentManager;
$dynamoDb = DynamoDbClient::factory(array(
'key' => '<public-key>',
'secret' => '<secret-key>',
'region' => '<aws-region>',
));
$dm = new DocumentManager($dynamoDb);
// Register one or more namespaces that contain entities in order to avoid
// having to pass the fully qualified class names as arguments.
$dm->registerEntityNamesapce('Acme\Entity');
Create a document.
// Instantiate the entity object to model the new document. "Book" is the
// entity's class name as defined in the "Defining Entities" example above.
$book = $dm->entityFactory('Book')
->setHashKey('0-1234-5678-9')
->setAttribute('title', 'The Book Title')
->setAttribute('author', 'Chris Pliakas')
;
// Documents can also act like arrays
$book['copyright'] = 2014;
// Save the document
$dm->create($book);
// Bulk insert
foreach($books as $book) {
$dm->createBatch($book);
}
$dm->flush();
Read, update, and delete the document.
// Read the document
$book = $dm->read('Book', '0-1234-5678-9');
// Update the document
$book['title'] = 'Revised title';
$dm->update($book);
// Delete the document
$dm->delete($book);
// Bulk delete
foreach($books as $book) {
$dm->deleteBatch($book);
}
$dm->flush();
NOTE: Other ODMs use the unit of work pattern when persisting data to the backend. Due to the nature of DynamoDB and the desire to keep this library lightweight, we opted not to use this pattern.
Pass an array as the primary key parameter when an entity's table uses a hash and range primary key type.
// Assume that the "Thread" entity's table uses the hash and range primary key
// type containing the forumName and subject attributes.
// Load the document by the hash and range keys
$book = $dm->read('Thread', array('PHP Libraries', 'Using the DynamoDB ODM'));
You can either pass the raw data structure defined by the AWS SDK for PHP as the second parameter or use the object oriented wrapper to build the search conditions. The example below uses the OO wrapper.
use Aws\DynamoDb\Enum\ComparisonOperator;
// Search for books published after 2010 that don't have the title "Do not read me"
$conditions = Conditions::factory()
->addCondition('title', 'Do not read me', ComparisonOperator::NE)
->addCondition('copyright', 2010, ComparisonOperator::GT)
;
// Search for books with existing attribute 'extra'
$conditions = Conditions::factory()
->addNotNullCondition('extra')
;
$result = $dm->scan('Book', $conditions);
Transformers convert attribute values set through the entity object to something else.
The following example builds upon the book entity above to transform \DateTime
objects set as the created
attribute to Unix timestamps.
namespace Acme\Entity;
use Cpliakas\DynamoDb\ODM\Entity;
use Cpliakas\DynamoDb\ODM\Renderer as Renderer;
use Cpliakas\DynamoDb\ODM\Transformer as Transformer;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class Book extends Entity
{
// Set statics here ...
public function __construct(EventDispatcherInterface $dispatcher, $data = array())
{
parent::__construct($dispatcher, $data);
$this->addTransformer('created', new Transformer\Date());
}
}
Set a \DateTime
object as the created
attribute and create the document.
$time = new \DateTime();
$book = $dm->entityFactory('Book')
->setHashKey('0-1234-5678-9')
->setAttribute('created', $time)
;
$dm->create($book);
The value is stored as a Unix timestamp in DynamoDB.
Renderers convert the value stored in DynamoDB to something that is normalized or native to PHP when it is accessed.
The following example is the opposite of the use case above. It converts the
Unix timestamp stored in DynamoDB to a \DateTime
object.
Add the following statement to the Book
object's constructor like we did with
the transformer.
$this->addRenderer('created', new Renderer\Date());
Read the document from DynamoDB. Accessing the created
attribute will return
a \DateTime
object.
$book = $dm->read('Book', '0-1234-5678-9');
echo $book['created']->format(\DateTime::ATOM);