Passed
Push — master ( a814c5...4a07f1 )
by hugh
08:00
created

Store   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 426
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 167
c 1
b 0
f 0
dl 0
loc 426
rs 9.36
wmc 38

17 Methods

Rating   Name   Duplication   Size   Complexity  
A put() 0 12 1
A add() 0 28 3
A forever() 0 12 1
A putMany() 0 28 6
A lock() 0 3 1
A flush() 0 3 1
A putManyForever() 0 3 1
A increment() 0 4 1
A forget() 0 11 1
A decrement() 0 4 1
A get() 0 11 1
A incrementOrDecrement() 0 36 5
B flushExpiredRows() 0 68 5
A restoreLock() 0 3 1
B many() 0 43 7
A getIndexTable() 0 3 1
A __construct() 0 7 1
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
     * @throws OTSServerException
67
     * @throws OTSClientException
68
     */
69
    public function get($key)
70
    {
71
        $request = [
72
            'table_name' => $this->getTable(),
73
            'primary_key' => $this->makePrimaryKey($key),
74
            'max_versions' => 1,
75
        ];
76
77
        $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

77
        $response = $this->getOts()->/** @scrutinizer ignore-call */ getRow($request);
Loading history...
78
79
        return $this->parseValueInOtsResponse($response);
80
    }
81
82
    /**
83
     * Store an item in the cache for a given number of seconds.
84
     *
85
     * @inheritDoc
86
     * @throws OTSClientException
87
     * @throws OTSServerException
88
     */
89
    public function put($key, $value, $seconds): bool
90
    {
91
        $request = [
92
            'table_name' => $this->getTable(),
93
            'condition' => RowExistenceExpectationConst::CONST_IGNORE,
94
            'primary_key' => $this->makePrimaryKey($key),
95
            'attribute_columns' => $this->makeAttributeColumns($value, $seconds),
96
        ];
97
98
        $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

98
        $response = $this->getOts()->/** @scrutinizer ignore-call */ putRow($request);
Loading history...
99
100
        return isset($response['primary_key'], $response['attribute_columns']);
101
    }
102
103
    /**
104
     * @param  string|int  $key
105
     * @param  mixed  $value
106
     * @param  int|null  $seconds
107
     * @return bool
108
     * @throws OTSClientException
109
     * @throws OTSServerException
110
     */
111
    public function add($key, $value, int $seconds = null): bool
112
    {
113
        $request = [
114
            'table_name' => $this->getTable(),
115
            'condition' => [
116
                'row_existence' => RowExistenceExpectationConst::CONST_IGNORE,
117
                /** (Col2 <= 10) */
118
                'column_condition' => [
119
                    'column_name' => 'expiration',
120
                    'value' => [$this->currentTime(), ColumnTypeConst::CONST_INTEGER],
121
                    'comparator' => ComparatorTypeConst::CONST_LESS_EQUAL,
122
                    'pass_if_missing' => true,
123
                    'latest_version_only' => true,
124
                ],
125
            ],
126
            'primary_key' => $this->makePrimaryKey($key),
127
            'attribute_columns' => $this->makeAttributeColumns($value, $seconds),
128
        ];
129
130
        try {
131
            $response = $this->getOts()->putRow($request);
132
133
            return isset($response['primary_key'], $response['attribute_columns']);
134
        } catch (OTSServerException $exception) {
135
            if ('OTSConditionCheckFail' === $exception->getOTSErrorCode()) {
136
                return false;
137
            }
138
            throw $exception;
139
        }
140
    }
141
142
    /**
143
     * @inheritDoc
144
     * @throws OTSClientException
145
     * @throws OTSServerException
146
     */
147
    public function many(array $keys): array
148
    {
149
        if (empty($keys)) {
150
            return [];
151
        }
152
153
        $primaryKeys = Collection::make($keys)->values()->map(function ($key) {
154
            return $this->makePrimaryKey($key);
155
        });
156
157
        $request = [
158
            'tables' => [
159
                [
160
                    'table_name' => $this->getTable(),
161
                    'max_versions' => 1,
162
                    'primary_keys' => $primaryKeys->toArray()
163
                ]
164
            ],
165
        ];
166
167
        $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

167
        $response = $this->getOts()->/** @scrutinizer ignore-call */ batchGetRow($request);
Loading history...
168
169
        $results = Collection::make($keys)->values()->mapWithKeys(function ($key) {
170
            return [$key => null];
171
        })->toArray();
172
173
        foreach ($response['tables'] as $table) {
174
            if ('cache' !== $table['table_name']) {
175
                continue;
176
            }
177
178
            foreach ($table['rows'] as $row) {
179
                if (!$row['is_ok']) {
180
                    continue;
181
                }
182
183
                if (!is_null($key = $this->parseKeyInOtsResponse($row))) {
184
                    $results[$key] = $this->parseValueInOtsResponse($row);
185
                }
186
            }
187
        }
188
189
        return $results;
190
    }
