Coder Social home page Coder Social logo

autoprotect-group / php-dynamodb-odm Goto Github PK

View Code? Open in Web Editor NEW
3.0 4.0 2.0 188 KB

This is a PHP library and an Object Document Mapper to use with AWS DynamoDB in a more convenient way

License: Apache License 2.0

Dockerfile 0.14% PHP 95.60% Gherkin 4.26%
aws dynamodb object-document-mapper odm php php81

php-dynamodb-odm's Introduction

AWS Dynamodb ODM for PHP

Code style, unit and functional tests

This is a library and an Object Document Mapper to use with AWS DynamoDB in a more convenient way.

Usage

Configure the ODM

Set up native client:

$dynamoDbClient = new DynamoDbClient(array_merge(
    [
        'region' => 'eu-west-2',
        'version' => 'latest',
    ]
));

Set up the main operations lib client:

$client = new DynamodbOperationsClient($dynamoDbClient);

Set up the marshaller. Native AWS marshaller may be taken:

$marshaler = new Marshaler();

Set up the Query builder:

$queryBuilder = new QueryBuilder($marshaler, new ExpressionFactory($marshaler));

Set up annotation reader and annotation manager:

// annotation reader
$annotationReader = new AnnotationReader();

// annotation manager
$annotationManager = new AnnotationManager($annotationReader);

The hydrators for the models:

$newModelHydrator = new Hydrator(NewModel::class, $annotationManager);
$sortKeyModelHydrator = new Hydrator(SortKeyModel::class, $annotationManager);

Serializer for inserting records into DB:

// serializer for
$serializer = new Serializer($annotationManager);

The full example is in here.

Model

Model field types

The lib operates with models. Each model may have various supported field types. Here is a list of types which correlate with PHP and Dynamodb types:

  • BooleanType: boolean for DynamoDb and for php
  • CollectionType: list for DynamoDb, in php it's an array list of items of specific model
  • DateType: string for DynamoDb, DateTime for php
  • EnumType: string for DynamoDb, enum for php
  • FloatType: number for DynamoDb, float for php
  • HashMapType: map for DynamoDb, in php it's associative array of items of specific model
  • IntegerType: number for DynamoDb, int for php
  • ModelType: map for DynamoDb, instance of model for php
  • Money: map for DynamoDb, special MoneyObject for PHP. Money value as a concept
  • NumberType: number for DynamoDb. An abstract type, not a handy one. My be used occasionally
  • ScalarCollectionType: map for DynamoDb. in php it's associative array of any dynamodb compatible types excluding CollectionType, HashMapType or ModelType
  • StringType: string for DynamoDb and for php

Here is a model example:

class ExampleDemoModel extends Model
{
    protected const TABLE_NAME = 'test-table';
    
    // Primary means that this is a partition key for the DynamoDb table
    #[StringType, Primary]  protected string $id;
    #[StringType]           protected string $name;
    #[FloatType]            protected float $price;
    #[Money]                protected Money $priceNet;
    #[FloatType]            protected float $percent;
    #[IntegerType]          protected int $itemsAmount;
    #[DateType]             protected DateTime $createdAt;
    #[BooleanType]          protected bool $isDeleted;
    #[BooleanType]          protected bool $isPhoneNumber;
    #[ModelType([ModelType::MODEL_CLASS_NAME => RelatedModel::class])]
    protected RelatedModel $buyer;
    #[CollectionType([CollectionType::MODEL_CLASS_NAME => RelatedModel::class])]
    protected array $buyers;
    #[ModelType([Asset::MODEL_CLASS_NAME => Asset::class])]
    protected Asset $asset;
    #[HashMapType([HashMapType::MODEL_CLASS_NAME => RelatedModel::class])]
    protected array $buyersMap;
    
    // getter and setters should be here    
 }

Model full example is here: model.php

Enumerations example

Enumerations are also supported. Here is an example of the model with enumeration fields:

class ModelWithEnumeration extends Model
{
    #[Primary, StringType]
    protected string $id;

    #[EnumType]
    protected OrderStatus $orderStatus;
    
    // union types
    #[EnumType]
    protected OrderStatus|ApplicationStatus $unionStatus;

    // union types with null
    #[EnumType]
    protected OrderStatus|ApplicationStatus|null $unionNullableStatus;

    // isStrict means the value will be null in case wrong value comes from the DB
    #[EnumType(isStrict: false)]
    protected ?OrderStatus $orderStatusAdditional = null;

    #[EnumType]
    protected CustomerType $customerType;
}

Fields encryption

Certain types custom encryption is supported. In case there are some fields which needs to be encrypted.

First of all we need to create a custom encryptor:

MyEncryptor implements EncryptorInterface {
    protected const ENCRYPTION_KEY = 'def000008053addc0f94b14c0e480a10631a0a970b3565e5a7a2aeaeeb51a39e2d139a8977bc02be0195f0036a29aefff9df6d2ddb81432d14b4dce82b83b3a95c6d0205';

    public function decrypt(string|array $encryptedData, array $options = []): string|array
    {
        // any decryption way may be implemented
        if (is_array($encryptedData)) {
            // ...specific property decryption operations...
            return $encryptedData;
        }
        
        return Crypto::decrypt(
            $encryptedData,
            Key::loadFromAsciiSafeString(static::ENCRYPTION_KEY)
        );
    }
}

