1 | <?php |
||
2 | |||
3 | namespace BaoPham\DynamoDb; |
||
4 | |||
5 | use Exception; |
||
6 | use DateTime; |
||
7 | use Illuminate\Database\Eloquent\Model; |
||
8 | use Illuminate\Support\Arr; |
||
9 | |||
10 | /** |
||
11 | * Class DynamoDbModel. |
||
12 | */ |
||
13 | abstract class DynamoDbModel extends Model |
||
14 | { |
||
15 | /** |
||
16 | * Always set this to false since DynamoDb does not support incremental Id. |
||
17 | * |
||
18 | * @var bool |
||
19 | */ |
||
20 | public $incrementing = false; |
||
21 | |||
22 | /** |
||
23 | * @var \BaoPham\DynamoDb\DynamoDbClientInterface |
||
24 | */ |
||
25 | protected static $dynamoDb; |
||
26 | |||
27 | /** |
||
28 | * @deprecated |
||
29 | * @var \Aws\DynamoDb\Marshaler |
||
30 | */ |
||
31 | protected $marshaler; |
||
32 | |||
33 | /** |
||
34 | * @deprecated |
||
35 | * @var \BaoPham\DynamoDb\EmptyAttributeFilter |
||
36 | */ |
||
37 | protected $attributeFilter; |
||
38 | |||
39 | /** |
||
40 | * Indexes. |
||
41 | * [ |
||
42 | * '<simple_index_name>' => [ |
||
43 | * 'hash' => '<index_key>' |
||
44 | * ], |
||
45 | * '<composite_index_name>' => [ |
||
46 | * 'hash' => '<index_hash_key>', |
||
47 | * 'range' => '<index_range_key>' |
||
48 | * ], |
||
49 | * ] |
||
50 | * |
||
51 | * @var array |
||
52 | */ |
||
53 | protected $dynamoDbIndexKeys = []; |
||
54 | |||
55 | /** |
||
56 | * Array of your composite key. |
||
57 | * ['<hash>', '<range>'] |
||
58 | * |
||
59 | * @var array |
||
60 | */ |
||
61 | protected $compositeKey = []; |
||
62 | |||
63 | /** |
||
64 | * Default Date format |
||
65 | * ISO 8601 Compliant |
||
66 | */ |
||
67 | protected $dateFormat = DateTime::ATOM; |
||
68 | |||
69 | |||
70 | 131 | public function __construct(array $attributes = []) |
|
71 | { |
||
72 | 131 | $this->bootIfNotBooted(); |
|
73 | |||
74 | 131 | $this->syncOriginal(); |
|
75 | |||
76 | 131 | $this->fill($attributes); |
|
77 | |||
78 | 131 | $this->setupDynamoDb(); |
|
79 | 131 | } |
|
80 | |||
81 | /** |
||
82 | * Get the DynamoDbClient service that is being used by the models. |
||
83 | * |
||
84 | * @return DynamoDbClientInterface |
||
85 | */ |
||
86 | 3 | public static function getDynamoDbClientService() |
|
87 | { |
||
88 | 3 | return static::$dynamoDb; |
|
89 | } |
||
90 | |||
91 | /** |
||
92 | * Set the DynamoDbClient used by models. |
||
93 | * |
||
94 | * @param DynamoDbClientInterface $dynamoDb |
||
95 | * |
||
96 | * @return void |
||
97 | */ |
||
98 | 146 | public static function setDynamoDbClientService(DynamoDbClientInterface $dynamoDb) |
|
99 | { |
||
100 | 146 | static::$dynamoDb = $dynamoDb; |
|
101 | 146 | } |
|
102 | |||
103 | /** |
||
104 | * Unset the DynamoDbClient service for models. |
||
105 | * |
||
106 | * @return void |
||
107 | */ |
||
108 | 3 | public static function unsetDynamoDbClientService() |
|
109 | { |
||
110 | 3 | static::$dynamoDb = null; |
|
111 | 3 | } |
|
112 | |||
113 | 131 | protected function setupDynamoDb() |
|
114 | { |
||
115 | 131 | $this->marshaler = static::$dynamoDb->getMarshaler(); |
|
116 | 131 | $this->attributeFilter = static::$dynamoDb->getAttributeFilter(); |
|
117 | 131 | } |
|
118 | |||
119 | 92 | public function newCollection(array $models = [], $index = null) |
|
120 | { |
||
121 | 92 | return new DynamoDbCollection($models, $index); |
|
122 | } |
||
123 | |||
124 | 8 | public function save(array $options = []) |
|
125 | { |
||
126 | 8 | $create = !$this->exists; |
|
127 | |||
128 | 8 | if ($this->fireModelEvent('saving') === false) { |
|
129 | return false; |
||
130 | } |
||
131 | |||
132 | 8 | if ($create && $this->fireModelEvent('creating') === false) { |
|
133 | return false; |
||
134 | } |
||
135 | |||
136 | 8 | if (!$create && $this->fireModelEvent('updating') === false) { |
|
137 | return false; |
||
138 | } |
||
139 | |||
140 | 8 | if ($this->usesTimestamps()) { |
|
141 | 8 | $this->updateTimestamps(); |
|
142 | } |
||
143 | |||
144 | 8 | $saved = $this->newQuery()->save(); |
|
145 | |||
146 | 8 | if (!$saved) { |
|
147 | return $saved; |
||
148 | } |
||
149 | |||
150 | 8 | $this->exists = true; |
|
151 | 8 | $this->wasRecentlyCreated = $create; |
|
152 | 8 | $this->fireModelEvent($create ? 'created' : 'updated', false); |
|
153 | |||
154 | 8 | $this->finishSave($options); |
|
155 | |||
156 | 8 | return $saved; |
|
157 | } |
||
158 | |||
159 | /** |
||
160 | * Saves the model to DynamoDb asynchronously and returns a promise |
||
161 | * @param array $options |
||
162 | * @return bool|\GuzzleHttp\Promise\Promise |
||
163 | */ |
||
164 | 6 | public function saveAsync(array $options = []) |
|
165 | { |
||
166 | 6 | $create = !$this->exists; |
|
167 | |||
168 | 6 | if ($this->fireModelEvent('saving') === false) { |
|
169 | return false; |
||
170 | } |
||
171 | |||
172 | 6 | if ($create && $this->fireModelEvent('creating') === false) { |
|
173 | return false; |
||
174 | } |
||
175 | |||
176 | 6 | if (!$create && $this->fireModelEvent('updating') === false) { |
|
177 | return false; |
||
178 | } |
||
179 | |||
180 | 6 | if ($this->usesTimestamps()) { |
|
181 | 6 | $this->updateTimestamps(); |
|
182 | } |
||
183 | |||
184 | 6 | $savePromise = $this->newQuery()->saveAsync(); |
|
185 | |||
186 | $savePromise->then(function ($result) use ($create, $options) { |
||
187 | 6 | if (Arr::get($result, '@metadata.statusCode') === 200) { |
|
188 | 6 | $this->exists = true; |
|
189 | 6 | $this->wasRecentlyCreated = $create; |
|
190 | 6 | $this->fireModelEvent($create ? 'created' : 'updated', false); |
|
191 | |||
192 | 6 | $this->finishSave($options); |
|
193 | } |
||
194 | 6 | }); |
|
195 | |||
196 | 6 | return $savePromise; |
|
197 | } |
||
198 | |||
199 | 3 | public function update(array $attributes = [], array $options = []) |
|
200 | { |
||
201 | 3 | return $this->fill($attributes)->save(); |
|
202 | } |
||
203 | |||
204 | 2 | public function updateAsync(array $attributes = [], array $options = []) |
|
205 | { |
||
206 | 2 | return $this->fill($attributes)->saveAsync($options); |
|
207 | } |
||
208 | |||
209 | public static function create(array $attributes = []) |
||
210 | { |
||
211 | $model = new static; |
||
212 | |||
213 | $model->fill($attributes)->save(); |
||
214 | |||
215 | return $model; |
||
216 | } |
||
217 | |||
218 | 2 | public function delete() |
|
219 | { |
||
220 | 2 | if (is_null($this->getKeyName())) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
221 | throw new Exception('No primary key defined on model.'); |
||
222 | } |
||
223 | |||
224 | 2 | if ($this->exists) { |
|
225 | 2 | if ($this->fireModelEvent('deleting') === false) { |
|
226 | return false; |
||
227 | } |
||
228 | |||
229 | 2 | $this->exists = false; |
|
230 | |||
231 | 2 | $success = $this->newQuery()->delete(); |
|
232 | |||
233 | 2 | if ($success) { |
|
234 | 2 | $this->fireModelEvent('deleted', false); |
|
235 | } |
||
236 | |||
237 | 2 | return $success; |
|
238 | } |
||
239 | } |
||
240 | |||
241 | 2 | public function deleteAsync() |
|
242 | { |
||
243 | 2 | if (is_null($this->getKeyName())) { |
|
244 | throw new Exception('No primary key defined on model.'); |
||
245 | } |
||
246 | |||
247 | 2 | if ($this->exists) { |
|
248 | 2 | if ($this->fireModelEvent('deleting') === false) { |
|
249 | return false; |
||
250 | } |
||
251 | |||
252 | 2 | $this->exists = false; |
|
253 | |||
254 | 2 | $deletePromise = $this->newQuery()->deleteAsync(); |
|
255 | |||
256 | $deletePromise->then(function () { |
||
257 | 2 | $this->fireModelEvent('deleted', false); |
|
258 | 2 | }); |
|
259 | |||
260 | 2 | return $deletePromise; |
|
261 | } |
||
262 | } |
||
263 | |||
264 | 7 | public static function all($columns = []) |
|
265 | { |
||
266 | 7 | $instance = new static; |
|
267 | |||
268 | 7 | return $instance->newQuery()->get($columns); |
|
269 | } |
||
270 | |||
271 | 2 | public function refresh() |
|
272 | { |
||
273 | 2 | if (! $this->exists) { |
|
274 | return $this; |
||
275 | } |
||
276 | |||
277 | 2 | $query = $this->newQuery(); |
|
278 | |||
279 | 2 | $refreshed = $query->find($this->getKeys()); |
|
280 | |||
281 | 2 | $this->setRawAttributes($refreshed->toArray()); |
|
282 | |||
283 | 2 | return $this; |
|
284 | } |
||
285 | |||
286 | /** |
||
287 | * @return DynamoDbQueryBuilder |
||
288 | */ |
||
289 | 131 | public function newQuery() |
|
290 | { |
||
291 | 131 | $builder = new DynamoDbQueryBuilder($this); |
|
292 | |||
293 | 131 | foreach ($this->getGlobalScopes() as $identifier => $scope) { |
|
294 | 7 | $builder->withGlobalScope($identifier, $scope); |
|
295 | } |
||
296 | |||
297 | 131 | return $builder; |
|
298 | } |
||
299 | |||
300 | 110 | public function hasCompositeKey() |
|
301 | { |
||
302 | 110 | return !empty($this->compositeKey); |
|
303 | } |
||
304 | |||
305 | /** |
||
306 | * @deprecated |
||
307 | * @param $item |
||
308 | * @return array |
||
309 | */ |
||
310 | public function marshalItem($item) |
||
311 | { |
||
312 | return $this->marshaler->marshalItem($item); |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * @deprecated |
||
317 | * @param $value |
||
318 | * @return array |
||
319 | */ |
||
320 | public function marshalValue($value) |
||
321 | { |
||
322 | return $this->marshaler->marshalValue($value); |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * @deprecated |
||
327 | * @param $item |
||
328 | * @return array|\stdClass |
||
329 | */ |
||
330 | public function unmarshalItem($item) |
||
331 | { |
||
332 | return $this->marshaler->unmarshalItem($item); |
||
333 | } |
||
334 | |||
335 | 36 | public function setId($id) |
|
336 | { |
||
337 | 36 | if (!is_array($id)) { |
|
338 | 14 | $this->setAttribute($this->getKeyName(), $id); |
|
339 | |||
340 | 14 | return $this; |
|
341 | } |
||
342 | |||
343 | 24 | foreach ($id as $keyName => $value) { |
|
344 | 24 | $this->setAttribute($keyName, $value); |
|
345 | } |
||
346 | |||
347 | 24 | return $this; |
|
348 | } |
||
349 | |||
350 | /** |
||
351 | * @return \Aws\DynamoDb\DynamoDbClient |
||
352 | */ |
||
353 | 131 | public function getClient() |
|
354 | { |
||
355 | 131 | return static::$dynamoDb->getClient($this->getConnectionName()); |
|
356 | } |
||
357 | |||
358 | /** |
||
359 | * Get the value of the model's primary key. |
||
360 | * |
||
361 | * @return mixed |
||
362 | */ |
||
363 | public function getKey() |
||
364 | { |
||
365 | return $this->getAttribute($this->getKeyName()); |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Get the value of the model's primary / composite key. |
||
370 | * Use this if you always want the key values in associative array form. |
||
371 | * |
||
372 | * @return array |
||
373 | * |
||
374 | * ['id' => 'foo'] |
||
375 | * |
||
376 | * or |
||
377 | * |
||
378 | * ['id' => 'foo', 'id2' => 'bar'] |
||
379 | */ |
||
380 | 46 | public function getKeys() |
|
381 | { |
||
382 | 46 | if ($this->hasCompositeKey()) { |
|
383 | 22 | $key = []; |
|
384 | |||
385 | 22 | foreach ($this->compositeKey as $name) { |
|
386 | 22 | $key[$name] = $this->getAttribute($name); |
|
387 | } |
||
388 | |||
389 | 22 | return $key; |
|
390 | } |
||
391 | |||
392 | 24 | $name = $this->getKeyName(); |
|
393 | |||
394 | 24 | return [$name => $this->getAttribute($name)]; |
|
395 | } |
||
396 | |||
397 | /** |
||
398 | * Get the primary key for the model. |
||
399 | * |
||
400 | * @return string |
||
401 | */ |
||
402 | 28 | public function getKeyName() |
|
403 | { |
||
404 | 28 | return $this->primaryKey; |
|
405 | } |
||
406 | |||
407 | /** |
||
408 | * Get the primary/composite key for the model. |
||
409 | * |
||
410 | * @return array |
||
411 | */ |
||
412 | 104 | public function getKeyNames() |
|
413 | { |
||
414 | 104 | return $this->hasCompositeKey() ? $this->compositeKey : [$this->primaryKey]; |
|
415 | } |
||
416 | |||
417 | /** |
||
418 | * @return array |
||
419 | */ |
||
420 | 70 | public function getDynamoDbIndexKeys() |
|
421 | { |
||
422 | 70 | return $this->dynamoDbIndexKeys; |
|
423 | } |
||
424 | |||
425 | /** |
||
426 | * @param array $dynamoDbIndexKeys |
||
427 | */ |
||
428 | public function setDynamoDbIndexKeys($dynamoDbIndexKeys) |
||
429 | { |
||
430 | $this->dynamoDbIndexKeys = $dynamoDbIndexKeys; |
||
431 | } |
||
432 | |||
433 | /** |
||
434 | * @deprecated |
||
435 | * @return \Aws\DynamoDb\Marshaler |
||
436 | */ |
||
437 | public function getMarshaler() |
||
438 | { |
||
439 | return $this->marshaler; |
||
440 | } |
||
441 | |||
442 | /** |
||
443 | * Remove non-serializable properties when serializing. |
||
444 | * |
||
445 | * @return array |
||
446 | */ |
||
447 | 2 | public function __sleep() |
|
448 | { |
||
449 | 2 | return array_keys( |
|
450 | 2 | Arr::except(get_object_vars($this), ['marshaler', 'attributeFilter']) |
|
451 | ); |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * When a model is being unserialized, check if it needs to be booted and setup DynamoDB. |
||
456 | * |
||
457 | * @return void |
||
458 | */ |
||
459 | 2 | public function __wakeup() |
|
460 | { |
||
461 | 2 | parent::__wakeup(); |
|
462 | 2 | $this->setupDynamoDb(); |
|
463 | 2 | } |
|
464 | } |
||
465 |