mirror of
https://github.com/doctrine/KeyValueStore.git
synced 2026-03-24 00:32:09 +01:00
Merge pull request #96 from doctrine/dynamodb-refactoring
DynamoDB internal refactoring
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
language: php
|
||||
|
||||
services:
|
||||
- docker
|
||||
- mongodb
|
||||
- redis-server
|
||||
|
||||
@@ -26,6 +27,7 @@ cache:
|
||||
apt: true
|
||||
|
||||
before_install:
|
||||
- docker run -d -p 8000:8000 amazon/dynamodb-local
|
||||
- sudo apt-get install -y --allow-unauthenticated riak
|
||||
- sudo service riak start
|
||||
- pecl install --force mongodb
|
||||
|
||||
@@ -58,6 +58,7 @@ So far the following drivers exist (and are documented here):
|
||||
* Microsoft Windows Azure Table
|
||||
* Couchbase
|
||||
* CouchDB
|
||||
* DynamoDB
|
||||
* MongoDB
|
||||
* Riak
|
||||
|
||||
@@ -209,17 +210,9 @@ See the `AWS docs <http://docs.aws.amazon.com/amazondynamodb/latest/developergui
|
||||
|
||||
<?php
|
||||
|
||||
$sdk = new \Aws\Sdk([...]);
|
||||
$client = $sdk->createDynamoDb();
|
||||
$client = DynamoDbClient::factory([...])
|
||||
|
||||
$storage = new DynamoDbStorage(
|
||||
$client,
|
||||
// Optional key name, defaults to Id.
|
||||
null,
|
||||
// Optional table name/ key name pairs.
|
||||
// This example uses a table called Awesome keyed by MyKey.
|
||||
['storage_keys' => ['Awesome' => 'MyKey']]
|
||||
);
|
||||
$storage = new DynamoDbStorage($client);
|
||||
|
||||
MongoDB
|
||||
-------
|
||||
|
||||
@@ -22,40 +22,21 @@ namespace Doctrine\KeyValueStore\Storage;
|
||||
|
||||
use Aws\DynamoDb\DynamoDbClient;
|
||||
use Aws\DynamoDb\Marshaler;
|
||||
use Doctrine\KeyValueStore\InvalidArgumentException;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\KeyValueStore\NotFoundException;
|
||||
|
||||
/**
|
||||
* DyanmoDb storage
|
||||
* DynamoDb storage.
|
||||
*
|
||||
* @author Stan Lemon <stosh1985@gmail.com>
|
||||
*/
|
||||
class DynamoDbStorage implements Storage
|
||||
{
|
||||
/**
|
||||
* The key that DynamoDb uses to indicate the name of the table.
|
||||
* @var DynamoDbClient
|
||||
*/
|
||||
const TABLE_NAME_KEY = 'TableName';
|
||||
|
||||
/**
|
||||
* The key that DynamoDb uses to indicate whether or not to do a consistent read.
|
||||
*/
|
||||
const CONSISTENT_READ_KEY = 'ConsistentRead';
|
||||
|
||||
/**
|
||||
* The key that is used to refer to the DynamoDb table key.
|
||||
*/
|
||||
const TABLE_KEY = 'Key';
|
||||
|
||||
/**
|
||||
* The key that is used to refer to the marshaled item for DynamoDb table.
|
||||
*/
|
||||
const TABLE_ITEM_KEY = 'Item';
|
||||
|
||||
/**
|
||||
* @var \Aws\DynamoDb\DynamoDbClient
|
||||
*/
|
||||
protected $client;
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* @var Marshaler
|
||||
@@ -63,158 +44,27 @@ class DynamoDbStorage implements Storage
|
||||
private $marshaler;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var Cache
|
||||
*/
|
||||
private $defaultKeyName = 'Id';
|
||||
private $descriptionCache;
|
||||
|
||||
/**
|
||||
* A associative array where the key is the table name and the value is the name of the key.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tableKeys = [];
|
||||
|
||||
/**
|
||||
* @param DynamoDbClient $client The client for connecting to AWS DynamoDB.
|
||||
* @param Marshaler|null $marshaler (optional) Marshaller for converting data to/from DynamoDB format.
|
||||
* @param string $defaultKeyName (optional) Default name to use for keys.
|
||||
* @param array $tableKeys $tableKeys (optional) An associative array for keys representing table names and values
|
||||
* representing key names for those tables.
|
||||
* @param DynamoDbClient $client The client for connecting to AWS DynamoDB
|
||||
* @param Marshaler|null $marshaler (optional) Marshaller for converting data to/from DynamoDB format
|
||||
* @param Cache|null $descriptionCache Cache used to store tables description
|
||||
*/
|
||||
public function __construct(
|
||||
DynamoDbClient $client,
|
||||
Marshaler $marshaler = null,
|
||||
$defaultKeyName = null,
|
||||
array $tableKeys = []
|
||||
DynamoDbClient $client,
|
||||
Marshaler $marshaler = null,
|
||||
Cache $descriptionCache = null
|
||||
) {
|
||||
$this->client = $client;
|
||||
$this->client = $client;
|
||||
$this->marshaler = $marshaler ?: new Marshaler();
|
||||
|
||||
if ($defaultKeyName !== null) {
|
||||
$this->setDefaultKeyName($defaultKeyName);
|
||||
}
|
||||
|
||||
foreach ($tableKeys as $table => $keyName) {
|
||||
$this->setKeyForTable($table, $keyName);
|
||||
}
|
||||
$this->descriptionCache = $descriptionCache ?: new ArrayCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a DynamoDB key name.
|
||||
*
|
||||
* @param $name mixed The name to validate.
|
||||
*
|
||||
* @throws InvalidArgumentException When the key name is invalid.
|
||||
*/
|
||||
private function validateKeyName($name)
|
||||
{
|
||||
if (! is_string($name)) {
|
||||
throw InvalidArgumentException::invalidType('key', 'string', $name);
|
||||
}
|
||||
|
||||
$len = strlen($name);
|
||||
if ($len > 255 || $len < 1) {
|
||||
throw InvalidArgumentException::invalidLength('name', 1, 255);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a DynamoDB table name.
|
||||
*
|
||||
* @see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html
|
||||
*
|
||||
* @param $name string The table name to validate.
|
||||
*
|
||||
* @throws InvalidArgumentException When the name is invalid.
|
||||
*/
|
||||
private function validateTableName($name)
|
||||
{
|
||||
if (! is_string($name)) {
|
||||
throw InvalidArgumentException::invalidType('key', 'string', $name);
|
||||
}
|
||||
|
||||
if (! preg_match('/^[a-z0-9_.-]{3,255}$/i', $name)) {
|
||||
throw InvalidArgumentException::invalidTableName($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default key name for storage tables.
|
||||
*
|
||||
* @param $name string The default name to use for the key.
|
||||
*
|
||||
* @throws InvalidArgumentException When the key name is invalid.
|
||||
*/
|
||||
private function setDefaultKeyName($name)
|
||||
{
|
||||
$this->validateKeyName($name);
|
||||
$this->defaultKeyName = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default key name.
|
||||
*
|
||||
* @return string The default key name.
|
||||
*/
|
||||
public function getDefaultKeyName()
|
||||
{
|
||||
return $this->defaultKeyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key name for a specific table.
|
||||
*
|
||||
* @param $table string The name of the table.
|
||||
* @param $key string The name of the string.
|
||||
*
|
||||
* @throws InvalidArgumentException When the key or table name is invalid.
|
||||
*/
|
||||
private function setKeyForTable($table, $key)
|
||||
{
|
||||
$this->validateTableName($table);
|
||||
$this->validateKeyName($key);
|
||||
$this->tableKeys[$table] = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific name for a key for a given table. The default is returned if this table does not have
|
||||
* an actual override.
|
||||
*
|
||||
* @param string $tableName The name of the table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getKeyNameForTable($tableName)
|
||||
{
|
||||
return isset($this->tableKeys[$tableName]) ?
|
||||
$this->tableKeys[$tableName] :
|
||||
$this->defaultKeyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a key to be in a valid format for lookups for DynamoDB. If passing an array, that means that the key
|
||||
* is the name of the key and the value is the actual value for the lookup.
|
||||
*
|
||||
* @param string $storageName Table name.
|
||||
* @param string $key Key name.
|
||||
*
|
||||
* @return array The key in DynamoDB format.
|
||||
*/
|
||||
private function prepareKey($storageName, $key)
|
||||
{
|
||||
if (is_array($key)) {
|
||||
$keyValue = reset($key);
|
||||
$keyName = key($key);
|
||||
} else {
|
||||
$keyValue = $key;
|
||||
$keyName = $this->getKeyNameForTable($storageName);
|
||||
}
|
||||
|
||||
return $this->marshaler->marshalItem([$keyName => $keyValue]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsPartialUpdates()
|
||||
{
|
||||
@@ -222,7 +72,7 @@ class DynamoDbStorage implements Storage
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsCompositePrimaryKeys()
|
||||
{
|
||||
@@ -230,7 +80,7 @@ class DynamoDbStorage implements Storage
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function requiresCompositePrimaryKeys()
|
||||
{
|
||||
@@ -238,55 +88,98 @@ class DynamoDbStorage implements Storage
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Prepares a key to be in a valid format for lookups for DynamoDB. If passing an array, that means that the key
|
||||
* is the name of the key and the value is the actual value for the lookup.
|
||||
*
|
||||
* @param string $storageName Table name
|
||||
* @param array|string $key Key name
|
||||
*
|
||||
* @return array The key in DynamoDB format
|
||||
*/
|
||||
private function prepareKey($storageName, $key)
|
||||
{
|
||||
if (! $this->descriptionCache->contains($storageName)) {
|
||||
$result = $this->client->describeTable([
|
||||
'TableName' => $storageName,
|
||||
]);
|
||||
|
||||
$keys = isset($result['Table']['KeySchema'])
|
||||
? $result['Table']['KeySchema']
|
||||
: [];
|
||||
$keys = array_column($keys, 'AttributeName') ?: [];
|
||||
|
||||
$this->descriptionCache->save($storageName, $keys);
|
||||
}
|
||||
|
||||
$keys = isset($keys) ? $keys : $this->descriptionCache->fetch($storageName);
|
||||
$keys = array_combine($keys, array_fill(0, (count($keys) - 1) ?: 1, $key));
|
||||
|
||||
if (!is_array($key)) {
|
||||
$key = [
|
||||
$storageName => $key,
|
||||
];
|
||||
}
|
||||
|
||||
$keys = array_intersect_assoc($keys, $key) ?: $keys;
|
||||
|
||||
return $this->marshaler->marshalItem($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert($storageName, $key, array $data)
|
||||
{
|
||||
$this->client->putItem([
|
||||
self::TABLE_NAME_KEY => $storageName,
|
||||
self::TABLE_ITEM_KEY => $this->prepareKey($storageName, $key) + $this->marshaler->marshalItem($this->prepareData($data)),
|
||||
'TableName' => $storageName,
|
||||
'Item' => $this->prepareKey($storageName, $key) + $this->marshaler->marshalItem($data),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update($storageName, $key, array $data)
|
||||
{
|
||||
//We are using PUT so we just replace the original item, if the key
|
||||
//does not exist, it will be created.
|
||||
$this->insert($storageName, $key, $this->prepareData($data));
|
||||
// we are using PUT so we just replace the original item, if the key
|
||||
// does not exist, it will be created.
|
||||
$this->insert($storageName, $key, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($storageName, $key)
|
||||
{
|
||||
$this->client->deleteItem([
|
||||
self::TABLE_NAME_KEY => $storageName,
|
||||
self::TABLE_KEY => $this->prepareKey($storageName, $key),
|
||||
'Key' => $this->prepareKey($storageName, $key),
|
||||
'TableName' => $storageName,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function find($storageName, $key)
|
||||
{
|
||||
$keys = $this->prepareKey($storageName, $key);
|
||||
|
||||
$item = $this->client->getItem([
|
||||
self::TABLE_NAME_KEY => $storageName,
|
||||
self::CONSISTENT_READ_KEY => true,
|
||||
self::TABLE_KEY => $this->prepareKey($storageName, $key),
|
||||
'ConsistentRead' => true,
|
||||
'Key' => $keys,
|
||||
'TableName' => $storageName,
|
||||
]);
|
||||
|
||||
if (! $item) {
|
||||
if (! $item->hasKey('Item')) {
|
||||
throw NotFoundException::notFoundByKey($key);
|
||||
}
|
||||
|
||||
$item = $item->get(self::TABLE_ITEM_KEY);
|
||||
$item = $item->get('Item');
|
||||
|
||||
return $this->marshaler->unmarshalItem($item);
|
||||
$result = $this->marshaler->unmarshalItem($item);
|
||||
$result = array_diff_key($result, $keys);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,25 +191,4 @@ class DynamoDbStorage implements Storage
|
||||
{
|
||||
return 'dynamodb';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare data by removing empty item attributes.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareData($data)
|
||||
{
|
||||
$callback = function ($value) {
|
||||
return $value !== null && $value !== [] && $value !== '';
|
||||
};
|
||||
|
||||
foreach ($data as &$value) {
|
||||
if (is_array($value)) {
|
||||
$value = $this->prepareData($value);
|
||||
}
|
||||
}
|
||||
return array_filter($data, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
<var name="DOCTRINE_KEYVALUE_AZURE_NAME" value="" />
|
||||
<var name="DOCTRINE_KEYVALUE_AZURE_KEY" value="" />
|
||||
<env name="RIAK_DNS" value="" />
|
||||
<env name="DYNAMODB_DNS" value="" />
|
||||
</php>
|
||||
</phpunit>
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\Tests\KeyValueStore\Storage;
|
||||
|
||||
use Doctrine\KeyValueStore\Storage\DynamoDbStorage;
|
||||
use Aws\DynamoDb\DynamoDbClient;
|
||||
use Doctrine\KeyValueStore\NotFoundException;
|
||||
|
||||
/**
|
||||
* @covers \Doctrine\KeyValueStore\Storage\DynamoDbStorage
|
||||
*/
|
||||
class DynamoDbStorageTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
const DATA = [
|
||||
'author' => 'John Doe',
|
||||
'title' => 'example book',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var DynamoDbClient|null
|
||||
*/
|
||||
private static $client;
|
||||
|
||||
/**
|
||||
* @var DynamoDbStorage
|
||||
*/
|
||||
private $storage;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
$dns = getenv('DYNAMODB_DNS');
|
||||
|
||||
if (empty($dns)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static::$client = DynamoDbClient::factory(array(
|
||||
'credentials' => [
|
||||
'key' => 'YOUR_KEY',
|
||||
'secret' => 'YOUR_SECRET',
|
||||
],
|
||||
'region' => 'us-west-2',
|
||||
'endpoint' => $dns,
|
||||
'version' => 'latest',
|
||||
'retries' => 1,
|
||||
));
|
||||
|
||||
try {
|
||||
static::$client->deleteTable([
|
||||
'TableName' => 'dynamodb',
|
||||
]);
|
||||
} catch (\Exception $exception) {
|
||||
// table does not exist
|
||||
}
|
||||
|
||||
try {
|
||||
static::$client->createTable(array(
|
||||
'TableName' => 'dynamodb',
|
||||
'AttributeDefinitions' => array(
|
||||
array(
|
||||
'AttributeName' => 'id',
|
||||
'AttributeType' => 'S',
|
||||
),
|
||||
),
|
||||
'KeySchema' => array(
|
||||
array(
|
||||
'AttributeName' => 'id',
|
||||
'KeyType' => 'HASH',
|
||||
),
|
||||
),
|
||||
'ProvisionedThroughput' => array(
|
||||
'ReadCapacityUnits' => 10,
|
||||
'WriteCapacityUnits' => 20,
|
||||
),
|
||||
));
|
||||
} catch (\Exception $exception) {
|
||||
static::$client = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
if (! static::$client) {
|
||||
$this->markTestSkipped('DynamoDB is required.');
|
||||
}
|
||||
|
||||
$this->storage = new DynamoDbStorage(static::$client);
|
||||
}
|
||||
|
||||
public function testInsertAndFind()
|
||||
{
|
||||
$this->storage->insert('dynamodb', 'testInsertAndFind', self::DATA);
|
||||
|
||||
$data = $this->storage->find('dynamodb', 'testInsertAndFind');
|
||||
|
||||
$this->assertEquals(self::DATA, $data);
|
||||
}
|
||||
|
||||
public function testUpdate()
|
||||
{
|
||||
$this->storage->insert('dynamodb', 'testUpdate', self::DATA);
|
||||
|
||||
$newData = [
|
||||
'foo' => 'bar',
|
||||
];
|
||||
|
||||
$this->storage->update('dynamodb', 'testUpdate', $newData);
|
||||
|
||||
$data = $this->storage->find('dynamodb', 'testUpdate');
|
||||
$this->assertEquals($newData, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testInsertAndFind
|
||||
*/
|
||||
public function testFindWithNotExistKey()
|
||||
{
|
||||
$this->setExpectedException(NotFoundException::class);
|
||||
$this->storage->find('dynamodb', 'not-existing-key');
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testInsertAndFind
|
||||
* @depends testFindWithNotExistKey
|
||||
*/
|
||||
public function testDelete()
|
||||
{
|
||||
$this->storage->insert('dynamodb', 'testDelete', self::DATA);
|
||||
$this->storage->delete('dynamodb', 'testDelete');
|
||||
|
||||
$this->setExpectedException(NotFoundException::class);
|
||||
$this->storage->find('dynamodb', 'testDelete');
|
||||
}
|
||||
|
||||
public function testGetName()
|
||||
{
|
||||
$this->assertEquals('dynamodb', $this->storage->getName());
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\Tests\KeyValueStore\Storage;
|
||||
|
||||
use Doctrine\KeyValueStore\Storage\DynamoDbStorage;
|
||||
|
||||
/**
|
||||
* @covers \Doctrine\KeyValueStore\Storage\DynamoDbStorage
|
||||
*/
|
||||
class DynamoDbTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private function getDynamoDbMock($methods = [])
|
||||
{
|
||||
$client = $this->getMockBuilder('Aws\DynamoDb\DynamoDbClient')->disableOriginalConstructor();
|
||||
|
||||
if (count($methods)) {
|
||||
$client->setMethods($methods);
|
||||
}
|
||||
|
||||
return $client->getMock();
|
||||
}
|
||||
|
||||
private function getDynamoDbResultMock($methods = [])
|
||||
{
|
||||
$result = $this->getMockBuilder('Aws\Result')->disableOriginalConstructor();
|
||||
|
||||
if (count($methods)) {
|
||||
$result->setMethods($methods);
|
||||
}
|
||||
|
||||
return $result->getMock();
|
||||
}
|
||||
|
||||
public function testTheStorageName()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->assertSame('dynamodb', $storage->getName());
|
||||
}
|
||||
|
||||
public function testDefaultKeyName()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->assertAttributeSame('Id', 'defaultKeyName', $storage);
|
||||
}
|
||||
|
||||
public function testThatTableKeysInitiallyEmpty()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->assertAttributeSame([], 'tableKeys', $storage);
|
||||
}
|
||||
|
||||
public function testDefaultKeyCannotBeSomethingOtherThanString()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$this->setExpectedException(
|
||||
'\Doctrine\KeyValueStore\KeyValueStoreException',
|
||||
'The key must be a string, got "array" instead.'
|
||||
);
|
||||
new DynamoDbStorage($client, null, []);
|
||||
}
|
||||
|
||||
public function testTableKeysMustAllBeStringsOrElse()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$this->setExpectedException(
|
||||
'\Doctrine\KeyValueStore\KeyValueStoreException',
|
||||
'The key must be a string, got "object" instead.'
|
||||
);
|
||||
new DynamoDbStorage($client, null, null, ['mytable' => 'hello', 'yourtable' => new \stdClass()]);
|
||||
}
|
||||
|
||||
public function testKeyNameMustBeUnder255Bytes()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$this->setExpectedException(
|
||||
'\Doctrine\KeyValueStore\KeyValueStoreException',
|
||||
'The name must be at least 1 but no more than 255 chars.'
|
||||
);
|
||||
new DynamoDbStorage($client, null, str_repeat('a', 256));
|
||||
}
|
||||
|
||||
public function invalidTableNames()
|
||||
{
|
||||
return [
|
||||
['a2'],
|
||||
['yo%'],
|
||||
['что'],
|
||||
['h@llo'],
|
||||
];
|
||||
}
|
||||
|
||||
public function validTableNames()
|
||||
{
|
||||
return [
|
||||
['MyTable'],
|
||||
['This_is0k-...'],
|
||||
['hello_world'],
|
||||
['...........00....'],
|
||||
];
|
||||
}
|
||||
|
||||
private function invokeMethod($methodName, $obj, array $args = null)
|
||||
{
|
||||
$relf = new \ReflectionObject($obj);
|
||||
$method = $relf->getMethod($methodName);
|
||||
$method->setAccessible(true);
|
||||
|
||||
if ($args) {
|
||||
return $method->invokeArgs($obj, $args);
|
||||
}
|
||||
|
||||
return $method->invoke($obj);
|
||||
}
|
||||
|
||||
public function testTableNameMustBeAString()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->setExpectedException('\Doctrine\KeyValueStore\InvalidArgumentException');
|
||||
$this->invokeMethod('setKeyForTable', $storage, [[], 'Id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidTableNames
|
||||
*/
|
||||
public function testTableNameValidatesAgainstInvalidTableNames($tableName)
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->setExpectedException('\Doctrine\KeyValueStore\KeyValueStoreException');
|
||||
$this->invokeMethod('setKeyForTable', $storage, [$tableName, 'Id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validTableNames
|
||||
*/
|
||||
public function testTableNameValidatesAgainstValidTableNames($tableName)
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->invokeMethod('setKeyForTable', $storage, [$tableName, 'Id']);
|
||||
|
||||
$this->assertAttributeSame([$tableName => 'Id'], 'tableKeys', $storage);
|
||||
}
|
||||
|
||||
public function testThatYouCanHaveMultipleTablesWithOverrides()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->invokeMethod('setKeyForTable', $storage, ['Aaa', '2']);
|
||||
$this->invokeMethod('setKeyForTable', $storage, ['Bbb', '1']);
|
||||
|
||||
$this->assertAttributeSame(['Aaa' => '2', 'Bbb' => '1'], 'tableKeys', $storage);
|
||||
}
|
||||
|
||||
public function testGetterForDefaultKeyName()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client, null, 'CustomKey');
|
||||
$this->assertSame('CustomKey', $storage->getDefaultKeyName());
|
||||
}
|
||||
|
||||
public function testGetWillReturnDefaultKeyForUnrecognizedTableName()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client, null, 'CustomKey');
|
||||
$this->assertSame('CustomKey', $this->invokeMethod('getKeyNameForTable', $storage, ['whatever_this_is']));
|
||||
}
|
||||
|
||||
public function testGetWillReturnCorrectKeyForRecognizedTableName()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
$storage = new DynamoDbStorage($client, null, 'CustomKey', ['MyTable' => 'Yesss']);
|
||||
$this->assertSame('Yesss', $this->invokeMethod('getKeyNameForTable', $storage, ['MyTable']));
|
||||
}
|
||||
|
||||
public function testThatSomeStorageHasDifferentKey()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
|
||||
$storage = new DynamoDbStorage($client, null, 'sauce', ['this' => 'that', 'yolo' => 'now']);
|
||||
|
||||
$this->assertSame(['that' => ['N' => '111']], $this->invokeMethod('prepareKey', $storage, ['this', 111]));
|
||||
}
|
||||
|
||||
public function testThatSomeStorageUsesDefaultKey()
|
||||
{
|
||||
$client = $this->getDynamoDbMock();
|
||||
|
||||
$storage = new DynamoDbStorage($client, null, 'sauce', ['this' => 'that', 'yolo' => 'now']);
|
||||
|
||||
$this->assertSame(['sauce' => ['S' => 'hello']], $this->invokeMethod('prepareKey', $storage, ['MyTable', 'hello']));
|
||||
}
|
||||
|
||||
public function testInsertingCallsAPutItem()
|
||||
{
|
||||
$client = $this->getDynamoDbMock(['putItem']);
|
||||
|
||||
$client->expects($this->once())->method('putItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'Item' => [
|
||||
'Id' => ['S' => 'stuff'],
|
||||
'hi' => ['S' => 'there'],
|
||||
'yo' => ['BOOL' => false],
|
||||
],
|
||||
]));
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$storage->insert('MyTable', 'stuff', ['hi' => 'there', 'yo' => false]);
|
||||
}
|
||||
|
||||
public function testInsertingPreparesNestedAttributes()
|
||||
{
|
||||
$client = $this->getDynamoDbMock(['putItem']);
|
||||
|
||||
$client->expects($this->once())->method('putItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'Item' => [
|
||||
'Id' => ['S' => 'stuff'],
|
||||
'hi' => ['S' => 'there'],
|
||||
'what' => ['L' => [
|
||||
['S' => 'Yep'],
|
||||
]],
|
||||
'yo' => ['BOOL' => false],
|
||||
],
|
||||
]));
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$storage->insert('MyTable', 'stuff', ['hi' => 'there', 'yo' => false, 'what' => ['Yep', '']]);
|
||||
}
|
||||
|
||||
public function testUpdateActuallyAlsoCallsInsert()
|
||||
{
|
||||
$client = $this->getDynamoDbMock(['putItem']);
|
||||
|
||||
$client->expects($this->once())->method('putItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'Item' => [
|
||||
'Id' => ['S' => 'stuff'],
|
||||
'hi' => ['S' => 'there'],
|
||||
'yo' => ['BOOL' => false],
|
||||
],
|
||||
]));
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$storage->update('MyTable', 'stuff', ['hi' => 'there', 'yo' => false]);
|
||||
}
|
||||
|
||||
public function testDeleteItem()
|
||||
{
|
||||
$client = $this->getDynamoDbMock(['deleteItem']);
|
||||
|
||||
$client->expects($this->once())->method('deleteItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'Key' => ['Id' => ['S' => 'abc123']],
|
||||
]));
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$storage->delete('MyTable', 'abc123');
|
||||
}
|
||||
|
||||
public function testDeleteItemWithKeyValuePair()
|
||||
{
|
||||
$client = $this->getDynamoDbMock(['deleteItem']);
|
||||
|
||||
$client->expects($this->once())->method('deleteItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'Key' => ['Id' => ['S' => 'abc123']],
|
||||
]));
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$storage->delete('MyTable', ['Id' => 'abc123']);
|
||||
}
|
||||
|
||||
public function testPassingArrayAsKeyIsAPassthruToInsert()
|
||||
{
|
||||
$client = $this->getDynamoDbMock(['deleteItem']);
|
||||
|
||||
$client->expects($this->once())->method('deleteItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'Key' => ['Id' => ['S' => 'abc123']],
|
||||
]));
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$storage->delete('MyTable', 'abc123');
|
||||
}
|
||||
|
||||
public function testTryingToFindAnItemThatDoesNotExist()
|
||||
{
|
||||
$client = $this->getDynamoDbMock(['getItem']);
|
||||
$client->expects($this->once())->method('getItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'ConsistentRead' => true,
|
||||
'Key' => ['Id' => ['N' => '1000']],
|
||||
]))->willReturn(null);
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$this->setExpectedException(
|
||||
'\Doctrine\KeyValueStore\NotFoundException',
|
||||
'Could not find an item with key: 1000'
|
||||
);
|
||||
$storage->find('MyTable', 1000);
|
||||
}
|
||||
|
||||
public function testFindAnItemThatExists()
|
||||
{
|
||||
$result = $this->getDynamoDbResultMock(['get']);
|
||||
$result->expects($this->once())->method('get')->with('Item')->willReturn([
|
||||
'hello' => ['S' => 'world'],
|
||||
]);
|
||||
|
||||
$client = $this->getDynamoDbMock(['getItem']);
|
||||
$client->expects($this->once())->method('getItem')->with($this->equalTo([
|
||||
'TableName' => 'MyTable',
|
||||
'ConsistentRead' => true,
|
||||
'Key' => ['Id' => ['N' => '1000']],
|
||||
]))->willReturn($result);
|
||||
|
||||
$storage = new DynamoDbStorage($client);
|
||||
$actualResult = $storage->find('MyTable', 1000);
|
||||
|
||||
$this->assertSame(['hello' => 'world'], $actualResult);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user