191
192
    /**
193
     * @inheritDoc
194
     * @throws OTSClientException
195
     * @throws OTSServerException
196
     */
197
    public function putMany(array $values, $seconds): bool
198
    {
199
        if (empty($values)) {
200
            return true;
201
        }
202
203
        $rows = Collection::make($values)->map(function ($value, $key) use ($seconds) {
204
            return [
205
                'operation_type' => OperationTypeConst::CONST_PUT,
206
                'condition' => RowExistenceExpectationConst::CONST_IGNORE,
207
                'primary_key' => $this->makePrimaryKey($key),
208
                'attribute_columns' => $this->makeAttributeColumns($value, $seconds),
209
            ];
210
        });
211
        $request = ['tables' => [['table_name' => $this->getTable(), 'rows' => $rows->values()->toArray()]]];
212
        $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

212
        $response = $this->getOts()->/** @scrutinizer ignore-call */ batchWriteRow($request);
Loading history...
213
214
        $rowResults = [];
215
        foreach ($response['tables'] as $table) {
216
            if ($this->getTable() !== $table['table_name']) {
217
                continue;
218
            }
219
            foreach ($table['rows'] as $row) {
220
                $rowResults[] = $row['is_ok'];
221
            }
222
        }
223
224
        return !empty($rowResults) && !in_array(false, $rowResults, true);
225
    }
226
227
    /**
228
     * @param  array  $values
229
     * @return bool
230
     * @throws OTSClientException
231
     * @throws OTSServerException
232
     */
233
    public function putManyForever(array $values): bool
234
    {
235
        return $this->putMany($values, null);
236
    }
237
238
    /**
239
     * @inheritDoc
240
     * @throws OTSClientException
241
     * @throws OTSServerException
242
     */
243
    public function increment($key, $value = 1)
244
    {
245
        return $this->incrementOrDecrement($key, $value, function ($current, $value) {
246
            return $current + $value;
247
        });
248
    }
249
250
    /**
251
     * @inheritDoc
252
     * @throws OTSClientException
253
     * @throws OTSServerException
254
     */
255
    public function decrement($key, $value = 1)
256
    {
257
        return $this->incrementOrDecrement($key, $value, function ($current, $value) {
258
            return $current - $value;
259
        });
260
    }
261
262
    /**
263
     * @param  string|int  $key
264
     * @param  mixed  $value
265
     * @param  Closure  $callback
266
     * @return false|mixed
267
     * @throws OTSClientException
268
     * @throws OTSServerException
269
     */
270
    protected function incrementOrDecrement($key, $value, Closure $callback)
271
    {
272
        if (!is_numeric($current = $this->get($key))) {
273
            return false;
274
        }
275
276
        $new = $callback((int) $current, $value);
277
278
        $request = [
279
            'table_name' => $this->getTable(),
280
            'condition' => [
281
                'row_existence' => RowExistenceExpectationConst::CONST_EXPECT_EXIST,
282
                'column_condition' => [
283
                    'column_name' => 'value',
284
                    'value' => [$this->serialize($current), ColumnTypeConst::CONST_BINARY],
285
                    'comparator' => ComparatorTypeConst::CONST_EQUAL,
286
                ],
287
            ],
288
            'primary_key' => $this->makePrimaryKey($key),
289
            'update_of_attribute_columns' => [
290
                'PUT' => $this->makeAttributeColumns($new),
291
            ],
292
        ];
293
294
        try {
295
            $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

295
            $response = $this->getOts()->/** @scrutinizer ignore-call */ updateRow($request);
Loading history...
296
            if (isset($response['primary_key'], $response['attribute_columns'])) {
297
                return $new;
298
            }
299
        } catch (OTSServerException $exception) {
300
            if ('OTSConditionCheckFail' !== $exception->getOTSErrorCode()) {
301
                throw $exception;
302
            }
303
        }
304
305
        return false;
306
    }
307
308
    /**
309
     * @inheritDoc
310
     * @throws OTSClientException
311
     * @throws OTSServerException
312
     */
313
    public function forever($key, $value): bool
314
    {
315
        $request = [
316
            'table_name' => $this->getTable(),
317
            'condition' => RowExistenceExpectationConst::CONST_IGNORE,
318
            'primary_key' => $this->makePrimaryKey($key),
319
            'attribute_columns' => $this->makeAttributeColumns($value),
320
        ];
321
322
        $response = $this->getOts()->putRow($request);
323
324
        return isset($response['primary_key'], $response['attribute_columns']);
325
    }
326
327
    /**
328
     * @inheritDoc
329
     * @throws OTSClientException
330
     * @throws OTSServerException
331
     */
332
    public function forget($key): bool
333
    {
334
        $request = [
335
            'table_name' => $this->getTable(),
336
            'condition' => RowExistenceExpectationConst::CONST_IGNORE,
337
            'primary_key' => $this->makePrimaryKey($key),
338
        ];
339
340
        $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

340
        $response = $this->getOts()->/** @scrutinizer ignore-call */ deleteRow($request);
Loading history...
341
342
        return isset($response['primary_key'], $response['attribute_columns']);
343
    }
344
345
    /**
346
     * @inheritDoc
347
     */
348
    public function flush(): bool
349
    {
350
        return true;
351
    }
352
353
    /**
354
     * Get a lock instance.
355
     *
356
     * @param  string  $name
357
     * @param  int  $seconds
358
     * @param  string|null  $owner
359
     *
360
     * @return \Illuminate\Contracts\Cache\Lock
361
     */
362
    public function lock($name, $seconds = 0, $owner = null)
363
    {
364
        return new Lock($this->getOts(), $this->getTable(), $this->prefix, $name, $seconds, $owner);
365
    }
366
367
    /**
368
     * Restore a lock instance using the owner identifier.
369
     *
370
     * @param  string  $name
371
     * @param  string  $owner
372
     *
373
     * @return \Illuminate\Contracts\Cache\Lock
374
     */
375
    public function restoreLock($name, $owner)
376
    {
377
        return $this->lock($name, 0, $owner);
378
    }
379
380
    /**
381
     * @throws OTSServerException
382
     * @throws OTSClientException
383
     * @throws InvalidArgumentException
384
     * @throws Exception
385
     */
386
    public function flushExpiredRows($count = 200, $expiredDuration = 24 * 3600)
387
    {
388
        $indexTable = $this->getIndexTable();
389
        if (empty($indexTable)) {
390
            throw new Exception('Configure the INDEX table first!');
391
        }
392
393
        if ($expiredDuration < 24 * 3600) {
394
            throw new InvalidArgumentException('At least one day expired before it can be cleaned.');
395
        }
396
397
        /** Query */
398
        $request = [
399
            'table_name' => $indexTable,
400
            'max_versions' => 1,
401
            'direction' => DirectionConst::CONST_FORWARD,
402
            'inclusive_start_primary_key' => [
403
                ['expiration', 1],
404
                ['key', null, PrimaryKeyTypeConst::CONST_INF_MIN],
405
                ['prefix', null, PrimaryKeyTypeConst::CONST_INF_MIN],
406
                ['type', null, PrimaryKeyTypeConst::CONST_INF_MIN],
407
            ],
408
            'exclusive_end_primary_key' => [
409
                ['expiration', (time() - $expiredDuration)],
410
                ['key', null, PrimaryKeyTypeConst::CONST_INF_MAX],
411
                ['prefix', null, PrimaryKeyTypeConst::CONST_INF_MAX],
412
                ['type', null, PrimaryKeyTypeConst::CONST_INF_MAX],
413
            ],
414
            'limit' => $count,
415
        ];
416
        $response = $this->getOts()->getRange($request);
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

416
        $response = $this->getOts()->/** @scrutinizer ignore-call */ getRange($request);
Loading history...
417
418
        /** Parse */
419
        $rows = [];
420
        foreach ($response['rows'] as $row) {
421
            $row = Collection::make($row['primary_key'])->keyBy(0);
422
            $rows[] = [
423
                'operation_type' => OperationTypeConst::CONST_DELETE,
424
                'condition' => [
425
                    'row_existence' => RowExistenceExpectationConst::CONST_IGNORE,
426
                    /** (Col2 <= 10) */
427
                    'column_condition' => [
428
                        'column_name' => 'expiration',
429
                        'value' => [time(), ColumnTypeConst::CONST_INTEGER],
430
                        'comparator' => ComparatorTypeConst::CONST_LESS_EQUAL,
431
                        'pass_if_missing' => true,
432
                        'latest_version_only' => true,
433
                    ],
434
                ],
435
                'primary_key' => [
436
                    $row->get('key'),
437
                    $row->get('prefix'),
438
                    $row->get('type'),
439
                ],
440
            ];
441
        }
442
        if (empty($rows)) {
443
            return 0;
444
        }
445
446
        /** Delete */
447
        $this->getOts()->batchWriteRow([
448
            'tables' => [
449
                ['table_name' => $this->getTable(), 'rows' => $rows]
450
            ]
451
        ]);
452
453
        return count($rows);
454
    }
455
}
456