Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Cache;
4
 
5
use Aws\DynamoDb\DynamoDbClient;
6
use Aws\DynamoDb\Exception\DynamoDbException;
7
use Illuminate\Contracts\Cache\LockProvider;
8
use Illuminate\Contracts\Cache\Store;
9
use Illuminate\Support\Carbon;
10
use Illuminate\Support\InteractsWithTime;
11
use Illuminate\Support\Str;
12
use RuntimeException;
13
 
14
class DynamoDbStore implements LockProvider, Store
15
{
16
    use InteractsWithTime;
17
 
18
    /**
19
     * The DynamoDB client instance.
20
     *
21
     * @var \Aws\DynamoDb\DynamoDbClient
22
     */
23
    protected $dynamo;
24
 
25
    /**
26
     * The table name.
27
     *
28
     * @var string
29
     */
30
    protected $table;
31
 
32
    /**
33
     * The name of the attribute that should hold the key.
34
     *
35
     * @var string
36
     */
37
    protected $keyAttribute;
38
 
39
    /**
40
     * The name of the attribute that should hold the value.
41
     *
42
     * @var string
43
     */
44
    protected $valueAttribute;
45
 
46
    /**
47
     * The name of the attribute that should hold the expiration timestamp.
48
     *
49
     * @var string
50
     */
51
    protected $expirationAttribute;
52
 
53
    /**
54
     * A string that should be prepended to keys.
55
     *
56
     * @var string
57
     */
58
    protected $prefix;
59
 
60
    /**
61
     * Create a new store instance.
62
     *
63
     * @param  \Aws\DynamoDb\DynamoDbClient  $dynamo
64
     * @param  string  $table
65
     * @param  string  $keyAttribute
66
     * @param  string  $valueAttribute
67
     * @param  string  $expirationAttribute
68
     * @param  string  $prefix
69
     * @return void
70
     */
71
    public function __construct(DynamoDbClient $dynamo,
72
                                $table,
73
                                $keyAttribute = 'key',
74
                                $valueAttribute = 'value',
75
                                $expirationAttribute = 'expires_at',
76
                                $prefix = '')
77
    {
78
        $this->table = $table;
79
        $this->dynamo = $dynamo;
80
        $this->keyAttribute = $keyAttribute;
81
        $this->valueAttribute = $valueAttribute;
82
        $this->expirationAttribute = $expirationAttribute;
83
 
84
        $this->setPrefix($prefix);
85
    }
86
 
87
    /**
88
     * Retrieve an item from the cache by key.
89
     *
90
     * @param  string  $key
91
     * @return mixed
92
     */
93
    public function get($key)
94
    {
95
        $response = $this->dynamo->getItem([
96
            'TableName' => $this->table,
97
            'ConsistentRead' => false,
98
            'Key' => [
99
                $this->keyAttribute => [
100
                    'S' => $this->prefix.$key,
101
                ],
102
            ],
103
        ]);
104
 
105
        if (! isset($response['Item'])) {
106
            return;
107
        }
108
 
109
        if ($this->isExpired($response['Item'])) {
110
            return;
111
        }
112
 
113
        if (isset($response['Item'][$this->valueAttribute])) {
114
            return $this->unserialize(
115
                $response['Item'][$this->valueAttribute]['S'] ??
116
                $response['Item'][$this->valueAttribute]['N'] ??
117
                null
118
            );
119
        }
120
    }
121
 
122
    /**
123
     * Retrieve multiple items from the cache by key.
124
     *
125
     * Items not found in the cache will have a null value.
126
     *
127
     * @param  array  $keys
128
     * @return array
129
     */
130
    public function many(array $keys)
131
    {
132
        $prefixedKeys = array_map(function ($key) {
133
            return $this->prefix.$key;
134
        }, $keys);
135
 
136
        $response = $this->dynamo->batchGetItem([
137
            'RequestItems' => [
138
                $this->table => [
139
                    'ConsistentRead' => false,
140
                    'Keys' => collect($prefixedKeys)->map(function ($key) {
141
                        return [
142
                            $this->keyAttribute => [
143
                                'S' => $key,
144
                            ],
145
                        ];
146
                    })->all(),
147
                ],
148
            ],
149
        ]);
150
 
151
        $now = Carbon::now();
152
 
153
        return array_merge(collect(array_flip($keys))->map(function () {
154
            //
155
        })->all(), collect($response['Responses'][$this->table])->mapWithKeys(function ($response) use ($now) {
156
            if ($this->isExpired($response, $now)) {
157
                $value = null;
158
            } else {
159
                $value = $this->unserialize(
160
                    $response[$this->valueAttribute]['S'] ??
161
                    $response[$this->valueAttribute]['N'] ??
162
                    null
163
                );
164
            }
165
 
166
            return [Str::replaceFirst($this->prefix, '', $response[$this->keyAttribute]['S']) => $value];
167
        })->all());
168
    }
169
 
170
    /**
171
     * Determine if the given item is expired.
172
     *
173
     * @param  array  $item
174
     * @param  \DateTimeInterface|null  $expiration
175
     * @return bool
176
     */
177
    protected function isExpired(array $item, $expiration = null)
178
    {
179
        $expiration = $expiration ?: Carbon::now();
180
 
181
        return isset($item[$this->expirationAttribute]) &&
182
               $expiration->getTimestamp() >= $item[$this->expirationAttribute]['N'];
183
    }
184
 
185
    /**
186
     * Store an item in the cache for a given number of seconds.
187
     *
188
     * @param  string  $key
189
     * @param  mixed  $value
190
     * @param  int  $seconds
191
     * @return bool
192
     */
193
    public function put($key, $value, $seconds)
194
    {
195
        $this->dynamo->putItem([
196
            'TableName' => $this->table,
197
            'Item' => [
198
                $this->keyAttribute => [
199
                    'S' => $this->prefix.$key,
200
                ],
201
                $this->valueAttribute => [
202
                    $this->type($value) => $this->serialize($value),
203
                ],
204
                $this->expirationAttribute => [
205
                    'N' => (string) $this->toTimestamp($seconds),
206
                ],
207
            ],
208
        ]);
209
 
210
        return true;
211
    }
212
 
213
    /**
214
     * Store multiple items in the cache for a given number of seconds.
215
     *
216
     * @param  array  $values
217
     * @param  int  $seconds
218
     * @return bool
219
     */
220
    public function putMany(array $values, $seconds)
221
    {
222
        $expiration = $this->toTimestamp($seconds);
223
 
224
        $this->dynamo->batchWriteItem([
225
            'RequestItems' => [
226
                $this->table => collect($values)->map(function ($value, $key) use ($expiration) {
227
                    return [
228
                        'PutRequest' => [
229
                            'Item' => [
230
                                $this->keyAttribute => [
231
                                    'S' => $this->prefix.$key,
232
                                ],
233
                                $this->valueAttribute => [
234
                                    $this->type($value) => $this->serialize($value),
235
                                ],
236
                                $this->expirationAttribute => [
237
                                    'N' => (string) $expiration,
238
                                ],
239
                            ],
240
                        ],
241
                    ];
242
                })->values()->all(),
243
            ],
244
        ]);
245
 
246
        return true;
247
    }
248
 
249
    /**
250
     * Store an item in the cache if the key doesn't exist.
251
     *
252
     * @param  string  $key
253
     * @param  mixed  $value
254
     * @param  int  $seconds
255
     * @return bool
256
     */
257
    public function add($key, $value, $seconds)
258
    {
259
        try {
260
            $this->dynamo->putItem([
261
                'TableName' => $this->table,
262
                'Item' => [
263
                    $this->keyAttribute => [
264
                        'S' => $this->prefix.$key,
265
                    ],
266
                    $this->valueAttribute => [
267
                        $this->type($value) => $this->serialize($value),
268
                    ],
269
                    $this->expirationAttribute => [
270
                        'N' => (string) $this->toTimestamp($seconds),
271
                    ],
272
                ],
273
                'ConditionExpression' => 'attribute_not_exists(#key) OR #expires_at < :now',
274
                'ExpressionAttributeNames' => [
275
                    '#key' => $this->keyAttribute,
276
                    '#expires_at' => $this->expirationAttribute,
277
                ],
278
                'ExpressionAttributeValues' => [
279
                    ':now' => [
280
                        'N' => (string) Carbon::now()->getTimestamp(),
281
                    ],
282
                ],
283
            ]);
284
 
285
            return true;
286
        } catch (DynamoDbException $e) {
287
            if (str_contains($e->getMessage(), 'ConditionalCheckFailed')) {
288
                return false;
289
            }
290
 
291
            throw $e;
292
        }
293
    }
294
 
295
    /**
296
     * Increment the value of an item in the cache.
297
     *
298
     * @param  string  $key
299
     * @param  mixed  $value
300
     * @return int|bool
301
     */
302
    public function increment($key, $value = 1)
303
    {
304
        try {
305
            $response = $this->dynamo->updateItem([
306
                'TableName' => $this->table,
307
                'Key' => [
308
                    $this->keyAttribute => [
309
                        'S' => $this->prefix.$key,
310
                    ],
311
                ],
312
                'ConditionExpression' => 'attribute_exists(#key) AND #expires_at > :now',
313
                'UpdateExpression' => 'SET #value = #value + :amount',
314
                'ExpressionAttributeNames' => [
315
                    '#key' => $this->keyAttribute,
316
                    '#value' => $this->valueAttribute,
317
                    '#expires_at' => $this->expirationAttribute,
318
                ],
319
                'ExpressionAttributeValues' => [
320
                    ':now' => [
321
                        'N' => (string) Carbon::now()->getTimestamp(),
322
                    ],
323
                    ':amount' => [
324
                        'N' => (string) $value,
325
                    ],
326
                ],
327
                'ReturnValues' => 'UPDATED_NEW',
328
            ]);
329
 
330
            return (int) $response['Attributes'][$this->valueAttribute]['N'];
331
        } catch (DynamoDbException $e) {
332
            if (str_contains($e->getMessage(), 'ConditionalCheckFailed')) {
333
                return false;
334
            }
335
 
336
            throw $e;
337
        }
338
    }
339
 
340
    /**
341
     * Decrement the value of an item in the cache.
342
     *
343
     * @param  string  $key
344
     * @param  mixed  $value
345
     * @return int|bool
346
     */
347
    public function decrement($key, $value = 1)
348
    {
349
        try {
350
            $response = $this->dynamo->updateItem([
351
                'TableName' => $this->table,
352
                'Key' => [
353
                    $this->keyAttribute => [
354
                        'S' => $this->prefix.$key,
355
                    ],
356
                ],
357
                'ConditionExpression' => 'attribute_exists(#key) AND #expires_at > :now',
358
                'UpdateExpression' => 'SET #value = #value - :amount',
359
                'ExpressionAttributeNames' => [
360
                    '#key' => $this->keyAttribute,
361
                    '#value' => $this->valueAttribute,
362
                    '#expires_at' => $this->expirationAttribute,
363
                ],
364
                'ExpressionAttributeValues' => [
365
                    ':now' => [
366
                        'N' => (string) Carbon::now()->getTimestamp(),
367
                    ],
368
                    ':amount' => [
369
                        'N' => (string) $value,
370
                    ],
371
                ],
372
                'ReturnValues' => 'UPDATED_NEW',
373
            ]);
374
 
375
            return (int) $response['Attributes'][$this->valueAttribute]['N'];
376
        } catch (DynamoDbException $e) {
377
            if (str_contains($e->getMessage(), 'ConditionalCheckFailed')) {
378
                return false;
379
            }
380
 
381
            throw $e;
382
        }
383
    }
384
 
385
    /**
386
     * Store an item in the cache indefinitely.
387
     *
388
     * @param  string  $key
389
     * @param  mixed  $value
390
     * @return bool
391
     */
392
    public function forever($key, $value)
393
    {
394
        return $this->put($key, $value, Carbon::now()->addYears(5)->getTimestamp());
395
    }
396
 
397
    /**
398
     * Get a lock instance.
399
     *
400
     * @param  string  $name
401
     * @param  int  $seconds
402
     * @param  string|null  $owner
403
     * @return \Illuminate\Contracts\Cache\Lock
404
     */
405
    public function lock($name, $seconds = 0, $owner = null)
406
    {
407
        return new DynamoDbLock($this, $this->prefix.$name, $seconds, $owner);
408
    }
409
 
410
    /**
411
     * Restore a lock instance using the owner identifier.
412
     *
413
     * @param  string  $name
414
     * @param  string  $owner
415
     * @return \Illuminate\Contracts\Cache\Lock
416
     */
417
    public function restoreLock($name, $owner)
418
    {
419
        return $this->lock($name, 0, $owner);
420
    }
421
 
422
    /**
423
     * Remove an item from the cache.
424
     *
425
     * @param  string  $key
426
     * @return bool
427
     */
428
    public function forget($key)
429
    {
430
        $this->dynamo->deleteItem([
431
            'TableName' => $this->table,
432
            'Key' => [
433
                $this->keyAttribute => [
434
                    'S' => $this->prefix.$key,
435
                ],
436
            ],
437
        ]);
438
 
439
        return true;
440
    }
441
 
442
    /**
443
     * Remove all items from the cache.
444
     *
445
     * @return bool
446
     *
447
     * @throws \RuntimeException
448
     */
449
    public function flush()
450
    {
451
        throw new RuntimeException('DynamoDb does not support flushing an entire table. Please create a new table.');
452
    }
453
 
454
    /**
455
     * Get the UNIX timestamp for the given number of seconds.
456
     *
457
     * @param  int  $seconds
458
     * @return int
459
     */
460
    protected function toTimestamp($seconds)
461
    {
462
        return $seconds > 0
463
                    ? $this->availableAt($seconds)
464
                    : Carbon::now()->getTimestamp();
465
    }
466
 
467
    /**
468
     * Serialize the value.
469
     *
470
     * @param  mixed  $value
471
     * @return mixed
472
     */
473
    protected function serialize($value)
474
    {
475
        return is_numeric($value) ? (string) $value : serialize($value);
476
    }
477
 
478
    /**
479
     * Unserialize the value.
480
     *
481
     * @param  mixed  $value
482
     * @return mixed
483
     */
484
    protected function unserialize($value)
485
    {
486
        if (filter_var($value, FILTER_VALIDATE_INT) !== false) {
487
            return (int) $value;
488
        }
489
 
490
        if (is_numeric($value)) {
491
            return (float) $value;
492
        }
493
 
494
        return unserialize($value);
495
    }
496
 
497
    /**
498
     * Get the DynamoDB type for the given value.
499
     *
500
     * @param  mixed  $value
501
     * @return string
502
     */
503
    protected function type($value)
504
    {
505
        return is_numeric($value) ? 'N' : 'S';
506
    }
507
 
508
    /**
509
     * Get the cache key prefix.
510
     *
511
     * @return string
512
     */
513
    public function getPrefix()
514
    {
515
        return $this->prefix;
516
    }
517
 
518
    /**
519
     * Set the cache key prefix.
520
     *
521
     * @param  string  $prefix
522
     * @return void
523
     */
524
    public function setPrefix($prefix)
525
    {
526
        $this->prefix = ! empty($prefix) ? $prefix.':' : '';
527
    }
528
 
529
    /**
530
     * Get the DynamoDb Client instance.
531
     *
532
     * @return \Aws\DynamoDb\DynamoDbClient
533
     */
534
    public function getClient()
535
    {
536
        return $this->dynamo;
537
    }
538
}