Skip to content

Commit c30fdf4

Browse files
Eloquent save, update and delete method async execution using promises
Eloquent save method asynchronously and tests for createAsync and saveAsync fix issue `type GuzzleHttp\Promise\Promise, thus it always evaluated to true` Implemented javascript like event firing on promise resolution SaveAsync completed Eloquent delete method asynchronously and tests for deleteAsync Update method async and readme updated Nitpick CI fixes fix readme and documentation
1 parent 3d443de commit c30fdf4

File tree

5 files changed

+316
-3
lines changed

5 files changed

+316
-3
lines changed

README.md

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ Supports all key types - primary hash key and composite keys.
2020
* [Conditions](#conditions)
2121
* [all() and first()](#all-and-first)
2222
* [Pagination](#pagination)
23-
* [Update](#update)
24-
* [Save](#save)
23+
* [Update](#update) / [updateAsync()](#updateasync)
24+
* [Save](#save) / [saveAsync()](#saveasync)
25+
* [Delete](#delete) / [deleteAsync()](#deleteasync)
2526
* [Chunk](#chunk)
2627
* [limit() and take()](#limit-and-take)
2728
* [firstOrFail()](#firstorfail)
@@ -93,6 +94,7 @@ Usage
9394
* Extends your model with `BaoPham\DynamoDb\DynamoDbModel`, then you can use Eloquent methods that are supported. The idea here is that you can switch back to Eloquent without changing your queries.
9495
* Or if you want to sync your DB table with a DynamoDb table, use trait `BaoPham\DynamoDb\ModelTrait`, it will call a `PutItem` after the model is saved.
9596
* Alternatively, you can use the [query builder](#query-builder) facade to build more complex queries.
97+
* AWS SDK v3 for PHP uses guzzlehttp promises to allow for asynchronous workflows. Using this package you can run eloquent queries like [delete](#find-and-delete), [update](#updateasync), [save](#saveasync) asynchronously on DynamoDb.
9698

9799
### Supported features:
98100

@@ -101,6 +103,7 @@ Usage
101103
```php
102104
$model->find(<id>);
103105
$model->delete();
106+
$model->deleteAsync()->wait();
104107
```
105108

106109
#### Conditions
@@ -198,6 +201,13 @@ $nextPage = $query->afterKey($last->getKeys())->limit(2)->all();
198201
$model->update($attributes);
199202
```
200203
204+
#### updateasync()
205+
206+
```php
207+
// update asynchronously and wait on the promise for completion.
208+
$model->updateAsync($attributes)->wait();
209+
```
210+
201211
#### save()
202212
203213
```php
@@ -206,10 +216,50 @@ $model = new Model();
206216
$model->fillableAttr1 = 'foo';
207217
$model->fillableAttr2 = 'foo';
208218
// DynamoDb doesn't support incremented Id, so you need to use UUID for the primary key.
209-
$model->id = 'de305d54-75b4-431b-adb2-eb6b9e546014'
219+
$model->id = 'de305d54-75b4-431b-adb2-eb6b9e546014';
210220
$model->save();
211221
```
212222
223+
#### saveasync()
224+
**Examples**
225+
Saving single model asynchronously and waiting on the promise for completion.
226+
```php
227+
$model = new Model();
228+
// Define fillable attributes in your Model class.
229+
$model->fillableAttr1 = 'foo';
230+
$model->fillableAttr2 = 'bar';
231+
// DynamoDb doesn't support incremented Id, so you need to use UUID for the primary key.
232+
$model->id = 'de305d54-75b4-431b-adb2-eb6b9e546014';
233+
$model->saveAsync()->wait();
234+
```
235+
Saving multiple models asynchronously and waiting on all of them simultaneously.
236+
```php
237+
for($i = 0; $i < 10; $i++){
238+
$model = new Model();
239+
// Define fillable attributes in your Model class.
240+
$model->fillableAttr1 = 'foo';
241+
$model->fillableAttr2 = 'bar';
242+
// DynamoDb doesn't support incremented Id, so you need to use UUID for the primary key.
243+
$model->id = uniqid();
244+
// Returns a promise which you can wait on later.
245+
$promises[] = $model->saveAsync();
246+
}
247+
248+
\GuzzleHttp\Promise\all($promises)->wait();
249+
```
250+
251+
#### delete()
252+
253+
```php
254+
$model->delete();
255+
```
256+
257+
#### deleteasync()
258+
259+
```php
260+
$model->deleteAsync()->wait();
261+
```
262+
213263
#### chunk()
214264
215265
```php

src/DynamoDbModel.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,56 @@ public function save(array $options = [])
155155
return $saved;
156156
}
157157

158+
/**
159+
* Saves the model to DynamoDb asynchronously and returns a promise
160+
* @param array $options
161+
* @return bool|\GuzzleHttp\Promise\Promise
162+
*/
163+
public function saveAsync(array $options = [])
164+
{
165+
$create = !$this->exists;
166+
167+
if ($this->fireModelEvent('saving') === false) {
168+
return false;
169+
}
170+
171+
if ($create && $this->fireModelEvent('creating') === false) {
172+
return false;
173+
}
174+
175+
if (!$create && $this->fireModelEvent('updating') === false) {
176+
return false;
177+
}
178+
179+
if ($this->usesTimestamps()) {
180+
$this->updateTimestamps();
181+
}
182+
183+
$savePromise = $this->newQuery()->saveAsync();
184+
185+
$savePromise->then(function ($result) use ($create, $options) {
186+
if (array_get($result, '@metadata.statusCode') === 200) {
187+
$this->exists = true;
188+
$this->wasRecentlyCreated = $create;
189+
$this->fireModelEvent($create ? 'created' : 'updated', false);
190+
191+
$this->finishSave($options);
192+
}
193+
});
194+
195+
return $savePromise;
196+
}
197+
158198
public function update(array $attributes = [], array $options = [])
159199
{
160200
return $this->fill($attributes)->save();
161201
}
162202

203+
public function updateAsync(array $attributes = [], array $options = [])
204+
{
205+
return $this->fill($attributes)->saveAsync();
206+
}
207+
163208
public static function create(array $attributes = [])
164209
{
165210
$model = new static;
@@ -192,6 +237,29 @@ public function delete()
192237
}
193238
}
194239

240+
public function deleteAsync()
241+
{
242+
if (is_null($this->getKeyName())) {
243+
throw new Exception('No primary key defined on model.');
244+
}
245+
246+
if ($this->exists) {
247+
if ($this->fireModelEvent('deleting') === false) {
248+
return false;
249+
}
250+
251+
$this->exists = false;
252+
253+
$deletePromise = $this->newQuery()->deleteAsync();
254+
255+
$deletePromise->then(function () {
256+
$this->fireModelEvent('deleted', false);
257+
});
258+
259+
return $deletePromise;
260+
}
261+
}
262+
195263
public static function all($columns = [])
196264
{
197265
$instance = new static;

src/DynamoDbQueryBuilder.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,16 @@ public function delete()
605605
return array_get($result->toArray(), '@metadata.statusCode') === 200;
606606
}
607607

608+
public function deleteAsync()
609+
{
610+
$promise = DynamoDb::table($this->model->getTable())
611+
->setKey(DynamoDb::marshalItem($this->model->getKeys()))
612+
->prepare($this->client)
613+
->deleteItemAsync();
614+
615+
return $promise;
616+
}
617+
608618
public function save()
609619
{
610620
$result = DynamoDb::table($this->model->getTable())
@@ -615,6 +625,16 @@ public function save()
615625
return array_get($result, '@metadata.statusCode') === 200;
616626
}
617627

628+
public function saveAsync()
629+
{
630+
$promise = DynamoDb::table($this->model->getTable())
631+
->setItem(DynamoDb::marshalItem($this->model->getAttributes()))
632+
->prepare($this->client)
633+
->putItemAsync();
634+
635+
return $promise;
636+
}
637+
618638
public function get($columns = [])
619639
{
620640
return $this->all($columns);

tests/DynamoDbCompositeModelTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,29 @@ public function testCreateRecord()
4242
$this->assertEquals($this->testModel->id2, $record['Item']['id2']['S']);
4343
}
4444

45+
public function testCreateAsyncRecord()
46+
{
47+
$this->testModel->id = 'id1';
48+
$this->testModel->id2 = str_random(36);
49+
$this->testModel->name = 'Test Create Async';
50+
$this->testModel->count = 1;
51+
$this->testModel->saveAsync()->wait();
52+
53+
$query = [
54+
'TableName' => $this->testModel->getTable(),
55+
'Key' => [
56+
'id' => ['S' => $this->testModel->id],
57+
'id2' => ['S' => $this->testModel->id2],
58+
],
59+
];
60+
61+
$record = $this->getClient()->getItem($query)->toArray();
62+
63+
$this->assertArrayHasKey('Item', $record);
64+
$this->assertEquals($this->testModel->id, $record['Item']['id']['S']);
65+
$this->assertEquals($this->testModel->id2, $record['Item']['id2']['S']);
66+
}
67+
4568
public function testFindRecord()
4669
{
4770
$seed = $this->seed();
@@ -179,6 +202,29 @@ public function testUpdateRecord()
179202
$this->assertEquals($newName, array_get($record, 'Item.name.S'));
180203
}
181204

205+
public function testUpdateAsyncRecord()
206+
{
207+
$seed = $this->seed();
208+
$seedId = array_get($seed, 'id.S');
209+
$seedId2 = array_get($seed, 'id2.S');
210+
211+
$newName = 'New Name';
212+
$this->testModel = $this->testModel->find(['id' => $seedId, 'id2' => $seedId2]);
213+
$this->testModel->updateAsync(['name' => $newName])->wait();
214+
215+
$query = [
216+
'TableName' => $this->testModel->getTable(),
217+
'Key' => [
218+
'id' => ['S' => $seedId],
219+
'id2' => ['S' => $seedId2],
220+
],
221+
];
222+
223+
$record = $this->getClient()->getItem($query)->toArray();
224+
225+
$this->assertEquals($newName, array_get($record, 'Item.name.S'));
226+
}
227+
182228
public function testSaveRecord()
183229
{
184230
$seed = $this->seed();
@@ -204,6 +250,31 @@ public function testSaveRecord()
204250
$this->assertEquals($newName, array_get($record, 'Item.name.S'));
205251
}
206252

253+
public function testSaveAsyncRecord()
254+
{
255+
$seed = $this->seed();
256+
$seedId = array_get($seed, 'id.S');
257+
$seedId2 = array_get($seed, 'id2.S');
258+
259+
$newName = 'New Name to be saved asynchronously';
260+
$this->testModel = $this->testModel->find(['id' => $seedId, 'id2' => $seedId2]);
261+
$this->testModel->name = $newName;
262+
263+
$this->testModel->saveAsync()->wait();
264+
265+
$query = [
266+
'TableName' => $this->testModel->getTable(),
267+
'Key' => [
268+
'id' => ['S' => $seedId],
269+
'id2' => ['S' => $seedId2]
270+
],
271+
];
272+
273+
$record = $this->getClient()->getItem($query)->toArray();
274+
275+
$this->assertEquals($newName, array_get($record, 'Item.name.S'));
276+
}
277+
207278
public function testDeleteRecord()
208279
{
209280
$seed = $this->seed();
@@ -225,6 +296,27 @@ public function testDeleteRecord()
225296
$this->assertArrayNotHasKey('Item', $record);
226297
}
227298

299+
public function testDeleteAsyncRecord()
300+
{
301+
$seed = $this->seed();
302+
$seedId = array_get($seed, 'id.S');
303+
$seedId2 = array_get($seed, 'id2.S');
304+
305+
$this->testModel->find(['id' => $seedId, 'id2' => $seedId2])->deleteAsync()->wait();
306+
307+
$query = [
308+
'TableName' => $this->testModel->getTable(),
309+
'Key' => [
310+
'id' => ['S' => $seedId],
311+
'id2' => ['S' => $seedId2]
312+
],
313+
];
314+
315+
$record = $this->getClient()->getItem($query)->toArray();
316+
317+
$this->assertArrayNotHasKey('Item', $record);
318+
}
319+
228320
public function testLookUpByKey()
229321
{
230322
$this->seed();

0 commit comments

Comments
 (0)