Passed
Push — master ( 92f8ce...effd12 )
by hugh
12:15
created

Store::putMany()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
eloc 17
c 2
b 0
f 0
nc 7
nop 2
dl 0
loc 28
rs 9.0777
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: hugh.li
5
 * Date: 2021/6/8
6
 * Time: 5:56 下午.
7
 */
8
9
namespace HughCube\Laravel\OTS\Cache;
10
11
use Aliyun\OTS\Consts\ColumnTypeConst;
12
use Aliyun\OTS\Consts\ComparatorTypeConst;
13
use Aliyun\OTS\Consts\DirectionConst;
14
use Aliyun\OTS\Consts\OperationTypeConst;
15
use Aliyun\OTS\Consts\PrimaryKeyTypeConst;
16
use Aliyun\OTS\Consts\RowExistenceExpectationConst;
17
use Aliyun\OTS\OTSClientException;
18
use Aliyun\OTS\OTSServerException;
19
use Closure;
20
use Exception;
21
use HughCube\Laravel\OTS\Connection;
22
use Illuminate\Cache\TaggableStore;
23
use Illuminate\Contracts\Cache\LockProvider;
24
use Illuminate\Contracts\Cache\Store as IlluminateStore;
25
use Illuminate\Support\Collection;
26
use InvalidArgumentException;
27
28
class Store extends TaggableStore implements IlluminateStore, LockProvider
29
{
30
    use Attribute;
31
32
    /**
33
     * @var string
34
     */
35
    protected $indexTable;
36
37
    /**
38
     * Store constructor.
39
     *
40
     * @param  Connection  $ots
41
     * @param  string  $table
42
     * @param  string  $prefix
43
     * @param  string|null  $indexTable
44
     */
45
    public function __construct(Connection $ots, string $table, string $prefix, ?string $indexTable)
46
    {
47
        $this->ots = $ots;
48
        $this->table = $table;
49
        $this->prefix = $prefix;
50
        $this->type = 'cache';
51
        $this->indexTable = $indexTable;
52
    }
53
54
    /**
55
     * @return string|null
56
     */
57
    public function getIndexTable(): ?string
58
    {
59
        return $this->indexTable;
60
    }
61
62
    /**
63
     * Retrieve an item from the cache by key.
64
     *
65
     * @inheritDoc
66
     *
67
     * @throws OTSServerException
68
     * @throws OTSClientException
69
     */
70
    public function get($key)
71
    {
72
        $request = [
73
            'table_name' => $this->getTable(),
74
            'primary_key' => $this->makePrimaryKey($key),
75
            'max_versions' => 1,
76
        ];
77
78
        $response = $this->getOts()->getRow($request);
0 ignored issues
show
Bug introduced by
The method getRow() does not exist on HughCube\Laravel\OTS\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

78
        $response = $this->getOts()->/** @scrutinizer ignore-call */ getRow($request);
Loading history...
79
80
        return $this->parseValueInOtsResponse($response);
81
    }
82
83
    /**
84
     * Store an item in the cache for a given number of seconds.
85
     *
86
     * @inheritDoc
87
     *
88
     * @throws OTSClientException
89
     * @throws OTSServerException
90
     */
91
    public function put($key, $value, $seconds): bool
92
    {
93
        $request = [
94
            'table_name' => $this->getTable(),
95
            'condition' => RowExistenceExpectationConst::CONST_IGNORE,
96
            'primary_key' => $this->makePrimaryKey($key),
97
            'attribute_columns' => $this->makeAttributeColumns($value, $seconds),
98
        ];
99
100
        $response = $this->getOts()->putRow($request);
0 ignored issues
show
Bug introduced by
The method putRow() does not exist on HughCube\Laravel\OTS\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

100
        $response = $this->getOts()->/** @scrutinizer ignore-call */ putRow($request);
Loading history...
101
102
        return isset($response['primary_key'], $response['attribute_columns']);
103
    }
104
105
    /**
106
     * @param  string|int  $key
107
     * @param  mixed  $value
108
     * @param  int|null  $seconds
109
     *
110
     * @return bool
111
     * @throws OTSServerException
112
     *
113
     * @throws OTSClientException
114
     */
115
    public function add($key, $value, int $seconds = null): bool
116
    {
117
        $request = [
118
            'table_name' => $this->getTable(),
119
            'condition' => [
120
                'row_existence' => RowExistenceExpectationConst::CONST_IGNORE,
121
                /** (Col2 <= 10) */
122
                'column_condition' => [
123
                    'column_name' => 'expiration',
124
                    'value' => [$this->currentTime(), ColumnTypeConst::CONST_INTEGER],
125
                    'comparator' => ComparatorTypeConst::CONST_LESS_EQUAL,
126
                    'pass_if_missing' => true,
127
                    'latest_version_only' => true,
128
                ],
129
            ],
130
            'primary_key' => $this->makePrimaryKey($key),
131
            'attribute_columns' => $this->makeAttributeColumns($value, $seconds),
132
        ];
133
134
        try {
135
            $response = $this->getOts()->putRow($request);
136
137
            return isset($response['primary_key'], $response['attribute_columns']);
138
        } catch (OTSServerException $exception) {
139
            if ('OTSConditionCheckFail' === $exception->getOTSErrorCode()) {
140
                return false;
141
            }
142
143
            throw $exception;
144
        }
145
    }
146
147
    /**
148
     * @inheritDoc
149
     *
150
     * @throws OTSClientException
151
     * @throws OTSServerException
152
     */
153
    public function many(array $keys): array
154
    {
155
        if (empty($keys)) {
156
            return [];
157
        }
158
159
        $primaryKeys = Collection::make($keys)->values()->map(function ($key) {
160
            return $this->makePrimaryKey($key);
161
        });
162
163
        $request = [
164
            'tables' => [
165
                [
166
                    'table_name' => $this->getTable(),
167
                    'max_versions' => 1,
168
                    'primary_keys' => $primaryKeys->toArray(),
169
                ],
170
            ],
171
        ];
172
173
        $response = $this->getOts()->batchGetRow($request);
0 ignored issues
show
Bug introduced by
The method batchGetRow() does not exist on HughCube\Laravel\OTS\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

173
        $response = $this->getOts()->/** @scrutinizer ignore-call */ batchGetRow($request);
Loading history...
174
175
        $results = Collection::make($keys)->values()->mapWithKeys(function ($key) {
176
            return [$key => null];
177
        })->toArray();
178
179
        foreach ($response['tables'] as $table) {
180
            if ('cache' !== $table['table_name']) {
181
                continue;
182
            }
183
184
            foreach ($table['rows'] as $row) {
185
                if (!$row['is_ok']) {
186
                    continue;
187
                }
188
189
                if (!is_null($key = $this->parseKeyInOtsResponse($row))) {
190
                    $results[$key] = $this->parseValueInOtsResponse($row);
191
                }
192
            }
193
        }
194
195
        return $results;
196
    }
197
198
    /**
199
     * @inheritDoc
200
     *
201
     * @throws OTSClientException
202
     * @throws OTSServerException
203
     */
204
    public function putMany(array $values, $seconds): bool
205
    {
206
        if (empty($values)) {
207
            return true;
208
        }
209
210
        $rows = Collection::make($values)->map(function ($value, $key) use ($seconds) {
211
            return [
212
                'operation_type' => OperationTypeConst::CONST_PUT,
213
                'condition' => RowExistenceExpectationConst::CONST_IGNORE,
214
                'primary_key' => $this->makePrimaryKey($key),
215
                'attribute_columns' => $this->makeAttributeColumns($value, $seconds),
216
            ];
217
        });
218
        $request = ['tables' => [['table_name' => $this->getTable(), 'rows' => $rows->values()->toArray()]]];
219
        $response = $this->getOts()->batchWriteRow($request);
0 ignored issues
show
Bug introduced by
The method batchWriteRow() does not exist on HughCube\Laravel\OTS\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

219
        $response = $this->getOts()->/** @scrutinizer ignore-call */ batchWriteRow($request);
Loading history...
220
221
        $rowResults = [];
222
        foreach ($response['tables'] as $table) {
223
            if ($this->getTable() !== $table['table_name']) {
224
                continue;
225
            }
226
            foreach ($table['rows'] as $row) {
227
                $rowResults[] = $row['is_ok'];
228
            }
229
        }
230
231
        return !empty($rowResults) && !in_array(false, $rowResults, true);
232
    }
233
234
    /**
235
     * @param  array  $values
236
     *
237
     * @return bool
238
     * @throws OTSServerException
239
     *
240
     * @throws OTSClientException
241
     */
242
    public function putManyForever(array $values): bool
243
    {
244
        return $this->putMany($values, null);
245
    }
246
247
    /**
248
     * @inheritDoc
249
     *
250
     * @throws OTSClientException
251
     * @throws OTSServerException
252
     */
253
    public function increment($key, $value = 1)
254
    {
255
        return $this->incrementOrDecrement($key, $value, function ($current, $value) {
256
            return $current + $value;
257
        });
258
    }
259
260
    /**
261
     * @inheritDoc
262
     *
263
     * @throws OTSClientException
264
     * @throws OTSServerException
265
     */
266
    public function decrement($key, $value = 1)
267
    {
268
        return $this->incrementOrDecrement($key, $value, function ($current, $value) {
269
            return $current - $value;
270
        });
271
    }
272
273
    /**
274
     * @param  string|int  $key
275
     * @param  mixed  $value
276
     * @param  Closure  $callback
277
     *
278
     * @return false|mixed
279
     * @throws OTSServerException
280
     *
281
     * @throws OTSClientException
282
     */
283
    protected function incrementOrDecrement($key, $value, Closure $callback)
284
    {
285
        if (!is_numeric($current = $this->get($key))) {
286
            return false;
287
        }
288
289
        $new = $callback((int) $current, $value);
290
291
        $request = [
292
            'table_name' => $this->getTable(),
293
            'condition' => [
294
                'row_existence' => RowExistenceExpectationConst::CONST_EXPECT_EXIST,
295
                'column_condition' => [
296
                    'column_name' => 'value',
297
                    'value' => [$this->serialize($current), ColumnTypeConst::CONST_BINARY],
298
                    'comparator' => ComparatorTypeConst::CONST_EQUAL,
299
                ],
300
            ],
301
            'primary_key' => $this->makePrimaryKey($key),
302
            'update_of_attribute_columns' => [
303
                'PUT' => $this->makeAttributeColumns($new),
304
            ],
305
        ];
306
307
        try {
308
            $response = $this->getOts()->updateRow($request);
0 ignored issues
show
Bug introduced by
The method updateRow() does not exist on HughCube\Laravel\OTS\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

308
            $response = $this->getOts()->/** @scrutinizer ignore-call */ updateRow($request);
Loading history...
309
            if (isset($response['primary_key'], $response['attribute_columns'])) {
310
                return $new;
311
            }
312
        } catch (OTSServerException $exception) {
313
            if ('OTSConditionCheckFail' !== $exception->getOTSErrorCode()) {
314
                throw $exception;
315
            }
316
        }
317
318
        return false;
319
    }
320
321
    /**
322
     * @inheritDoc
323
     *
324
     * @throws OTSClientException
325
     * @throws OTSServerException
326
     */
327
    public function forever($key, $value): bool
328
    {
329
        $request = [
330
            'table_name' => $this->getTable(),
331
            'condition' => RowExistenceExpectationConst::CONST_IGNORE,
332
            'primary_key' => $this->makePrimaryKey($key),
333
            'attribute_columns' => $this->makeAttributeColumns($value),
334
        ];
335
336
        $response = $this->getOts()->putRow($request);
337
338
        return isset($response['primary_key'], $response['attribute_columns']);
339
    }
340
341
    /**
342
     * @inheritDoc
343
     *
344
     * @throws OTSClientException
345
     * @throws OTSServerException
346
     */
347
    public function forget($key): bool
348
    {
349
        $request = [
350
            'table_name' => $this->getTable(),
351
            'condition' => RowExistenceExpectationConst::CONST_IGNORE,
352
            'primary_key' => $this->makePrimaryKey($key),
353
        ];
354
355
        $response = $this->getOts()->deleteRow($request);
0 ignored issues
show
Bug introduced by
The method deleteRow() does not exist on HughCube\Laravel\OTS\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

355
        $response = $this->getOts()->/** @scrutinizer ignore-call */ deleteRow($request);
Loading history...
356
357
        return isset($response['primary_key'], $response['attribute_columns']);
358
    }
359
360
    /**
361
     * @inheritDoc
362
     */
363
    public function flush(): bool
364
    {
365
        return true;
366
    }
367
368
    /**
369
     * Get a lock instance.
370
     *
371
     * @param  string  $name
372
     * @param  int  $seconds
373
     * @param  string|null  $owner
374
     *
375
     * @return \Illuminate\Contracts\Cache\Lock
376
     */
377
    public function lock($name, $seconds = 0, $owner = null)
378
    {
379
        return new Lock($this->getOts(), $this->getTable(), $this->prefix, $name, $seconds, $owner);
380
    }
381
382
    /**
383
     * Restore a lock instance using the owner identifier.
384
     *
385
     * @param  string  $name
386
     * @param  string  $owner
387
     *
388
     * @return \Illuminate\Contracts\Cache\Lock
389
     */
390
    public function restoreLock($name, $owner)
391
    {
392
        return $this->lock($name, 0, $owner);
393
    }
394
395
    /**
396
     * @throws OTSServerException
397
     * @throws OTSClientException
398
     * @throws InvalidArgumentException
399
     * @throws Exception
400
     */
401
    public function flushExpiredRows($count = 200, $expiredDuration = 24 * 3600)
402
    {
403
        $indexTable = $this->getIndexTable();
404
        if (empty($indexTable)) {
405
            throw new Exception('Configure the INDEX table first!');
406
        }
407
408
        if ($expiredDuration < 24 * 3600) {
409
            throw new InvalidArgumentException('At least one day expired before it can be cleaned.');
410
        }
411
412
        /** Query */
413
        $response = $this->getOts()->getRange([
0 ignored issues
show
Bug introduced by
The method getRange() does not exist on HughCube\Laravel\OTS\Connection. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

413
        $response = $this->getOts()->/** @scrutinizer ignore-call */ getRange([
Loading history...
414
            'table_name' => $indexTable,
415
            'max_versions' => 1,
416
            'direction' => DirectionConst::CONST_FORWARD,
417
            'inclusive_start_primary_key' => [
418
                ['expiration', 1],
419
                ['key', null, PrimaryKeyTypeConst::CONST_INF_MIN],
420
                ['prefix', null, PrimaryKeyTypeConst::CONST_INF_MIN],
421
                ['type', null, PrimaryKeyTypeConst::CONST_INF_MIN],
422
            ],
423
            'exclusive_end_primary_key' => [
424
                ['expiration', (time() - $expiredDuration)],
425
                ['key', null, PrimaryKeyTypeConst::CONST_INF_MAX],
426
                ['prefix', null, PrimaryKeyTypeConst::CONST_INF_MAX],
427
                ['type', null, PrimaryKeyTypeConst::CONST_INF_MAX],
428
            ],
429
            'limit' => $count,
430
        ]);
431
        if (empty($response['rows'])) {
432
            return 0;
433
        }
434
435
        /** Delete */
436
        $this->getOts()->batchWriteRow([
437
            'tables' => [
438
                [
439
                    'table_name' => $this->getTable(),
440
                    'rows' => Collection::make($response['rows'])->map(function ($row) {
441
                        $row = Collection::make($row['primary_key'])->keyBy(0);
442
                        return [
443
                            'operation_type' => OperationTypeConst::CONST_DELETE,
444
                            'condition' => [
445
                                'row_existence' => RowExistenceExpectationConst::CONST_IGNORE,
446
                                /** (Col2 <= 10) */
447
                                'column_condition' => [
448
                                    'column_name' => 'expiration',
449
                                    'value' => [time(), ColumnTypeConst::CONST_INTEGER],
450
                                    'comparator' => ComparatorTypeConst::CONST_LESS_EQUAL,
451
                                    'pass_if_missing' => true,
452
                                    'latest_version_only' => true,
453
                                ],
454
                            ],
455
                            'primary_key' => [
456
                                $row->get('key'),
457
                                $row->get('prefix'),
458
                                $row->get('type'),
459
                            ],
460
                        ];
461
                    })->values()->toArray()
462
                ],
463
            ],
464
        ]);
465
466
        return count($response['rows']);
467
    }
468
}
469