45 Commits

Author SHA1 Message Date
Emanuele Minotto 6faf449755 Include code coverage (with coveralls) 2016-07-09 12:52:30 +02:00
Emanuele Minotto 5ecca2e0e2 Force coding style 2016-07-09 12:51:59 +02:00
Emanuele Minotto 22af505cac Remove notifications 2016-07-09 12:51:30 +02:00
Emanuele Minotto 16c1bc3d3d Use travis cache 2016-07-09 12:51:14 +02:00
Emanuele Minotto 5e6144a852 Fixed coding style 2016-07-09 12:50:45 +02:00
Emanuele Minotto 8ba3db4ce7 Merge pull request #79 from larowlan/dynamodb
Enhances existing dynamodb implementation and adds docs
2016-07-05 23:08:02 +02:00
Lee Rowlands bb1ba64453 Revert changes to composer.json 2016-06-06 10:09:09 +10:00
Lee Rowlands 903f6a8c97 Update docs to use new format 2016-03-24 11:44:07 +10:00
Lee Rowlands 0a5917a2a9 PHPCS fixes 2016-03-23 09:26:05 +10:00
Lee Rowlands b7daeb0d19 Restore missing Riak dependency 2016-03-23 09:19:17 +10:00
Lee Rowlands 06e11dae33 Remove redundant docs 2016-03-23 09:18:22 +10:00
Lee Rowlands 4584d99529 Add missing docs 2016-03-23 09:17:59 +10:00
Lee Rowlands 54e05f9acd Fix typo 2016-03-23 09:17:00 +10:00
Lee Rowlands 4d4a17aca9 Update documentation for DynamoDb 2016-03-23 08:45:44 +10:00
Lee Rowlands b2a4e320b0 Combine existing DynamoDb implementation with new AmazonDynamoDb and expands tests 2016-03-23 08:13:32 +10:00
Marco Pivetta b2906e209e Merge pull request #78 from doctrine/feature/array-storage
Add array storage for development/testing purposes
2016-03-11 13:34:51 +01:00
Emanuele Minotto f29622194e Add array storage for development/testing purposes 2016-03-09 23:18:37 +01:00
Marco Pivetta 2517bb04c5 Merge pull request #77 from doctrine/fix/redis-on-travis
Redis on TravisCI setup as PECL extension
2016-03-09 23:17:48 +01:00
Emanuele Minotto a0d85c1c27 Redis on TravisCI setup as PECL extension 2016-02-26 00:36:39 +01:00
Nick Ilyin 0d9c10edf2 Replaced @exception annotations 2016-02-03 09:41:57 -05:00
Nick Ilyin 606ca8b29d License headers and added @covers 2016-02-03 09:41:57 -05:00
Nick Ilyin 1ffab2e77d More precise info for NotFoundException 2016-02-03 09:41:57 -05:00
Nick Ilyin e0878ca97a Converted keys to consts 2016-02-03 09:41:56 -05:00
Nick Ilyin 7877050062 Comment cleanups 2016-02-03 09:41:56 -05:00
Nick Ilyin cc3e478d10 Accessor level refactors 2016-02-03 09:41:56 -05:00
Nick Ilyin 79bee991a2 Exception refactoring 2016-02-03 09:41:56 -05:00
Nick Ilyin f61ce21d4b Members downgraded to private 2016-02-03 09:41:56 -05:00
Nick Ilyin 58f98f8a94 Removed unnecessary not empty check 2016-02-03 09:41:56 -05:00
Nick Ilyin 902da8f693 Moved AWS to require-dev 2016-02-03 09:41:53 -05:00
Nick Ilyin 84d5db7073 Cleaner options; php 5.4 compat 2016-02-03 09:40:42 -05:00
Nick Ilyin ef425578fb Whitespace; useless comment 2016-02-03 09:40:42 -05:00
Nick Ilyin 518f4211b0 Implementation for Amazon DynamoDB 2016-02-03 09:40:35 -05:00
Marco Pivetta bad3b56f4b Merge pull request #71 from doctrine/fix/missing-methods
Added some missing methods
2015-12-30 20:11:57 +01:00
Marco Pivetta 2836bc0cfd Merge pull request #70 from doctrine/fix/instance-reference
Fixed wrong instance reference
2015-12-30 20:10:25 +01:00
Marco Pivetta 6000d76099 Merge pull request #69 from doctrine/fix/composer-dependencies
Moved CouchDB to require-dev + suggestions
2015-12-30 20:09:15 +01:00
Marco Pivetta b095170bdc Merge pull request #68 from doctrine/enhancement/coding-style
Removed space before logical negation operator
2015-12-30 20:07:26 +01:00
Marco Pivetta fe7195fc2d Merge pull request #66 from doctrine/docs/cassandra-configuration
Added Cassandra configuration
2015-12-30 20:07:03 +01:00
Emanuele Minotto e282c1d9f6 added some missing methods 2015-12-30 18:32:18 +01:00
Emanuele Minotto e2f7ea54f7 added fix' test 2015-12-28 23:44:41 +01:00
Emanuele Minotto 03a2413e99 fixed wrong instance reference 2015-12-21 00:42:01 +01:00
Emanuele Minotto 2c99b71d19 moved couchdb to require-dev + suggestions 2015-12-21 00:38:10 +01:00
Emanuele Minotto fe1e155e69 coding style 2015-12-21 00:34:14 +01:00
Emanuele Minotto ec14ab554a Merge pull request #67 from zeroedin-bill/reconstitute-typo-fix
fix typo reconsitute -> reconstititute
2015-12-16 19:18:38 +01:00
Bill Schaller 3c7ba8415e fix typo reconsitute -> reconstititute 2015-12-16 10:20:28 -05:00
Emanuele Minotto 69832f5423 added Cassandra configuration 2015-12-16 00:18:52 +01:00
39 changed files with 1396 additions and 324 deletions
-1
View File
@@ -61,7 +61,6 @@ $config = Symfony\CS\Config\Config::create()
'align_equals',
'concat_with_spaces',
'header_comment',
'logical_not_operators_with_spaces',
'logical_not_operators_with_successor_space',
'multiline_spaces_before_semicolon',
'newline_after_open_tag',
+16 -2
View File
@@ -4,6 +4,10 @@ services:
- mongodb
- redis-server
cache:
directories:
- $HOME/.composer/cache
php:
- 5.5
- 5.6
@@ -15,7 +19,17 @@ before_install:
- composer self-update
install:
- composer --prefer-source install
- composer require satooshi/php-coveralls:~0.6@stable --no-update
- composer install --prefer-dist --no-interaction --no-progress
before_script:
- vendor/bin/php-cs-fixer -v fix --diff --dry-run
script:
- vendor/bin/phpunit --verbose
- vendor/bin/phpunit --verbose --coverage-text --coverage-clover=coverage.clover
after_script:
- if [[ $TRAVIS_PHP_VERSION != "5.6" && $TRAVIS_PULL_REQUEST == "false" ]]; then php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover; fi
notifications:
email: false
+1 -1
View File
@@ -23,7 +23,7 @@ Following vendors are targeted:
* Doctrine\Common\Cache provider (Implemented)
* RDBMS (Implemented)
* Couchbase (Implemented)
* Amazon DynamoDB
* Amazon DynamoDB (Implemented)
* CouchDB (Implemented)
* Cassandra
* MongoDB (Implemented)
+10 -7
View File
@@ -1,19 +1,22 @@
{
"name": "doctrine/key-value-store",
"require": {
"php": ">=5.5",
"doctrine/common": "^2.4",
"doctrine/couchdb": "^1.0.0-beta4"
"php": "^5.5|^7.0",
"doctrine/common": "^2.4"
},
"require-dev": {
"datastax/php-driver": "^1.0",
"phpunit/phpunit": "^4.8",
"riak/riak-client": "dev-master"
"doctrine/couchdb": "^1.0.0-beta4",
"phpunit/phpunit": "^4.8|^5.0",
"aws/aws-sdk-php": "^3.8",
"riak/riak-client": "dev-master",
"friendsofphp/php-cs-fixer": "^1.11"
},
"suggest": {
"riak/riak-client": "to use the Riak storage",
"aws/aws-sdk-php": "to use the DynamoDB storage",
"doctrine/couchdb": "to use the CouchDB storage",
"ext-couchbase": "to use the Couchbase storage",
"aws/aws-sdk-php": "to use the DynamoDB storage"
"riak/riak-client": "to use the Riak storage"
},
"description": "Simple Key-Value Store Abstraction Layer that maps to PHP objects, allowing for many backends.",
"license": "MIT",
+57
View File
@@ -52,6 +52,7 @@ database backends, you have to configure the actual storage you want to use.
This configuration is obviously specific to all the different storage drivers.
So far the following drivers exist (and are documented here):
* PHP Array
* Doctrine Cache Backend
* SQL Backend with Doctrine DBAL
* Microsoft Windows Azure Table
@@ -63,6 +64,19 @@ So far the following drivers exist (and are documented here):
Also all those storage backends obviously have different dependencies in terms
of PHP libraries or PHP PECL extensions.
PHP Array
---------
PHP array is used mainly for development and teesting purposes.
.. code-block:: php
<?php
use Doctrine\KeyValueStore\Storage\ArrayStorage;
$storage = new ArrayStorage();
Doctrine Cache Backend
----------------------
@@ -131,6 +145,26 @@ as a storage layer through the Windows Azure PHP SDK:
$storage = new AzureSdkTableStorage($client);
Cassandra
---------
Cassandra is supported through the `PECL extension <https://pecl.php.net/package/cassandra>`_
and the `DataStax PHP driver <https://github.com/datastax/php-driver>`_:
.. code-block:: php
<?php
use Cassandra;
use Cassandra\SimpleStatement;
use Doctrine\KeyValueStore\Storage\CassandraStorage;
$cluster = Cassandra::cluster()->build();
$session = $cluster->connect();
$session->execute(new SimpleStatement('USE doctrine'));
$storage = new CassandraStorage($session);
Couchbase
---------
@@ -164,6 +198,29 @@ CouchDB storage setup based on `doctrine/couchdb-client <https://github.com/doct
$storage = new CouchDbStorage($client);
DynamoDb
---------
DynamoDb is supported through the `AWS SDK for PHP <https://aws.amazon.com/sdk-for-php/>`_
Create your tables via the AWS DynamoDb console or using the `PHP based API <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LowLevelPHPTableOperationsExample.html>`_
See the `AWS docs <http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/UsingPHP.html#PHPSDKCredentialsSet>`_ for more information on configuring credentials for the client.
.. code-block:: php
<?php
$sdk = new \Aws\Sdk([...]);
$client = $sdk->createDynamoDb();
$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']]
);
MongoDB
-------
+3 -3
View File
@@ -45,7 +45,7 @@ class Configuration
*/
public function getMappingDriverImpl()
{
if ( ! isset($this->config['mappingDriver'])) {
if (! isset($this->config['mappingDriver'])) {
throw KeyValueStoreException::mappingDriverMissing();
}
@@ -85,7 +85,7 @@ class Configuration
*/
public function getMetadataCache()
{
if ( ! isset($this->config['metadataCache'])) {
if (! isset($this->config['metadataCache'])) {
$this->config['metadataCache'] = new ArrayCache();
}
@@ -112,7 +112,7 @@ class Configuration
*/
public function getIdConverterStrategy()
{
if ( ! isset($this->config['idConverter'])) {
if (! isset($this->config['idConverter'])) {
$this->config['idConverter'] = new NullIdConverter();
}
+1 -1
View File
@@ -66,7 +66,7 @@ class EntityManager
*/
public function find($className, $key)
{
return $this->unitOfWork->reconsititute($className, $key);
return $this->unitOfWork->reconstititute($className, $key);
}
/**
+1 -1
View File
@@ -71,7 +71,7 @@ class Response
*/
public function getHeader($name)
{
if ( ! isset($this->headers[$name])) {
if (! isset($this->headers[$name])) {
return;
}
return $this->headers[$name];
@@ -157,7 +157,7 @@ class SocketClient implements Client
// Remove leading newlines, should not accur at all, actually.
while (true) {
if ( ! (($line = fgets($this->connection)) !== false) || ! (($lineContent = rtrim($line)) === '')) {
if (! (($line = fgets($this->connection)) !== false) || ! (($lineContent = rtrim($line)) === '')) {
break;
}
}
@@ -194,7 +194,7 @@ class SocketClient implements Client
// Read response body
$body = '';
if ( ! isset($headers['transfer-encoding']) ||
if (! isset($headers['transfer-encoding']) ||
($headers['transfer-encoding'] !== 'chunked')) {
// HTTP 1.1 supports chunked transfer encoding, if the according
// header is not set, just read the specified amount of bytes.
@@ -26,14 +26,14 @@ class CompositeIdHandler implements IdHandlingStrategy
{
public function normalizeId(ClassMetadata $metadata, $key)
{
if ( ! $metadata->isCompositeKey && ! is_array($key)) {
if (! $metadata->isCompositeKey && ! is_array($key)) {
$id = [$metadata->identifier[0] => $key];
} elseif ( ! is_array($key)) {
} elseif (! is_array($key)) {
throw new \InvalidArgumentException('Array of identifier key-value pairs is expected!');
} else {
$id = [];
foreach ($metadata->identifier as $field) {
if ( ! isset($key[$field])) {
if (! isset($key[$field])) {
throw new \InvalidArgumentException(
"Missing identifier field $field in request for the primary key."
);
@@ -0,0 +1,48 @@
<?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\KeyValueStore;
class InvalidArgumentException extends KeyValueStoreException
{
public static function invalidType($name, $expectedType, &$actual)
{
return new static(
sprintf('The %s must be a %s, got "%s" instead.', $name, $expectedType, gettype($actual)),
0
);
}
public static function invalidLength($name, $min, $max)
{
return new static(
sprintf('The %s must be at least %d but no more than %d chars.', $name, $min, $max),
0
);
}
public static function invalidTableName($name)
{
return new static(
sprintf('Invalid table name: %s', $name),
0
);
}
}
@@ -52,14 +52,14 @@ class AnnotationDriver implements MappingDriver
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$class = $metadata->getReflectionClass();
if ( ! $class) {
if (! $class) {
// this happens when running annotation driver in combination with
// static reflection services. This is not the nicest fix
$class = new \ReflectionClass($metadata->name);
}
$entityAnnot = $this->reader->getClassAnnotation($class, 'Doctrine\KeyValueStore\Mapping\Annotations\Entity');
if ( ! $entityAnnot) {
if (! $entityAnnot) {
throw new \InvalidArgumentException($metadata->name . ' is not a valid key-value-store entity.');
}
$metadata->storageName = $entityAnnot->storageName;
@@ -21,6 +21,7 @@
namespace Doctrine\KeyValueStore\Mapping;
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
use ReflectionClass;
class ClassMetadata implements BaseClassMetadata
{
@@ -50,7 +51,7 @@ class ClassMetadata implements BaseClassMetadata
public function mapField($mapping)
{
if ( ! isset($this->transientFields[$mapping['fieldName']])) {
if (! isset($this->transientFields[$mapping['fieldName']])) {
$this->fields[$mapping['fieldName']] = $mapping;
}
}
@@ -93,6 +94,7 @@ class ClassMetadata implements BaseClassMetadata
}
return $id;
}
/**
* Get fully-qualified class name of this persistent class.
*
@@ -100,6 +102,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function getName()
{
return $this->name;
}
/**
@@ -111,6 +114,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
@@ -120,6 +124,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function getReflectionClass()
{
return new ReflectionClass($this->name);
}
/**
@@ -131,6 +136,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function isIdentifier($fieldName)
{
return in_array($fieldName, $this->identifier);
}
/**
@@ -142,6 +148,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function hasField($fieldName)
{
return isset($this->fields[$fieldName]);
}
/**
@@ -153,6 +160,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function hasAssociation($fieldName)
{
return false;
}
/**
@@ -164,6 +172,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function isSingleValuedAssociation($fieldName)
{
return false;
}
/**
@@ -175,6 +184,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function isCollectionValuedAssociation($fieldName)
{
return false;
}
/**
@@ -186,6 +196,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function getFieldNames()
{
return array_column($this->fields, 'fieldName');
}
/**
@@ -195,6 +206,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function getIdentifierFieldNames()
{
return $this->identifier;
}
/**
@@ -242,6 +254,7 @@ class ClassMetadata implements BaseClassMetadata
*/
public function isAssociationInverseSide($assocName)
{
return false;
}
/**
@@ -58,12 +58,12 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$class->storageName = $parent->storageName;
}
if ( ! $class->storageName) {
if (! $class->storageName) {
$parts = explode('\\', $class->name);
$class->storageName = end($parts);
}
if ( ! $class->identifier) {
if (! $class->identifier) {
throw new \InvalidArgumentException('Class ' . $class->name . ' has no identifier.');
}
}
@@ -22,4 +22,8 @@ namespace Doctrine\KeyValueStore;
class NotFoundException extends KeyValueStoreException
{
public static function notFoundByKey($key)
{
return new static(sprintf('Could not find an item with key: %s', $key), 0);
}
}
@@ -208,9 +208,9 @@ class RangeQuery
{
$storage = $this->em->unwrap();
if ( ! ($storage instanceof RangeQueryStorage)) {
if (! $storage instanceof RangeQueryStorage) {
throw new \RuntimeException(
'The storage backend ' . $this->storage->getName() . ' does not support range queries.'
'The storage backend ' . $storage->getName() . ' does not support range queries.'
);
}
@@ -1,93 +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\KeyValueStore\Storage;
abstract class AbstractStorage implements Storage
{
/**
* {@inheritDoc}
*/
abstract public function supportsPartialUpdates();
/**
* {@inheritDoc}
*/
abstract public function supportsCompositePrimaryKeys();
/**
* {@inheritDoc}
*/
abstract public function requiresCompositePrimaryKeys();
/**
* {@inheritDoc}
*/
abstract public function insert($storageName, $key, array $data);
/**
* {@inheritDoc}
*/
abstract public function update($storageName, $key, array $data);
/**
* {@inheritDoc}
*/
abstract public function delete($storageName, $key);
/**
* {@inheritDoc}
*/
abstract public function find($storageName, $key);
/**
* {@inheritDoc}
*/
abstract public function getName();
/**
* Used to flattening keys.
*
* @param string $storageName
* @param string|int|float|bool|array $key
*
* @return string
*/
protected function flattenKey($storageName, $key)
{
if (is_scalar($key)) {
return $storageName . '-' . $key;
}
if ( ! is_array($key)) {
throw new \InvalidArgumentException('The key should be a string or a flat array.');
}
ksort($key);
$hash = $storageName . '-oid:';
foreach ($key as $property => $value) {
$hash .= $property . '=' . $value . ';';
}
return $hash;
}
}
@@ -0,0 +1,135 @@
<?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\KeyValueStore\Storage;
use Doctrine\KeyValueStore\NotFoundException;
/**
* Array storage, mainly used for development purposes.
*
* @author Emanuele Minotto <minottoemanuele@gmail.com>
*/
class ArrayStorage implements Storage
{
/**
* @var array
*/
private $data = [];
public function supportsPartialUpdates()
{
return false;
}
/**
* Does this storage support composite primary keys?
*
* @return bool
*/
public function supportsCompositePrimaryKeys()
{
return false;
}
/**
* Does this storage require composite primary keys?
*
* @return bool
*/
public function requiresCompositePrimaryKeys()
{
return false;
}
/**
* Insert data into the storage key specified.
*
* @param array|string $key
* @param array $data
*/
public function insert($storageName, $key, array $data)
{
$this->update($storageName, $key, $data);
}
/**
* Update data into the given key.
*
* @param array|string $key
* @param array $data
*/
public function update($storageName, $key, array $data)
{
if (! isset($this->data[$storageName])) {
$this->data[$storageName] = [];
}
$this->data[$storageName][serialize($key)] = $data;
}
/**
* Delete data at key
*
* @param array|string $key
*/
public function delete($storageName, $key)
{
if (! isset($this->data[$storageName])) {
return;
}
if (! isset($this->data[$storageName][serialize($key)])) {
return;
}
unset($this->data[$storageName][serialize($key)]);
}
/**
* Find data at key
*
* @param array|string $key
*
* @return array
*/
public function find($storageName, $key)
{
if (! isset($this->data[$storageName])) {
throw new NotFoundException();
}
if (! isset($this->data[$storageName][serialize($key)])) {
throw new NotFoundException();
}
unset($this->data[$storageName][serialize($key)]);
}
/**
* Return a name of the underlying storage.
*
* @return string
*/
public function getName()
{
return 'array';
}
}
@@ -33,7 +33,7 @@ use WindowsAzure\Table\TableRestProxy;
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class AzureSdkTableStorage extends AbstractStorage implements RangeQueryStorage
class AzureSdkTableStorage implements Storage, RangeQueryStorage
{
/**
* @var \WindowsAzure\Table\TableRestProxy
@@ -184,7 +184,7 @@ class AzureSdkTableStorage extends AbstractStorage implements RangeQueryStorage
$filters = ['PartitionKey eq ' . $this->quoteFilterValue($query->getPartitionKey())];
foreach ($query->getConditions() as $condition) {
if ( ! in_array($condition[0], ['eq', 'neq', 'le', 'lt', 'ge', 'gt'])) {
if (! in_array($condition[0], ['eq', 'neq', 'le', 'lt', 'ge', 'gt'])) {
throw new \InvalidArgumentException(
'Windows Azure Table only supports eq, neq, le, lt, ge, gt as conditions.'
);
@@ -31,7 +31,7 @@ use Doctrine\KeyValueStore\NotFoundException;
*
* @uses https://github.com/datastax/php-driver
*/
class CassandraStorage extends AbstractStorage
class CassandraStorage implements Storage
{
/**
* @var \Cassandra\Session
@@ -160,7 +160,7 @@ class CassandraStorage extends AbstractStorage
$result = $this->session->execute($stmt, $options);
$rows = iterator_to_array($result);
if ( ! isset($rows[0])) {
if (! isset($rows[0])) {
throw new NotFoundException();
}
@@ -30,7 +30,7 @@ use Doctrine\CouchDB\CouchDBClient;
*
* @author Emanuele Minotto <minottoemanuele@gmail.com>
*/
final class CouchDbStorage extends AbstractStorage
final class CouchDbStorage implements Storage
{
/**
* @var CouchDBClient
@@ -112,4 +112,29 @@ final class CouchDbStorage extends AbstractStorage
{
return 'couchdb';
}
/**
* @param string $storageName
* @param array|string $key
*
* @return string
*/
private function flattenKey($storageName, $key)
{
$finalKey = $storageName . '-';
if (is_string($key)) {
return $finalKey . $key;
}
if (! is_array($key)) {
throw new \InvalidArgumentException('The key should be a string or a flat array.');
}
foreach ($key as $property => $value) {
$finalKey .= sprintf('%s:%s-', $property, $value);
}
return $finalKey;
}
}
@@ -25,7 +25,7 @@ use Doctrine\KeyValueStore\NotFoundException;
/**
* @author Simon Schick <simonsimcity@gmail.com>
*/
class CouchbaseStorage extends AbstractStorage
class CouchbaseStorage implements Storage
{
/**
* @var \Couchbase
@@ -30,7 +30,7 @@ use Doctrine\KeyValueStore\NotFoundException;
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DBALStorage extends AbstractStorage
class DBALStorage implements Storage
{
private $conn;
private $table;
@@ -142,7 +142,7 @@ class DBALStorage extends AbstractStorage
$data = $stmt->fetchColumn();
if ( ! $data) {
if (! $data) {
throw new NotFoundException();
}
@@ -30,7 +30,7 @@ use Doctrine\Common\Cache\Cache;
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DoctrineCacheStorage extends AbstractStorage
class DoctrineCacheStorage implements Storage
{
/**
* @var Doctrine\Common\Cache\Cache
@@ -60,6 +60,20 @@ class DoctrineCacheStorage extends AbstractStorage
return false;
}
private function flattenKey($storageName, $key)
{
if (! $this->supportsCompositeKeys) {
return $storageName . '-' . $key;
}
$hash = $storageName . '-oid:';
ksort($key);
foreach ($key as $property => $value) {
$hash .= $property . '=' . $value . ';';
}
return $hash;
}
public function insert($storageName, $key, array $data)
{
$key = $this->flattenKey($storageName, $key);
@@ -21,8 +21,8 @@
namespace Doctrine\KeyValueStore\Storage;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Exception\ResourceNotFoundException;
use Aws\DynamoDb\Iterator\ItemIterator;
use Aws\DynamoDb\Marshaler;
use Doctrine\KeyValueStore\InvalidArgumentException;
use Doctrine\KeyValueStore\NotFoundException;
/**
@@ -30,21 +30,187 @@ use Doctrine\KeyValueStore\NotFoundException;
*
* @author Stan Lemon <stosh1985@gmail.com>
*/
class DynamoDbStorage extends AbstractStorage
class DynamoDbStorage implements Storage
{
/**
* The key that DynamoDb uses to indicate the name of the table.
*/
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;
/**
* Constructor
*
* @param \Aws\DynamoDb\DynamoDbClient $client
* @var Marshaler
*/
public function __construct(DynamoDbClient $client)
private $marshaler;
/**
* @var string
*/
private $defaultKeyName = 'Id';
/**
* 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.
*/
public function __construct(
DynamoDbClient $client,
Marshaler $marshaler = null,
$defaultKeyName = null,
array $tableKeys = []
) {
$this->client = $client;
$this->marshaler = $marshaler ?: new Marshaler();
if ($defaultKeyName !== null) {
$this->setDefaultKeyName($defaultKeyName);
}
foreach ($tableKeys as $table => $keyName) {
$this->setKeyForTable($table, $keyName);
}
}
/**
* Validates a DynamoDB key name.
*
* @param $name mixed The name to validate.
*
* @throws InvalidArgumentException When the key name is invalid.
*/
private function validateKeyName($name)
{
$this->client = $client;
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]);
}
/**
@@ -76,14 +242,9 @@ class DynamoDbStorage extends AbstractStorage
*/
public function insert($storageName, $key, array $data)
{
$this->createTable($storageName);
$this->prepareData($key, $data);
$result = $this->client->putItem([
'TableName' => $storageName,
'Item' => $this->client->formatAttributes($data),
'ReturnConsumedCapacity' => 'TOTAL',
$this->client->putItem([
self::TABLE_NAME_KEY => $storageName,
self::TABLE_ITEM_KEY => $this->prepareKey($storageName, $key) + $this->marshaler->marshalItem($this->prepareData($data)),
]);
}
@@ -92,23 +253,9 @@ class DynamoDbStorage extends AbstractStorage
*/
public function update($storageName, $key, array $data)
{
$this->prepareData($key, $data);
unset($data['id']);
foreach ($data as $k => $v) {
$data[$k] = [
'Value' => $this->client->formatValue($v),
];
}
$result = $this->client->updateItem([
'TableName' => $storageName,
'Key' => [
'id' => ['S' => $key],
],
'AttributeUpdates' => $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));
}
/**
@@ -116,11 +263,9 @@ class DynamoDbStorage extends AbstractStorage
*/
public function delete($storageName, $key)
{
$result = $this->client->deleteItem([
'TableName' => $storageName,
'Key' => [
'id' => ['S' => $key],
],
$this->client->deleteItem([
self::TABLE_NAME_KEY => $storageName,
self::TABLE_KEY => $this->prepareKey($storageName, $key),
]);
}
@@ -129,20 +274,19 @@ class DynamoDbStorage extends AbstractStorage
*/
public function find($storageName, $key)
{
$iterator = new ItemIterator($this->client->getScanIterator([
'TableName' => $storageName,
'Key' => [
'Id' => ['S' => $key],
],
]));
$item = $this->client->getItem([
self::TABLE_NAME_KEY => $storageName,
self::CONSISTENT_READ_KEY => true,
self::TABLE_KEY => $this->prepareKey($storageName, $key),
]);
$results = $iterator->toArray();
if (count($results)) {
return array_shift($results);
if (! $item) {
throw NotFoundException::notFoundByKey($key);
}
throw new NotFoundException();
$item = $item->get(self::TABLE_ITEM_KEY);
return $this->marshaler->unmarshalItem($item);
}
/**
@@ -156,51 +300,23 @@ class DynamoDbStorage extends AbstractStorage
}
/**
* @param string $tableName
* Prepare data by removing empty item attributes.
*
* @param array $data
*
* @return array
*/
protected function createTable($tableName)
protected function prepareData($data)
{
try {
$this->client->describeTable([
'TableName' => $tableName,
]);
} catch (ResourceNotFoundException $e) {
$this->client->createTable([
'AttributeDefinitions' => [
[
'AttributeName' => 'id',
'AttributeType' => 'S',
],
],
'TableName' => $tableName,
'KeySchema' => [
[
'AttributeName' => 'id',
'KeyType' => 'HASH',
],
],
'ProvisionedThroughput' => [
'ReadCapacityUnits' => 1,
'WriteCapacityUnits' => 1,
],
]);
}
}
$callback = function ($value) {
return $value !== null && $value !== [] && $value !== '';
};
/**
* @param string $key
* @param array $data
*/
protected function prepareData($key, &$data)
{
$data = array_merge($data, [
'id' => $key,
]);
foreach ($data as $key => $value) {
if ($value === null || $value === [] || $value === '') {
unset($data[$key]);
foreach ($data as &$value) {
if (is_array($value)) {
$value = $this->prepareData($value);
}
}
return array_filter($data, $callback);
}
}
@@ -27,7 +27,7 @@ use Doctrine\KeyValueStore\NotFoundException;
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class MongoDbStorage extends AbstractStorage
class MongoDbStorage implements Storage
{
/**
* @var \Mongo
@@ -25,7 +25,7 @@ use Doctrine\KeyValueStore\NotFoundException;
/**
* @author Marcel Araujo <admin@marcelaraujo.me>
*/
class RedisStorage extends AbstractStorage
class RedisStorage implements Storage
{
/**
* @var \Redis
@@ -118,7 +118,7 @@ class RedisStorage extends AbstractStorage
{
$key = $this->getKeyName($key);
if ( ! $this->client->exists($key)) {
if (! $this->client->exists($key)) {
throw new NotFoundException();
}
@@ -26,7 +26,7 @@ use Riak\Client;
/**
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class RiakStorage extends AbstractStorage
class RiakStorage implements Storage
{
/**
* @var \Riak\Client
@@ -101,7 +101,7 @@ class RiakStorage extends AbstractStorage
/** @var $object \Riak\Object */
$object = $bucket->get($key);
if ( ! $object->exists()) {
if (! $object->exists()) {
// object does not exist, do nothing
return;
}
@@ -119,7 +119,7 @@ class RiakStorage extends AbstractStorage
/** @var $object \Riak\Object */
$object = $bucket->get($key);
if ( ! $object->exists()) {
if (! $object->exists()) {
throw new NotFoundException;
}
@@ -31,7 +31,7 @@ use Doctrine\KeyValueStore\NotFoundException;
*
* @author Stan Lemon <stosh1985@gmail.com>
*/
class SimpleDbStorage extends AbstractStorage
class SimpleDbStorage implements Storage
{
/**
* @var \Aws\SimpleDb\SimpleDbClient
@@ -36,7 +36,7 @@ use Doctrine\KeyValueStore\Storage\WindowsAzureTable\HttpStorageException;
*
* @deprecated This class is deprecated and will be removed in 2.0, use the AzureSdkTableStorage instead.
*/
class WindowsAzureTableStorage extends AbstractStorage implements RangeQueryStorage
class WindowsAzureTableStorage implements Storage, RangeQueryStorage
{
const WINDOWS_AZURE_TABLE_BASEURL = 'https://%s.table.core.windows.net';
@@ -183,7 +183,7 @@ class WindowsAzureTableStorage extends AbstractStorage implements RangeQueryStor
$tableNode->appendChild($dom->createTextNode($tableName));
$xml = $dom->saveXML();
$url = $this->baseUrl . '/Tables';
$url = $this->baseUrl . '/Tables';
$response = $this->request('POST', $url, $xml, $headers);
if ($response->getStatusCode() != 201) {
@@ -338,7 +338,7 @@ class WindowsAzureTableStorage extends AbstractStorage implements RangeQueryStor
$filters = ['PartitionKey eq ' . $this->quoteFilterValue($query->getPartitionKey())];
foreach ($query->getConditions() as $condition) {
if ( ! in_array($condition[0], ['eq', 'neq', 'le', 'lt', 'ge', 'gt'])) {
if (! in_array($condition[0], ['eq', 'neq', 'le', 'lt', 'ge', 'gt'])) {
throw new \InvalidArgumentException(
'Windows Azure Table only supports eq, neq, le, lt, ge, gt as conditions.'
);
+8 -8
View File
@@ -85,13 +85,13 @@ class UnitOfWork
return;
}
public function reconsititute($className, $key)
public function reconstititute($className, $key)
{
$class = $this->cmf->getMetadataFor($className);
$id = $this->idHandler->normalizeId($class, $key);
$data = $this->storageDriver->find($class->storageName, $id);
if ( ! $data) {
if (! $data) {
throw new NotFoundException();
}
@@ -143,7 +143,7 @@ class UnitOfWork
$originalData = $this->originalData[spl_object_hash($object)];
foreach ($snapshot as $field => $value) {
if ( ! isset($originalData[$field]) || $originalData[$field] !== $value) {
if (! isset($originalData[$field]) || $originalData[$field] !== $value) {
$changeSet[$field] = $value;
}
}
@@ -159,13 +159,13 @@ class UnitOfWork
$data = [];
foreach ($class->reflFields as $fieldName => $reflProperty) {
if ( ! isset($class->fields[$fieldName]['id'])) {
if (! isset($class->fields[$fieldName]['id'])) {
$data[$fieldName] = $reflProperty->getValue($object);
}
}
foreach (get_object_vars($object) as $property => $value) {
if ( ! isset($data[$property])) {
if (! isset($data[$property])) {
$data[$property] = $value;
}
}
@@ -183,7 +183,7 @@ class UnitOfWork
$class = $this->cmf->getMetadataFor(get_class($object));
$id = $this->idHandler->getIdentifier($class, $object);
if ( ! $id) {
if (! $id) {
throw new \RuntimeException('Trying to persist entity that has no id.');
}
@@ -200,7 +200,7 @@ class UnitOfWork
public function scheduleForDelete($object)
{
$oid = spl_object_hash($object);
if ( ! isset($this->identifiers[$oid])) {
if (! isset($this->identifiers[$oid])) {
throw new \RuntimeException(
'Object scheduled for deletion is not managed. Only managed objects can be deleted.'
);
@@ -242,7 +242,7 @@ class UnitOfWork
$id = $this->idHandler->getIdentifier($class, $object);
$id = $this->idConverter->serialize($class, $id);
if ( ! $id) {
if (! $id) {
throw new \RuntimeException('Trying to persist entity that has no id.');
}
@@ -21,7 +21,11 @@
namespace Doctrine\Tests\KeyValueStore\Mapping;
use Doctrine\KeyValueStore\Mapping\ClassMetadata;
use ReflectionClass;
/**
* @coversDefaultClass \Doctrine\KeyValueStore\Mapping\ClassMetadata
*/
class ClassMetadataTest extends \PHPUnit_Framework_TestCase
{
private $metadata;
@@ -68,4 +72,118 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
$this->assertEquals([], $this->metadata->fields);
}
/**
* @covers ::getIdentifier
*/
public function testGetIdentifier()
{
$identifier = $this->metadata->getIdentifier();
$this->assertInternalType('array', $identifier);
foreach ($identifier as $key => $value) {
$this->assertInternalType('integer', $key);
$this->assertInternalType('string', $value);
}
}
/**
* @covers ::getReflectionClass
*/
public function testGetReflectionClass()
{
$reflectionClass = $this->metadata->getReflectionClass();
$this->assertInstanceOf(ReflectionClass::class, $reflectionClass);
$this->assertSame(__CLASS__, $reflectionClass->name);
}
/**
* @covers ::isIdentifier
*/
public function testIsIdentifier()
{
$this->metadata->mapIdentifier('id');
$this->assertTrue($this->metadata->isIdentifier('id'));
$this->assertFalse($this->metadata->isIdentifier('test'));
}
/**
* @covers ::hasField
*/
public function testHasField()
{
$this->metadata->mapField(['fieldName' => 'foo']);
$this->assertTrue($this->metadata->hasField('foo'));
$this->assertFalse($this->metadata->hasField('bar'));
}
/**
* @covers ::hasAssociation
*/
public function testHasAssociation()
{
$this->assertFalse($this->metadata->hasAssociation(sha1(rand())));
}
/**
* @covers ::isSingleValuedAssociation
*/
public function testIsSingleValuedAssociation()
{
$this->assertFalse($this->metadata->isSingleValuedAssociation(sha1(rand())));
}
/**
* @covers ::isCollectionValuedAssociation
*/
public function testIsCollectionValuedAssociation()
{
$this->assertFalse($this->metadata->isCollectionValuedAssociation(sha1(rand())));
}
/**
* @covers ::getFieldNames
*/
public function testGetFieldNames()
{
$this->metadata->mapField(['fieldName' => 'foo']);
$fieldNames = $this->metadata->getFieldNames();
$this->assertInternalType('array', $fieldNames);
foreach ($fieldNames as $key => $value) {
$this->assertInternalType('integer', $key);
$this->assertInternalType('string', $value);
}
$this->assertSame(['foo'], $fieldNames);
}
/**
* @covers ::getIdentifierFieldNames
*/
public function testGetIdentifierFieldNames()
{
$identifierFieldNames = $this->metadata->getIdentifierFieldNames();
$this->assertInternalType('array', $identifierFieldNames);
foreach ($identifierFieldNames as $key => $value) {
$this->assertInternalType('integer', $key);
$this->assertInternalType('string', $value);
}
}
/**
* @covers ::isAssociationInverseSide
*/
public function testIsAssociationInverseSide()
{
$this->assertFalse($this->metadata->isAssociationInverseSide(sha1(rand())));
}
}
@@ -0,0 +1,250 @@
<?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\Query;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\KeyValueStore\EntityManager;
use Doctrine\KeyValueStore\Query\RangeQuery;
use Doctrine\KeyValueStore\Storage\DoctrineCacheStorage;
use PHPUnit_Framework_TestCase;
use ReflectionClass;
use RuntimeException;
/**
* @coversDefaultClass \Doctrine\KeyValueStore\Query\RangeQuery
*/
class RangeQueryTest extends PHPUnit_Framework_TestCase
{
/**
* @var EntityManager
*/
private $entityManager;
/**
* @var string
*/
private $className;
/**
* @var string
*/
private $partitionKey;
/**
* @var RangeQuery
*/
protected $object;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->entityManager = $this
->getMockBuilder(EntityManager::class)
->disableOriginalConstructor()
->getMock();
$this->className = sha1(rand());
$this->partitionKey = sha1(rand());
$this->object = new RangeQuery(
$this->entityManager,
$this->className,
$this->partitionKey
);
}
/**
* @covers ::setLimit
* @covers ::getLimit
*/
public function testLimit()
{
$limit = rand();
$setterOutput = $this->object->setLimit($limit);
$this->assertInstanceOf(RangeQuery::class, $setterOutput);
$this->assertSame($limit, $this->object->getLimit());
}
/**
* @covers ::getClassName
*/
public function testGetClassName()
{
$this->assertSame(
$this->className,
$this->object->getClassName()
);
}
/**
* @covers ::getPartitionKey
*/
public function testGetPartitionKey()
{
$this->assertSame(
$this->partitionKey,
$this->object->getPartitionKey()
);
}
/**
* @covers ::getConditions
*/
public function testGetConditions()
{
$reflectionClass = new ReflectionClass($this->object);
$constants = $reflectionClass->getConstants();
$conditions = $this->object->getConditions();
$this->assertInternalType('array', $conditions);
foreach ($conditions as $condition) {
$this->assertArrayHasKey($condition[0], $constants);
}
}
/**
* @covers ::rangeEquals
* @depends testGetConditions
*/
public function testRangeEquals()
{
$value = 'test';
$output = $this->object->rangeEquals($value);
$this->assertInstanceOf(RangeQuery::class, $output);
$conditions = $this->object->getConditions();
$this->assertArraySubset(
[[RangeQuery::CONDITION_EQ, $value]],
$conditions
);
}
/**
* @covers ::rangeNotEquals
* @depends testGetConditions
*/
public function testRangeNotEquals()
{
$value = 'test';
$output = $this->object->rangeNotEquals($value);
$this->assertInstanceOf(RangeQuery::class, $output);
$conditions = $this->object->getConditions();
$this->assertArraySubset(
[[RangeQuery::CONDITION_NEQ, $value]],
$conditions
);
}
/**
* @covers ::rangeLessThan
* @depends testGetConditions
*/
public function testRangeLessThan()
{
$value = 'test';
$output = $this->object->rangeLessThan($value);
$this->assertInstanceOf(RangeQuery::class, $output);
$conditions = $this->object->getConditions();
$this->assertArraySubset(
[[RangeQuery::CONDITION_LT, $value]],
$conditions
);
}
/**
* @covers ::rangeLessThanEquals
* @depends testGetConditions
*/
public function testRangeLessThanEquals()
{
$value = 'test';
$output = $this->object->rangeLessThanEquals($value);
$this->assertInstanceOf(RangeQuery::class, $output);
$conditions = $this->object->getConditions();
$this->assertArraySubset(
[[RangeQuery::CONDITION_LE, $value]],
$conditions
);
}
/**
* @covers ::rangeGreaterThan
* @depends testGetConditions
*/
public function testRangeGreaterThan()
{
$value = 'test';
$output = $this->object->rangeGreaterThan($value);
$this->assertInstanceOf(RangeQuery::class, $output);
$conditions = $this->object->getConditions();
$this->assertArraySubset(
[[RangeQuery::CONDITION_GT, $value]],
$conditions
);
}
/**
* @covers ::rangeGreaterThanEquals
* @depends testGetConditions
*/
public function testRangeGreaterThanEquals()
{
$value = 'test';
$output = $this->object->rangeGreaterThanEquals($value);
$this->assertInstanceOf(RangeQuery::class, $output);
$conditions = $this->object->getConditions();
$this->assertArraySubset(
[[RangeQuery::CONDITION_GE, $value]],
$conditions
);
}
/**
* @covers ::execute
*/
public function testWrongExecute()
{
$this->entityManager
->method('unwrap')
->willReturn(new DoctrineCacheStorage(new ArrayCache));
$this->setExpectedException(RuntimeException::class);
$this->object->execute();
}
}
@@ -1,80 +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\AbstractStorage;
use PHPUnit_Framework_TestCase;
use ReflectionClass;
/**
* @covers \Doctrine\KeyValueStore\Storage\AbstractStorage
*/
class AbstractStorageTest extends PHPUnit_Framework_TestCase
{
/**
* @var AbstractStorage
*/
protected $object;
public function setUp()
{
$this->object = $this->getMockForAbstractClass(AbstractStorage::class);
}
/**
* @dataProvider keysDataProvider
*/
public function testFlattenKey($storageName, $key, $expected)
{
$reflectionClass = new ReflectionClass($this->object);
$method = $reflectionClass->getMethod('flattenKey');
$method->setAccessible(true);
$hash = $method->invokeArgs($this->object, [$storageName, $key]);
$this->assertInternalType('string', $hash);
$this->assertSame($expected, $hash);
}
/**
* @return array
*/
public function keysDataProvider()
{
return [
// key: string
['foo', 'bar', 'foo-bar'],
['foo', 0.0, 'foo-0'],
['foo', 0.05, 'foo-0.05'],
['foo', 1, 'foo-1'],
['foo', 1.0, 'foo-1'],
['foo', 1.05, 'foo-1.05'],
['foo', false, 'foo-'],
['foo', true, 'foo-1'],
// key: array
['foo', ['bar', 'test'], 'foo-oid:0=bar;1=test;'],
['foo', ['bar', 0.0], 'foo-oid:0=bar;1=0;'],
['foo', ['test' => 3, 'bar' => 5], 'foo-oid:bar=5;test=3;'],
['foo', ['test' => 3.1, 'bar' => 5.0], 'foo-oid:bar=5;test=3.1;'],
['foo', ['test' => true, 'bar' => false], 'foo-oid:bar=;test=1;'],
];
}
}
@@ -39,7 +39,7 @@ abstract class AbstractStorageTestCase extends \PHPUnit_Framework_TestCase
public function testInsertCompositeKey()
{
if ( ! $this->storage->supportsCompositePrimaryKeys()) {
if (! $this->storage->supportsCompositePrimaryKeys()) {
$this->markTestSkipped('Composite keys need to be supported for this test to run.');
}
@@ -59,7 +59,7 @@ abstract class AbstractStorageTestCase extends \PHPUnit_Framework_TestCase
public function testUpdateCompositeKey()
{
if ( ! $this->storage->supportsCompositePrimaryKeys()) {
if (! $this->storage->supportsCompositePrimaryKeys()) {
$this->markTestSkipped('Composite keys need to be supported for this test to run.');
}
@@ -79,7 +79,7 @@ abstract class AbstractStorageTestCase extends \PHPUnit_Framework_TestCase
public function testDeleteCompositeKey()
{
if ( ! $this->storage->supportsCompositePrimaryKeys()) {
if (! $this->storage->supportsCompositePrimaryKeys()) {
$this->markTestSkipped('Composite keys need to be supported for this test to run.');
}
@@ -91,7 +91,7 @@ abstract class AbstractStorageTestCase extends \PHPUnit_Framework_TestCase
public function testFindCompositeKey()
{
if ( ! $this->storage->supportsCompositePrimaryKeys()) {
if (! $this->storage->supportsCompositePrimaryKeys()) {
$this->markTestSkipped('Composite keys need to be supported for this test to run.');
}
@@ -0,0 +1,102 @@
<?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\ArrayStorage;
use ReflectionProperty;
/**
* @author Emanuele Minotto <minottoemanuele@gmail.com>
*/
class ArrayStorageTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ArrayStorage
*/
private $storage;
protected function setup()
{
$this->storage = new ArrayStorage();
}
public function testSupportsPartialUpdates()
{
$this->assertFalse($this->storage->supportsPartialUpdates());
}
public function testSupportsCompositePrimaryKeys()
{
$this->assertFalse($this->storage->supportsCompositePrimaryKeys());
}
public function testRequiresCompositePrimaryKeys()
{
$this->assertFalse($this->storage->requiresCompositePrimaryKeys());
}
/**
* @dataProvider methodsProvider
*/
public function testInsert($method)
{
$data = [
'author' => 'John Doe',
'title' => 'example book',
];
$this->storage->$method('foo', 'bar', $data);
$reflector = new ReflectionProperty(ArrayStorage::class, 'data');
$reflector->setAccessible(true);
$storedValue = $reflector->getValue($this->storage);
$this->assertEquals(
[
'foo' => [
serialize('bar') => $data,
],
],
$storedValue
);
$this->storage->$method('foo', 'bar', $data);
$this->assertCount(1, $storedValue);
$this->assertCount(1, $storedValue['foo']);
}
/**
* @return array
*/
public function methodsProvider()
{
return [
['insert'],
['update'],
];
}
public function testGetName()
{
$this->assertEquals('array', $this->storage->getName());
}
}
@@ -0,0 +1,346 @@
<?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);
}
}
+1 -1
View File
@@ -18,7 +18,7 @@
* <http://www.doctrine-project.org>.
*/
if ( ! @include __DIR__ . '/../vendor/autoload.php') {
if (! @include __DIR__ . '/../vendor/autoload.php') {
die(<<<'EOT'
You must set up the project dependencies, run the following commands:
wget http://getcomposer.org/composer.phar
+2 -1
View File
@@ -9,4 +9,5 @@ cd ext && ./install.sh && cd "$TRAVIS_BUILD_DIR"
echo "extension=cassandra.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
# PHP extensions
yes | pecl install mongo
yes | pecl install redis
# PECL extensions
echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini