A DynamoDB based Eloquent model and Query builder for Laravel.

Overview

Laravel DynamoDB

test codecov

A DynamoDB based Eloquent model and Query builder for Laravel.

You can find an example implementation in kitar/simplechat.

Motivation

  • I want to use DynamoDB with Laravel. (e.g., authenticate with custom user provider)
  • I want to use a simple API which doesn't need to worry about cumbersome things like manually handling Expression Attributes.
  • I want to extend Laravel's code as much as I can to:
    • Rely on Laravel's robust codes.
    • keep the additional implementation simple and maintainable.
  • I don't want to make it fully compatible with Eloquent because DynamoDB is different from relational databases.
  • I'm longing for jessengers/laravel-mongodb. What if we have that for DynamoDB?

Installation

Install the package via Composer:

$ composer require kitar/laravel-dynamodb

Laravel (6.x, 7.x, 8.x, 9.x)

Add dynamodb configs to config/database.php:

'connections' => [

    'dynamodb' => [
        'driver' => 'dynamodb',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
        'token' => env('AWS_SESSION_TOKEN', null),
        'endpoint' => env('DYNAMODB_ENDPOINT', null),
        'prefix' => '', // table prefix
    ],

    ...

],

Update the DB_CONNECTION variable in your .env file:

DB_CONNECTION=dynamodb

Non-Laravel projects

For usage outside Laravel, you can create the connection manually and start querying with Query Builder.

$connection = new Kitar\Dynamodb\Connection([
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'token' => env('AWS_SESSION_TOKEN', null),
    'endpoint' => env('DYNAMODB_ENDPOINT', null),
    'prefix' => '', // table prefix
]);

$connection->table('your-table')->...

Sample data

Many of the example codes in this document are querying to DynamoDB's official sample data. If you want to try these codes with actual DynamoDB tables, it's handy to load them to your tables before.

Model

DynamoDB model extends Eloquent model so that we can use familiar features such as mutators, serialization, etc.

The main difference between Eloquent model and DynamoDB model is:

  • Eloquent model
    • Can handle relations.
    • Forward calls to model (Eloquent) query builder. (e.g., create, createOrFirst where with)
  • DynamoDB model
    • Cannot handle relations.
    • Forward calls to database (DynamoDB) query builder. (e.g., getItem, putItem, scan, filter)

Extending the base model

Most of the attributes are the same as the original Eloquent model, but there are few DynamoDB-specific attributes.

Name Required Description
table yes Name of the Table.
primaryKey yes Name of the Partition Key.
sortKey Name of the Sort Key.
sortKeyDefault Default value for the Sort Key.

For example, if our table has only partition key, the model will look like this:

use Kitar\Dynamodb\Model\Model;

class ProductCatalog extends Model
{
    protected $table = 'ProductCatalog';
    protected $primaryKey = 'Id';
    protected $fillable = ['Id', 'Price', 'Title'];
}

If our table also has sort key:

use Kitar\Dynamodb\Model\Model;

class Thread extends Model
{
    protected $table = 'Thread';
    protected $primaryKey = 'ForumName';
    protected $sortKey = 'Subject';
    protected $fillable = ['ForumName', 'Subject'];
}

If we set sortKeyDefault, it will be used when we instantiate or call find without sort key.

use Kitar\Dynamodb\Model\Model;
use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;

class User extends Model implements AuthenticatableContract
{
    use Authenticatable;

    protected $table = 'User';
    protected $primaryKey = 'email';
    protected $sortKey = 'type';
    protected $sortKeyDefault = 'profile';
    protected $fillable = [
        'name', 'email', 'password', 'type',
    ];
}

Note that this model is implementing Illuminate\Contracts\Auth\Authenticatable and using Illuminate\Auth\Authenticatable. This is optional, but if we use them, we can use this model with authentication as well. For authentication, please refer to Authentication section) for more details.

Basic Usage

Retrieving all models

$products = ProductCatalog::scan();

or alternatively,

$products = ProductCatalog::all();

You also can override the scan() method to fit your needs, such as filtering models for single table design. For example:

public static function scan($exclusiveStartKey = null, $sort = 'asc', $limit = 50)
{
    $products = static::index('GSI1')
                      ->keyCondition('GSI1PK', '=', 'PRODUCT#')
                      ->keyCondition('GSI1SK', 'begins_with', 'PRODUCT#')
                      ->exclusiveStartKey($exclusiveStartKey)
                      ->scanIndexForward($sort == 'desc' ? false : true)
                      ->limit($limit)
                      ->query();

    return [
        'items' => $products,
        'LastEvaluatedKey' => $products->first()->meta()['LastEvaluatedKey'] ?? null,
    ];
}

DynamoDB can only handle result set up to 1MB per call, so we have to paginate if there are more results. see Paginating the Results for more details.

Retrieving a model

If the model has only partition key:

ProductCatalog::find(101);

If the model also has sort key:

Thread::find([
    'ForumName' => 'Amazon DynamoDB', // Partition key
    'Subject' => 'DynamoDB Thread 1' // Sort key
]);

If the model has sort key and sortKeyDefault is defined:

User::find('[email protected]'); // Partition key. sortKeyDefault will be used for Sort key.

You also can modify the behavior of the find() method to fit your needs. For example:

public static function find($userId)
{
    return parent::find([
        'PK' => str_starts_with($userId, 'USER#') ? $userId : 'USER#'.$userId,
        'SK' => 'USER#',
    ]);
}

create()

$user = User::create([
    'email' => '[email protected]',
    'type' => 'profile' // Sort key. If we don't specify this, sortKeyDefault will be used.
]);

save()

$user = new User([
    'email' => '[email protected]',
    'type' => 'profile'
]);

$user->save();
$user->name = 'foo';
$user->save();

update()

$user->update([
    'name' => 'foobar'
]);

delete()

$user->delete();

increment() / decrement()

When we call increment() and decrement(), the Atomic Counter will be used under the hood.

$user->increment('views', 1);
$user->decrement('views', 1);

We can also pass additional attributes to update.

$user->increment('views', 1, [
    'last_viewed_at' => '...',
]);

Advanced Queries

We can use Query Builder functions through model such as query scan filter condition keyCondition etc.

For example:

Thread::keyCondition('ForumName', '=', 'Amazon DynamoDB')
        ->keyCondition('Subject', 'begins_with', 'DynamoDB')
        ->filter('Views', '=', 0)
        ->query();

Please refer to Query Builder for the details.

Authentication with model

We can create a Custom User Provider to authenticate with DynamoDB. For the detail, please refer to Laravel's official document.

To use authentication with the model, the model should implement Illuminate\Contracts\Auth\Authenticatable contract. In this section, we'll use the example User model above.

Register custom user provider

After we prepare authenticatable model, we need to make the custom user provider. We can make it own (it's simple), but we'll use Kitar\Dynamodb\Model\AuthUserProvider in this section.

To register custom user provider, add codes below in App/Providers/AuthServiceProvider.php.

use Kitar\Dynamodb\Model\AuthUserProvider;
...
public function boot()
{
    $this->registerPolicies();

    Auth::provider('dynamodb', function ($app, array $config) {
        return new AuthUserProvider(
            $app['hash'],
            $config['model'],
            $config['api_token_name'] ?? null,
            $config['api_token_index'] ?? null
        );
    });
}

Change auth config

Then specify driver and model name for authentication in config/auth.php.

'providers' => [
    // Eloquent
    // 'users' => [
    //     'driver' => 'eloquent',
    //     'model' => App\User::class,
    // ],

    // DynamoDB
    'users' => [
        'driver' => 'dynamodb',
        'model' => App\User::class,
        'api_token_name' => 'api_token',
        'api_token_index' => 'api_token-index'
    ],
],

api_token_name and api_token_index are optional, but we need them if we use api token authentication.

Registration Controller

You might need to modify the registration controller. For example, if we use Laravel Breeze, the modification looks like below.

class RegisteredUserController extends Controller
{
    ...

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => ['required', 'string', 'email', 'max:255', function ($attribute, $value, $fail) {
                if (User::find($value)) {
                    $fail('The '.$attribute.' has already been taken.');
                }
            }],
            'password' => 'required|string|confirmed|min:8',
        ]);

        $user = new User([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);
        $user->save();

        Auth::login($user);

        event(new Registered($user));

        return redirect(RouteServiceProvider::HOME);
    }
}

There are two modifications. The first one is adding the closure validator for email instead of unique validator. The second one is using the save() method to create user instead of the create() method.

Query Builder

We can use Query Builder without model.

$result = DB::table('Thread')->scan();

Or even outside Laravel.

$connection = new Kitar\Dynamodb\Connection([
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'token' => env('AWS_SESSION_TOKEN', null),
    'endpoint' => env('DYNAMODB_ENDPOINT', null),
    'prefix' => '', // table prefix
]);

$result = $connection->table('Thread')->scan();

If we query through the model, we don't need to specify the table name, and the response will be the model instance(s).

$threads = Thread::scan();

Basic Usage

getItem()

$response = DB::table('ProductCatalog')
                ->getItem(['Id' => 101]);

Instead of marshaling manually, pass a plain array. Kitar\Dynamodb\Query\Grammar will automatically marshal them before querying.

putItem()

DB::table('Thread')
    ->putItem([
        'ForumName' => 'Amazon DynamoDB',
        'Subject' => 'New discussion thread',
        'Message' => 'First post in this thread',
        'LastPostedBy' => '[email protected]',
        'LastPostedDateTime' => '201603190422'
    ]);

updateItem()

DB::table('Thread')
    ->key([
        'ForumName' => 'Laravel',
        'Subject' => 'Laravel Thread 1'
    ])->updateItem([
        'LastPostedBy' => null, // REMOVE
        'Replies' => null, // REMOVE
        'Message' => 'Updated' // SET
    ]);

Currently, we only support simple SET and REMOVE actions. If the attribute has value, it will be passed to SET action. If the value is null, it will be passed to REMOVE action.

deleteItem()

DB::table('Thread')
    ->deleteItem([
        'ForumName' => 'Amazon DynamoDB',
        'Subject' => 'New discussion thread'
    ]);

Projection Expressions

A Projection Expression is a string that identifies the attributes that web want. (It's like select statement for SQL)

select()

We can specify Projection Expressions in the same manner as the original select clause.

$response = DB::table('ProductCatalog')
                ->select('Price', 'Title')
                ->getItem(['Id' => 101]);

Condition Expressions

When we manipulate data in Amazon DynamoDB table, we use putItem, updateItem and DeleteItem. We can use Condition Expressions to determine which items should be modified.

condition()

To specify Condition Expression, we use condition clause. This works basically same as the original where clause, but it's for Condition Expressions.

DB::table('ProductCatalog')
    ->condition('Id', 'attribute_not_exists')
    ->putItem([
        'Id' => 101,
        'ProductCategory' => 'Can I overwrite?'
    ]);

Note that we specify attribute_not_exists for the operator of condition. This is DynamoDB-specific operator which called function. See DynamoDB-specific operators for condition() and filter() for more details.

OR statements

DB::table('ProductCatalog')
    ->condition('Id', 'attribute_not_exists')
    ->orCondition('Price', 'attribute_not_exists)
    ->putItem([...]);

AND statements

DB::table('ProductCatalog')
    ->condition('Id', 'attribute_not_exists')
    ->condition('Price', 'attribute_not_exists)
    ->putItem([...]);

conditionIn()

ProductCatalog::key(['Id' => 101])
                ->conditionIn('ProductCategory', ['Book', 'Bicycle'])
                ->updateItem([
                    'Description' => 'updated!'
                ]);

conditionBetween()

ProductCatalog::key(['Id' => 101])
                ->conditionBetween('Price', [0, 10])
                ->updateItem([
                    'Description' => 'updated!'
                ]);

Working with Queries

The Query operation in Amazon DynamoDB finds items based on primary key values.

query() and keyCondition()

When we query, we must specify keyCondition as well.

We can use some comparison operators for sort key, but we must use the equality condition for the partition key.

$response = DB::table('Thread')
                ->keyCondition('ForumName', '=', 'Amazon DynamoDB')
                ->keyCondition('Subject', 'begins_with', 'DynamoDB')
                ->query();

keyConditionBetween()

$response = DB::table('Thread')
                ->keyCondition('ForumName', '=', 'Amazon DynamoDB')
                ->keyConditionBetween('Subject', ['DynamoDB Thread 1', 'DynamoDB Thread 2'])
                ->query();

Sort order

query results are always sorted by the sort key value. To reverse the order, set the ScanIndexForward parameter to false.

$response = DB::table('Thread')
                ->keyCondition('ForumName', '=', 'Amazon DynamoDB')
                ->scanIndexForward(false)
                ->query();

Working with Scans

scan()

$response = DB::table('Thread')->scan();

Filtering the Results

When we query or scan, we can filter results with Filter Expressions before it returned.

It can't reduce the amount of read capacity, but it can reduce the size of traffic data.

filter()

$response = DB::table('Thread')
                ->filter('LastPostedBy', '=', 'User A')
                ->scan();

OR statement

$response = DB::table('Thread')
                ->filter('LastPostedBy', '=', 'User A')
                ->orFilter('LastPostedBy', '=', 'User B')
                ->scan();

AND statement

$response = DB::table('Thread')
                ->filter('LastPostedBy', '=', 'User A')
                ->filter('Subject', 'begins_with', 'DynamoDB')
                ->scan();

filterIn()

$response = DB::table('Thread')
                ->filterIn('LastPostedBy', ['User A', 'User B'])
                ->scan();

filterBetween()

$response = DB::table('ProductCatalog')
                ->filterBetween('Price', [0, 100])
                ->scan();

Paginating the Results

A single query or scan only returns a result set that fits within the 1 MB size limit. If there are more results, we need to paginate.

exclusiveStartKey()

If there are more results, the response contains LastEvaluatedKey.

$response = DB::table('ProductCatalog')
                ->limit(5)
                ->scan();

$response['LastEvaluatedKey']; // array

We can pass this key to exclusiveStartKey to get next results.

$response = DB::table('ProductCatalog')
                ->exclusiveStartKey($response['LastEvaluatedKey'])
                ->limit(5)
                ->scan();

If you are using Query Builder through model, you can access to exclusiveStartKey by:

$products = ProductCatalog::limit(5)->scan();

$products->first()->meta()['LastEvaluatedKey']; // array

Using Global Secondary Indexes

Some applications might need to perform many kinds of queries, using a variety of different attributes as query criteria. To support these requirements, you can create one or more global secondary indexes and issue query requests against these indexes in Amazon DynamoDB.

index()

Use index clause to specify Global Secondary Index name.

$response = DB::table('Reply')
                ->index('PostedBy-Message-index')
                ->keyCondition('PostedBy', '=', 'User A')
                ->keyCondition('Message', '=', 'DynamoDB Thread 2 Reply 1 text')
                ->query();

Atomic Counter

DynamoDB supports Atomic Counter. When we call increment() and decrement() through Model or Query Builder, Atomic Counter will be used under the hood.

DB::('Thread')->key([
    'ForumName' => 'Laravel',
    'Subject' => 'Laravel Thread 1'
])->increment('Replies', 2);

We can also pass additional attributes to update.

DB::('Thread')->key([
    'ForumName' => 'Laravel',
    'Subject' => 'Laravel Thread 1'
])->increment('Replies', 2, [
    'LastPostedBy' => 'User A',
]);

DynamoDB-specific operators for condition() and filter()

For condition and filter clauses, we can use DynamoDB's comparators and functions.

Comparators

= <> < <= > >= can be used in the form of:

filter($key, $comparator, $value);

functions

Available functions are:

filter($key, 'attribute_exists');
filter($key, 'attribute_not_exists');
filter($key, 'attribute_type', $type);
filter($key, 'begins_with', $value);
filter($key, 'contains', $value);

size function is not supported at this time.

Testing

$ ./vendor/bin/phpunit
Comments
  • Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization.

    Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization.

    Hey Guys,

    i am a newby at Laravel + DynomoDB. I followed what is in the readme file. unfortunately I get the following error. What's the problem here?

    Aws\DynamoDb\Exception\DynamoDbException

    Error executing "Scan" on "dynamodb.eu-central-1.amazonaws.com"; AWS HTTP error: Client error: POST dynamodb.eu-central-1.amazonaws.com resulted in a 400 Bad Request response: {"__type":"com.amazon.coral.service#InvalidSignatureException","message":"'Host' or ':authority' must be a 'SignedHeader (truncated...) InvalidSignatureException (client): 'Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization. - {"__type":"com.amazon.coral.service#InvalidSignatureException","message":"'Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization."}

    opened by wwwDESIGN-basti 10
  • Marshal values before batch writing

    Marshal values before batch writing

    NOT TESTET

    This is pretty hard to test. Yesterday i changed our internal systems to use the kitar version of batch write items, instead of our own implementation. We are doing aprox 50.000 writes a day, and we began to see some errors where the structure is not valid for batch write items. So i compared our solution wiith the one in Kitar - and the only difference i can see is that we were marshalling.

    Here' the error we got: image

    Left this as a draft - to start a coversation ๐Ÿ˜„

    opened by negoziator 6
  • Add batch write item

    Add batch write item

    ---> DRAFT PULL REQUEST <---

    Description

    Starting to work on a implentation for batch writing items. This is to gain a lot of performance when inserting many items.

    Let's see if this can be done in a clean way.

    Please feel free to join in ๐Ÿ˜„

    Notes:

    • BatchWriteItem only supports insert of 25 items at a time - so probably something needs to check that.
    • BatchWriteItem does not support updates
    • The model layer don't understand "saveMany" in one go, so the query builder has to implement it.

    Documentation https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html

    opened by negoziator 6
  • Call to undefined method Illuminate\Database\Query\Builder::usingModel()

    Call to undefined method Illuminate\Database\Query\Builder::usingModel()

    I am getting error

    Call to undefined method Illuminate\Database\Query\Builder::usingModel()
    

    when trying to use package, for example

    $refunds = RefundsLog::scan();
    

    Laravel v8.83.12

    opened by andrewboohoo 5
  • Config parameter names in README incorrect

    Config parameter names in README incorrect

    In the Readme of this repo it states to use key and secret when creating a connection

    'connections' => [
    
        'dynamodb' => [
            'driver' => 'dynamodb',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
            'token' => env('AWS_SESSION_TOKEN', null),
            'endpoint' => env('DYNAMODB_ENDPOINT', null),
            'prefix' => '', // table prefix
        ],
    
        ...
    
    ],
    

    however in Connection.php the sdk is initiated as:

    $sdk = new AwsSdk([
                'region' => $config['region'] ?? 'us-east-1',
                'version' => $config['version'] ?? 'latest',
                'credentials' => [
                    'key' => $config['access_key'] ?? '',
                    'secret' => $config['secret_key'] ?? ''
                ]
            ]);
    

    where the key is expected to come from a config parameter called access_key and the secret from a config parameter named secret_key.

    opened by martsie 4
  • Call to undefined method Kitar\Dynamodb\Query\Builder::getQuery()

    Call to undefined method Kitar\Dynamodb\Query\Builder::getQuery()

    Hi,

    First thanks for the great work!

    I am having an issue with the "missing" method getQuery() when attempting to fetch the records from a model via Laravel Nova.

    I am using two databases, mysql and dynamdb. The system is writing to both. I cannot however read via Nova from the Dynamodb.

    I get the above exception triggered from the BadMethodException on line 420 of Kitar\Dynamodb\Query\Builder.

    From what I can understand of the Eloquent Query Builder all that getQuery() does is return the query object which be this stage is already defined.

    Any assistance would be greatly appreciated.

    Thanks

    opened by rubenspeculis 3
  • Sorting does not work

    Sorting does not work

    Hi, I am trying to sort the data with sort key but it's not working. Schema:

            $schema = [
                'AttributeDefinitions' => [
                    [
                        'AttributeName' => 'id',
                        'AttributeType' => 'S',
                    ],
                    [
                        'AttributeName' => 'customer_id',
                        'AttributeType' => 'N',
                    ],
                    [
                        'AttributeName' => 'created_at',
                        'AttributeType' => 'S',
                    ],
                ],
                'TableName' => $this->tableName,
                'KeySchema' => [
                    [
                        'AttributeName' => 'id',
                        'KeyType' => 'HASH',
                    ],
                    [
                        'AttributeName' => 'created_at',
                        'KeyType' => 'RANGE',
                    ],
                ],
                'StreamSpecification' => [
                    'StreamEnabled' => true,
                    'StreamViewType' => 'NEW_AND_OLD_IMAGES',
                ],
                'GlobalSecondaryIndexes' => [
                    [
                        'IndexName' => 'gsi_0',
                        'Projection' => [
                            'ProjectionType' => 'ALL',
                        ],
                        'KeySchema' => [
                            [
                                'AttributeName' => 'customer_id',
                                'KeyType' => 'HASH',
                            ],
                            [
                                'AttributeName' => 'created_at',
                                'KeyType' => 'RANGE',
                            ],
                        ],
                        'BillingMode' => 'PAY_PER_REQUEST',
                    ],
                ],
                'BillingMode' => 'PAY_PER_REQUEST',
            ];
    

    Model:

    class AuditLog extends Model
    {
        protected $connection= 'dynamodb';
        protected $table = "";
        protected $primaryKey = 'id';
        protected $sortKey = 'created_at';
        protected $fillable = ["id", "log_text", "customer_id", "created_at"];
    }
    

    Controller:

    $data = AuditLog::limit(5)->scanIndexForward(false)->scan();
    $response = [
        "success" => true,
        "data" => $data, 
    ];
    return response($response, 201);
    
    opened by mohsinsiyal-tsi 2
  • Single Table Design

    Single Table Design

    Do you plan to add Single Table Design pattern?

    https://aws.amazon.com/es/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/ https://github.com/sensedeep/dynamodb-onetable https://github.com/tywalch/electrodb#readme

    opened by amesas 2
  • Filter model related records when using scan()

    Filter model related records when using scan()

    DynamoDB, by its nature, often stores multiple models in a single table.

    This is not a problem when writing a query to narrow down like this,

    App\Models\User::filter('sort', '=', 'profile')->scan();
    

    but because we are scanning the model, it is more natural to just scan it.

    App\Models\User::scan();
    

    Global Scopes seems useful in this case, but since Scope is a feature of Eloquent Builder and we don't extend it (we extend Database Query Builder), we can't use Laravel's scope feature as it is.

    enhancement 
    opened by kitar 2
  • Support BatchGetItem and BatchWriteItem

    Support BatchGetItem and BatchWriteItem

    Working on an implementation for BatchGetItem and BatchWriteItem, inspired by these experiments: #25, #32. (Closes #32, closes #33)

    Usage

    via query builder

    $query->batchGetItem($keys);
    $query->batchWriteItem($requestItems);
    
    // handy methods for batchWriteItem
    $query->batchPutItem($items);
    $query->batchDeleteItem($keys);
    

    via Model

    MyModel::batchGetItem($keys);
    MyModel::batchWriteItem($requestItems);
    
    // handy methods for batchWriteItem
    MyModel::batchPutItem($items);
    MyModel::batchDeleteItem($keys);
    

    Params

    $keys

    Array of keys to get or delete. For example:

    [
      [
        'PK' => 'ITEM#',
        'SK' => 'ITEM#1',
      ],
      // ...
    ]
    

    $items

    Array of items to put. For example:

    [
      [
        'PK' => 'ITEM#',
        'SK' => 'ITEM#1',
        'attribute1' => 'foo',
        'attribute2' => 'bar',
      ],
      // ...
    ]
    

    $requestItems

    RequestItems (not marshaled) to pass directly to BatchWriteItem. For example:

    [
      [
        'PutRequest' => [
          'Item' => [
            'PK' => 'ITEM#',
            'SK' => 'ITEM#1',
            'attribute1' => 'foo',
            'attribute2' => 'bar',
          ],
        ],
      ],
      [
        'DeleteRequest' => [
          'Item' => [
            'PK' => 'ITEM#',
            'SK' => 'ITEM#1',
          ],
        ],
      ],
    ]
    
    opened by kitar 1
  • Fix return types

    Fix return types

    • Fixes return types to be fully qualified to enable code analysers to check return types properly. Example running phpstan level 9:
      20     Access to offset 0 on an unknown class Kitar\Dynamodb\Model\Illuminate\Database\Eloquent\Collection.                                                                                        
             ๐Ÿ’ก Learn more at https://phpstan.org/user-guide/discovering-symbols      
      
    • Implement PSR coding standards for new objects.
    • Fix typo on comment
    opened by madrussa 1
  • Deserializing model in event constructor fails for a queued event listener?

    Deserializing model in event constructor fails for a queued event listener?

    Hello. Ty for the great package. Using it I've come across a serialization issue when using queued event listeners. Seems like it's trying to connect on a null connection? Like it's reverting to the default database driver? Using the model created event and listening to that event with a queued event listener fails. I can toArray() or pass the DynamoDB partition keys to do a lookup but seems like laravel should be able to hydrate no matter the driver right?

    Laravel 9.x laravel-dynamodb 1.x

    [2022-06-23 01:20:15] local.ERROR: Call to a member function prepare() on null {"exception":"[object] (Error(code: 0): Call to a member function prepare() on null at /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Connection.php:396)
    [stacktrace]
    #0 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Connection.php(735): Illuminate\\Database\\Connection->Illuminate\\Database\\{closure}('select * from \"...', Array)
    #1 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Connection.php(702): Illuminate\\Database\\Connection->runQueryCallback('select * from \"...', Array, Object(Closure))
    #2 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Connection.php(404): Illuminate\\Database\\Connection->run('select * from \"...', Array, Object(Closure))
    #3 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2630): Illuminate\\Database\\Connection->select('select * from \"...', Array, false)
    #4 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2618): Illuminate\\Database\\Query\\Builder->runSelect()
    #5 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(3154): Illuminate\\Database\\Query\\Builder->Illuminate\\Database\\Query\\{closure}()
    #6 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(2619): Illuminate\\Database\\Query\\Builder->onceWithColumns(Array, Object(Closure))
    #7 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(698): Illuminate\\Database\\Query\\Builder->get(Array)
    #8 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(682): Illuminate\\Database\\Eloquent\\Builder->getModels(Array)
    #9 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php(74): Illuminate\\Database\\Eloquent\\Builder->get()
    #10 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php(56): App\\Events\\SystemEvents\\SystemEventCreated->restoreCollection(Object(Illuminate\\Contracts\\Database\\ModelIdentifier))
    #11 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php(126): App\\Events\\SystemEvents\\SystemEventCreated->getRestoredPropertyValue(Object(Illuminate\\Contracts\\Database\\ModelIdentifier))
    #12 [internal function]: App\\Events\\SystemEvents\\SystemEventCreated->__unserialize(Array)
    #13 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(96): unserialize('O:36:\"Illuminat...')
    #14 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(257): Illuminate\\Queue\\CallQueuedHandler->getCommand(Array)
    #15 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(213): Illuminate\\Queue\\CallQueuedHandler->failed(Array, Object(Error), 'c02f0cf2-5f3f-4...')
    #16 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(192): Illuminate\\Queue\\Jobs\\Job->failed(Object(Error))
    #17 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(581): Illuminate\\Queue\\Jobs\\Job->fail(Object(Error))
    #18 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(527): Illuminate\\Queue\\Worker->failJob(Object(Illuminate\\Queue\\Jobs\\DatabaseJob), Object(Error))
    #19 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(455): Illuminate\\Queue\\Worker->markJobAsFailedIfWillExceedMaxAttempts('database', Object(Illuminate\\Queue\\Jobs\\DatabaseJob), 1, Object(Error))
    #20 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(432): Illuminate\\Queue\\Worker->handleJobException('database', Object(Illuminate\\Queue\\Jobs\\DatabaseJob), Object(Illuminate\\Queue\\WorkerOptions), Object(Error))
    #21 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(378): Illuminate\\Queue\\Worker->process('database', Object(Illuminate\\Queue\\Jobs\\DatabaseJob), Object(Illuminate\\Queue\\WorkerOptions))
    #22 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(329): Illuminate\\Queue\\Worker->runJob(Object(Illuminate\\Queue\\Jobs\\DatabaseJob), 'database', Object(Illuminate\\Queue\\WorkerOptions))
    #23 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(130): Illuminate\\Queue\\Worker->runNextJob('database', 'unified-default...', Object(Illuminate\\Queue\\WorkerOptions))
    #24 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(114): Illuminate\\Queue\\Console\\WorkCommand->runWorker('database', 'unified-default...')
    #25 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Illuminate\\Queue\\Console\\WorkCommand->handle()
    #26 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Container/Util.php(41): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
    #27 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\\Container\\Util::unwrapIfClosure(Object(Closure))
    #28 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\\Container\\BoundMethod::callBoundMethod(Object(Illuminate\\Foundation\\Application), Array, Object(Closure))
    #29 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Container/Container.php(651): Illuminate\\Container\\BoundMethod::call(Object(Illuminate\\Foundation\\Application), Array, Array, NULL)
    #30 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Console/Command.php(136): Illuminate\\Container\\Container->call(Array)
    #31 /var/www/unified-api/vendor/symfony/console/Command/Command.php(291): Illuminate\\Console\\Command->execute(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
    #32 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Console/Command.php(121): Symfony\\Component\\Console\\Command\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Illuminate\\Console\\OutputStyle))
    #33 /var/www/unified-api/vendor/symfony/console/Application.php(998): Illuminate\\Console\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
    #34 /var/www/unified-api/vendor/symfony/console/Application.php(299): Symfony\\Component\\Console\\Application->doRunCommand(Object(Illuminate\\Queue\\Console\\WorkCommand), Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
    #35 /var/www/unified-api/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
    #36 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Console/Application.php(102): Symfony\\Component\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
    #37 /var/www/unified-api/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(129): Illuminate\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
    #38 /var/www/unified-api/artisan(37): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
    #39 {main}
    "} 
    
    opened by localpath 6
  • Support Transaction

    Support Transaction

    Since DynamoDB uses HTTP connection model, it's important to keep requests count as few as possible. For example, if we try to maintain "likes" count, we would do:

    1. Add like if the user didn't liked yet.
    2. If added, increment the counter.

    We can execute them in a single request with Transaction.

    enhancement 
    opened by kitar 0
Releases(v1.1.0)
  • v1.1.0(Dec 23, 2022)

  • v1.0.2(Dec 12, 2022)

  • v1.0.1(Aug 26, 2022)

  • v1.0.0(Mar 24, 2022)

    • Support Laravel 9.x (Thanks @andreagroferreira !)
    • Support PHP 8.1

    Since I've been using this package in a few production apps for several months, I think it's time to call it v1.0.0. However, there are no major changes. It's fully backward compatible. Please feel free to submit any questions, feature requests, and PRs.

    Source code(tar.gz)
    Source code(zip)
  • v0.6.0(Feb 5, 2022)

    • Support Model::create() method (Thanks @marvinosswald !)

    I'm already using this package in a few production apps, so I think it's time to tag it as 1.0 shortly. (maybe when we support Laravel 9)

    Source code(tar.gz)
    Source code(zip)
  • v0.5.0(Nov 13, 2021)

    • Align config options format with Laravel's cache driver #11 (this is backward compatible, thanks @paulhenri-l !)
    • Support table prefix #12 (thanks @paulhenri-l !)
    • Support ScanIndexForward option 524180e068736c81ece82f3c7d336d2f9a77b815
    • Support dynamic table name in the model 2c99077b154dbbcd02422f7ca06960b996f1b700
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Mar 22, 2021)

  • v0.3.0(Jan 22, 2021)

  • v0.2.0(Mar 25, 2020)

    Breaking change (Laravel users only)

    The purpose of this change is to make AuthUserProvider more testable and support api_token authentication.

    App/Providers/AuthServiceProvider.php

    Change the registration method.

    before

    use Kitar\Dynamodb\Model\AuthUserProvider;
    ...
    public function boot()
    {
        $this->registerPolicies();
    
        Auth::provider('dynamodb', function ($app, array $config) {
            return new AuthUserProvider(new $config['model']);
        });
    }
    

    after

    use Kitar\Dynamodb\Model\AuthUserProvider;
    ...
    public function boot()
    {
        $this->registerPolicies();
    
        Auth::provider('dynamodb', function ($app, array $config) {
            return new AuthUserProvider(
                $app['hash'],
                $config['model'],
                $config['api_token_name'] ?? null,
                $config['api_token_index'] ?? null
            );
        });
    }
    

    config/auth.php

    Optionally, add api_token_name and api_token_index if you use this package with api_token authentication.

    old

    'providers' => [
        'users' => [
            'driver' => 'dynamodb',
            'model' => App\User::class
        ],
    ],
    

    new

    'providers' => [
        'users' => [
            'driver' => 'dynamodb',
            'model' => App\User::class,
            'api_token_name' => 'api_token',
            'api_token_index' => 'api_token-index'
        ],
    ],
    
    Source code(tar.gz)
    Source code(zip)
Owner
Satoshi Kita
Satoshi Kita
A package to filter laravel model based on query params or retrieved model collection

Laravel Filterable A package to filter laravel model based on query params or retrived model collection. Installation Require/Install the package usin

Touhidur Rahman 17 Jan 20, 2022
Stop duplicating your Eloquent query scopes and constraints in PHP. This package lets you re-use your query scopes and constraints by adding them as a subquery.

Laravel Eloquent Scope as Select Stop duplicating your Eloquent query scopes and constraints in PHP. This package lets you re-use your query scopes an

Protone Media 75 Dec 7, 2022
Provides a Eloquent query builder for Laravel or Lumen

This package provides an advanced filter for Laravel or Lumen model based on incoming requets.

M.Fouladgar 484 Jan 4, 2023
An example chat app to illustrate the usage of kitar/laravel-dynamodb.

Simplechat An example chat app to illustrate the usage of kitar/laravel-dynamodb. Demo https://demo.simplechat.app/ This demo app is deployed with Lar

Satoshi Kita 38 Nov 22, 2022
A laravel package to generate model hashid based on model id column.

Laravel Model Hashid A package to generate model hash id from the model auto increment id for laravel models Installation Require the package using co

Touhidur Rahman 13 Jan 20, 2022
A WPDB wrapper and query builder library.

DB A WPDB wrapper and query builder library. Installation It's recommended that you install DB as a project dependency via Composer: composer require

StellarWP 35 Dec 15, 2022
A simple to use query builder for the jQuery QueryBuilder plugin for use with Laravel.

QueryBuilderParser Status Label Status Value Build Insights Code Climate Test Coverage QueryBuilderParser is designed mainly to be used inside Laravel

Tim Groeneveld 149 Nov 11, 2022
Update multiple Laravel Model records, each with it's own set of values, sending a single query to your database!

Laravel Mass Update Update multiple Laravel Model records, each with its own set of values, sending a single query to your database! Installation You

Jorge Gonzรกlez 88 Dec 31, 2022
Laravel basic Functions, eloquent cruds, query filters, constants

Emmanuelpcg laravel-basics Description Package with basic starter features for Laravel. Install If Builder Constants Install composer require emmanuel

Emmanuel Pereira Pires 3 Jan 1, 2022
This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

Jonas Staudenmeir 5 Jan 6, 2023
A laravel package to handle sanitize process of model data to create/update model records.

Laravel Model UUID A simple package to sanitize model data to create/update table records. Installation Require the package using composer: composer r

null 66 Sep 19, 2022
Laravel-model-mapper - Map your model attributes to class properties with ease.

Laravel Model-Property Mapper This package provides functionality to map your model attributes to local class properties with the same names. The pack

Michael Rubel 15 Oct 29, 2022
A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache.

Laravel OTP Introduction A package for Laravel One Time Password (OTP) generator and validation without Eloquent Model, since it done by Cache. The ca

Lim Teck Wei 52 Sep 6, 2022
Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Laravel comments - This package enables to easily associate comments to any Eloquent model in your Laravel application

Rubik 4 May 12, 2022
Collection of the Laravel/Eloquent Model classes that allows you to get data directly from a Magento 2 database.

Laragento LAravel MAgento Micro services Magento 2 has legacy code based on abandoned Zend Framework 1 with really ugly ORM on top of outdated Zend_DB

Egor Shitikov 87 Nov 26, 2022
Laravel Quran is static Eloquent model for Quran.

Laravel Quran ุจูุณู’ู…ู ูฑู„ู„ู‘ูฐู‡ู ุงู„ุฑูŽู‘ุญู’ู…ูฐู†ู ุงู„ุฑูŽู‘ุญููŠู’ู…ู Laravel Quran is static Eloquent model for Quran. The Quran has never changed and never will, bec

Devtical 13 Aug 17, 2022
Generate UUID for a Laravel Eloquent model attribute

Generate a UUIDv4 for the primary key or any other attribute on an Eloquent model.

Alex Bouma 4 Mar 1, 2022
Laravel package to create autonumber for Eloquent model

Laravel AutoNumber Laravel package to create autonumber for Eloquent model Installation You can install the package via composer: composer require gid

null 2 Jul 10, 2022
Turn any Eloquent model into a list!

Listify Turn any Eloquent model into a list! Description Listify provides the capabilities for sorting and reordering a number of objects in a list. T

Travis Vignon 138 Nov 28, 2022