Then the decryptor should be passed into the hydrator:

$newModelHydrator = new Hydrator(
    EncryptionDemoModel::class,
    $annotationManager,
    new MyEncryptor(),
);

And the model may be the following:

class EncryptionDemoModel extends Model
{
    #[Key\Primary, Types\StringType]
    protected string $id;

    // ability to encrypt a specific property in a scalar associative  array 
    #[Types\ScalarCollectionType, Encrypted(["encryptedProperty" => "secretProperty"])]
    protected array $encryptedArray;

    #[Types\StringType, Encrypted]
    protected string $encryptedName;
}

Set up the repository for your model

The best way to operate with records is to create a repository. There is a built-in already:

$newModelDynamoDbRepository = new DynamoDBRepository(
    NewModel::class,
    $client,
    $queryBuilder,
    $newModelHydrator,
    $annotationManager,
    $marshaler,
    $serializer
);

There are built-in operation in the default repository.

Get model by partition Id

Just by partition key
$foundModel = $newModelDynamoDbRepository->get($id);
By partition key and sort key
$foundModel = $newModelDynamoDbRepository->get($id, $sortKey);
Non-consistent read
$foundModel = $newModelDynamoDbRepository->get($id, $sortKey, false);
Get one item
$foundModel = $newModelDynamoDbRepository->getOneById($id, $sortKey, false);

Insert model

$newModelDynamoDbRepository->save($model);

Delete item

$newModelDynamoDbRepository->delete($model);

Document repository

Sometimes we need to fetch not the whole model, but just a part of it. For this purpose there is such called DocumentRepository. The part of the document may be technically fetched using native DynamoDb projection expressions.

Setting DocumentRepository up:

$documentRepository = new DocumentRepository(
    NewModelNested::class,
    $client,
    $this->queryBuilder,
    $this->newModelHydrator,
    $annotationManager,
    $marshaler,
    $serializer
);

Get a model by projection expression

$projectionExpression = "property.subPropertyModel";

$model = $documentRepository->getDocument()
    ->setConsistentRead(true)
    ->withAttrPath($projectionExpression)
    ->withPrKey($keyValue)
    ->execute()
;

Get a specific scalar property by projection expression

$projectionExpression = "property.subPropertyModel.name";

$name = $this->documentRepository->getDocumentProperty()
    ->setConsistentRead(true)
    ->withAttrPath($projection)
    ->withPrKey($keyValue)
    ->execute()
;

Get/create/update/delete operations

Document repository supports specific property get/create/update/delete operations:

  • createDocument()
  • updateDocument()
  • removeDocument()
  • getDocumentCollection()
  • updateDocumentCollection()
  • createDocumentCollection()

Query builder

Another powerful feature is query builders. This adds flexibility to fetch items by specific criteria which is supported by DynamoDB.

This is a way to work with the Dynamodb using raw queries and results

Get query builder

Fetch items:

$getItemQuery = $queryBuilder
    ->getItem(self::DB_TABLE)
    ->itemKey([$itemKey => $keyValue])
    ->getQuery();
    
$item = $this->dynamoDbClient
    ->getItem($getItemQuery)->get('Item');

Update query builder

Ability to update specific attributes.

$attributesForUpdate = [
   "numberProp" => 2, 
   "stringProp" => "updated string value", 
   "hashMapProp.map-id-1.type" => "updated map-type-1", 
   "hashMapProp.map-id-1.mapProp" => "updated mapProp", 
   "listProp" => [
         "updated listProp 1", 
         "updated listProp 2" 
      ] 
]; 

$getItemQuery = $queryBuilder
    ->updateItem(self::DB_TABLE)
    ->itemKey([$itemKey => $keyValue])
    ->attributes($attributesForUpdate)
    ->getQuery();

$dynamoDbClient->updateItem($getItemQuery);

Local dev environment installation

  1. In order to build a dev image, please, run:
docker-compose build
  1. Then run to install dependencies:
docker-compose run --no-deps dynamodb-odm composer install

Running tests

Unit tests

This package uses phpspec for running unit tests.

Run them using the following way:

docker-compose run --no-deps dynamodb-odm vendor/bin/phpspec run

One can use environment variables in the .env.local file to be able to debug the library. For this just Copy file .env.local.sample into .env.local and set up the variable according to your OS.

And then run the tests with:

docker-compose --env-file ./.env.local run  --no-deps dynamodb-odm vendor/bin/phpspec run

Functional tests

This package uses behat for running functional tests.

Then just run the tests:

docker-compose run dynamodb-odm vendor/bin/behat -c behat.yml --stop-on-failure

Syntax check tests

You need to check if the code style is OK by running:

docker-compose run --no-deps dynamodb-odm vendor/bin/phpcs --standard=/application/phpcs.xml

php-dynamodb-odm's People

Contributors

gep avatar ginopane avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

gep ginopane

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.