Couchbase::getMulti()   B
last analyzed

Complexity

Conditions 9
Paths 8

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 9
eloc 26
c 4
b 0
f 0
nc 8
nop 2
dl 0
loc 46
rs 8.0555
1
<?php
2
3
namespace MatthiasMullie\Scrapbook\Adapters;
4
5
use MatthiasMullie\Scrapbook\Adapters\Collections\Couchbase as Collection;
6
use MatthiasMullie\Scrapbook\Exception\Exception;
7
use MatthiasMullie\Scrapbook\Exception\InvalidKey;
8
use MatthiasMullie\Scrapbook\Exception\ServerUnhealthy;
9
use MatthiasMullie\Scrapbook\KeyValueStore;
10
11
/**
12
 * Couchbase adapter. Basically just a wrapper over \CouchbaseBucket, but in an
13
 * exchangeable (KeyValueStore) interface.
14
 *
15
 * @see http://developer.couchbase.com/documentation/server/4.0/sdks/php-2.0/php-intro.html
16
 * @see http://docs.couchbase.com/sdk-api/couchbase-php-client-2.1.0/
17
 * @see http://docs.couchbase.com/sdk-api/couchbase-php-client-2.6.2/
18
 *
19
 * @author Matthias Mullie <[email protected]>
20
 * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
21
 * @license LICENSE MIT
22
 */
23
class Couchbase implements KeyValueStore
24
{
25
    /**
26
     * @var \CouchbaseBucket|\Couchbase\Bucket|\Couchbase\Collection
0 ignored issues
show
Bug introduced by
The type Couchbase\Collection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
     *                                                               \CouchbaseBucket for Couchbase SDK <=2.2,
28
     *                                                               \Couchbase\Bucket for SDK >=2.3 & <3.0,
29
     *                                                               \Couchbase\Collection for SDK >=3.0
30
     */
31
    protected $collection;
32
33
    /**
34
     * @var \CouchbaseBucketManager|\Couchbase\BucketManager|\Couchbase\Management\BucketManager
0 ignored issues
show
Bug introduced by
The type Couchbase\Management\BucketManager was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
35
     *                                                                                           \CouchbaseBucketManager for Couchbase SDK <=2.2,
36
     *                                                                                           \Couchbase\BucketManager for SDK >=2.3 & <4.0,
37
     *                                                                                           \Couchbase\Management\BucketManager for SDK >=4.0
38
     */
39
    protected $bucketManager;
40
41
    /**
42
     * @var \CouchbaseBucket|\Couchbase\Bucket
43
     *                                         \CouchbaseBucket for Couchbase SDK <=2.2,
44
     *                                         \Couchbase\Bucket for SDK >=2.3
45
     */
46
    protected $bucket;
47
48
    /**
49
     * @var int|null Timeout in ms
50
     */
51
    protected $timeout;
52
53
    /**
54
     * @param \CouchbaseBucket|\Couchbase\Bucket|\Couchbase\Collection                                   $client
55
     *                                                                                                                  \CouchbaseBucket for Couchbase SDK <=2.2,
56
     *                                                                                                                  \Couchbase\Bucket for SDK >=2.3 & <3.0,
57
     *                                                                                                                  \Couchbase\Collection for SDK >=3.0
58
     * @param \CouchbaseBucketManager|\Couchbase\BucketManager|\Couchbase\Management\BucketManager|false $bucketManager
59
     *                                                                                                                  \CouchbaseBucketManager for Couchbase SDK <=2.2,
60
     *                                                                                                                  \Couchbase\BucketManager for SDK >=2.3 & <4.0,
61
     *                                                                                                                  \Couchbase\Management\BucketManager for SDK >=4.0,
62
     *                                                                                                                  false for compatibility with when this 2nd argument was $assertServerHealthy
63
     * @param \CouchbaseBucket|\Couchbase\Bucket                                                         $bucketManager
64
     *                                                                                                                  \CouchbaseBucket for Couchbase SDK <=2.2,
65
     *                                                                                                                  \Couchbase\Bucket for SDK >=2.3,
66
     *                                                                                                                  null for compatibility with when this argument didn't yet exist
67
     * @param int                                                                                        $timeout       K/V timeout in ms
68
     *
69
     * @throws ServerUnhealthy
70
     */
71
    public function __construct(
72
        /* \CouchbaseBucket|\Couchbase\Bucket|\Couchbase\Collection */
73
        $client,
74
        /* \CouchbaseBucketManager|\Couchbase\BucketManager|\Couchbase\Management\BucketManager|false */
75
        $bucketManager,
76
        /* \CouchbaseBucket|\Couchbase\Bucket|null */
77
        $bucket,
78
        /* int|null */
79
        $timeout = null
80
    ) {
81
        // BC: $assertServerHealthy used to be 2nd argument
82
        $assertServerHealthy = is_bool($bucketManager) ? $bucketManager : false;
0 ignored issues
show
introduced by
The condition is_bool($bucketManager) is always false.
Loading history...
83
        $this->timeout = $timeout;
84
85
        if ($client instanceof \CouchbaseBucket) {
86
            // SDK <=2.2
87
            $this->collection = $client;
88
            $this->bucket = $bucket instanceof \CouchbaseBucket ? $bucket : $client;
89
90
            if ($bucketManager instanceof \CouchbaseBucketManager) {
0 ignored issues
show
introduced by
$bucketManager is never a sub-type of CouchbaseBucketManager.
Loading history...
91
                $this->bucketManager = $bucketManager;
92
            } else {
93
                $this->bucketManager = $client->manager();
94
            }
95
96
            if ($assertServerHealthy) {
0 ignored issues
show
introduced by
The condition $assertServerHealthy is always false.
Loading history...
97
                $info = $this->bucketManager->info();
98
                foreach ($info['nodes'] as $node) {
99
                    if ('healthy' !== $node['status']) {
100
                        throw new ServerUnhealthy('Server isn\'t ready yet');
101
                    }
102
                }
103
            }
104
        } elseif ($client instanceof \Couchbase\Bucket && !method_exists($client, 'defaultCollection')) {
105
            // SDK <3.0
106
            $this->collection = $client;
107
            $this->bucket = $bucket instanceof \Couchbase\Bucket ? $bucket : $client;
108
109
            if ($bucketManager instanceof \Couchbase\BucketManager) {
0 ignored issues
show
introduced by
$bucketManager is never a sub-type of Couchbase\BucketManager.
Loading history...
110
                $this->bucketManager = $bucketManager;
111
            } elseif (method_exists($client, 'manager')) {
112
                $this->bucketManager = $client->manager();
113
            }
114
115
            if ($assertServerHealthy) {
0 ignored issues
show
introduced by
The condition $assertServerHealthy is always false.
Loading history...
116
                $info = $this->bucket->ping();
117
                foreach ($info['services']['kv'] as $kv) {
118
                    if ('ok' !== $kv['state']) {
119
                        throw new ServerUnhealthy('Server isn\'t ready yet');
120
                    }
121
                }
122
            }
123
        } elseif (
124
            $client instanceof \Couchbase\Collection && $bucket instanceof \Couchbase\Bucket &&
125
            (
126
                // SDK >= 3.0 & < 4.0
127
                $bucketManager instanceof \Couchbase\BucketManager ||
128
                // SDK >= 4.0
129
                $bucketManager instanceof \Couchbase\Management\BucketManager
130
            )
131
        ) {
132
            $this->collection = $client;
133
            $this->bucketManager = $bucketManager;
134
            $this->bucket = $bucket;
135
        } elseif (
136
            // received bucket for client, but since we didn't go down the SDK <3.0
137
            // path, we're on a more recent SDK & should've received a collection
138
            $client instanceof \Couchbase\Bucket ||
139
            // received collection, but other params are invalid
140
            $client instanceof \Couchbase\Collection
141
        ) {
142
            throw new Exception('Invalid Couchbase adapter constructor arguments. \Couchbase\Collection, \Couchbase\BucketManager & \Couchbase\Bucket arguments are required for Couchbase SDK >= 3.x or 4.x');
143
        } else {
144
            throw new Exception('Invalid Couchbase adapter constructor arguments');
145
        }
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function get($key, &$token = null)
152
    {
153
        $this->assertValidKey($key);
154
155
        if ($this->collection instanceof \Couchbase\Collection) {
156
            // SDK >=3.0
157
            try {
158
                $options = new \Couchbase\GetOptions();
0 ignored issues
show
Bug introduced by
The type Couchbase\GetOptions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
159
                $options = null === $this->timeout ? $options : $options->timeout($this->timeout);
160
                $result = $this->collection->get($key, $options);
0 ignored issues
show
Bug introduced by
It seems like $options can also be of type Couchbase\GetOptions; however, parameter $options of Couchbase\Bucket::get() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

160
                $result = $this->collection->get($key, /** @scrutinizer ignore-type */ $options);
Loading history...
161
                $token = $result->cas();
0 ignored issues
show
Bug introduced by
The method cas() does not exist on Couchbase\Document. ( Ignorable by Annotation )

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

161
                /** @scrutinizer ignore-call */ 
162
                $token = $result->cas();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
162
163
                return $this->unserialize($result->content());
0 ignored issues
show
Bug introduced by
The method content() does not exist on Couchbase\Document. ( Ignorable by Annotation )

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

163
                return $this->unserialize($result->/** @scrutinizer ignore-call */ content());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
164
            } catch (\Couchbase\Exception\CouchbaseException $e) {
0 ignored issues
show
Bug introduced by
The type Couchbase\Exception\CouchbaseException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
165
                // SDK >=4.0
166
                $token = null;
167
168
                return false;
169
            } catch (\Couchbase\BaseException $e) {
0 ignored issues
show
Bug introduced by
The type Couchbase\BaseException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
170
                // SDK >=3.0 & <4.0
171
                $token = null;
172
173
                return false;
174
            }
175
        }
176
177
        // SDK <3.0
178
        try {
179
            $result = $this->collection->get($key);
180
        } catch (\CouchbaseException $e) {
181
            $token = null;
182
183
            return false;
184
        }
185
186
        $token = $result->cas;
187
188
        return $result->error ? false : $this->unserialize($result->value);
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function getMulti(array $keys, array &$tokens = null)
195
    {
196
        if ($this->collection instanceof \Couchbase\Collection) {
197
            // SDK >=3.0 no longer provides *multi operations
198
            $results = array();
199
            $tokens = array();
200
            foreach ($keys as $key) {
201
                $token = null;
202
                $value = $this->get($key, $token);
203
204
                if (null !== $token) {
205
                    $results[$key] = $value;
206
                    $tokens[$key] = $token;
207
                }
208
            }
209
210
            return $results;
211
        }
212
213
        // SDK <3.0
214
        array_map(array($this, 'assertValidKey'), $keys);
215
216
        $tokens = array();
217
        if (empty($keys)) {
218
            return array();
219
        }
220
221
        try {
222
            $results = $this->collection->get($keys);
223
        } catch (\CouchbaseException $e) {
224
            return array();
225
        }
226
227
        $values = array();
228
        $tokens = array();
229
230
        foreach ($results as $key => $value) {
231
            if (!in_array($key, $keys) || $value->error) {
232
                continue;
233
            }
234
235
            $values[$key] = $this->unserialize($value->value);
236
            $tokens[$key] = $value->cas;
237
        }
238
239
        return $values;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245
    public function set($key, $value, $expire = 0)
246
    {
247
        $this->assertValidKey($key);
248
249
        if ($this->deleteIfExpired($key, $expire)) {
250
            return true;
251
        }
252
253
        $value = $this->serialize($value);
254
255
        if ($this->collection instanceof \Couchbase\Collection) {
256
            // SDK >=3.0
257
            try {
258
                $options = new \Couchbase\UpsertOptions();
0 ignored issues
show
Bug introduced by
The type Couchbase\UpsertOptions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
259
                $options = null === $this->timeout ? $options : $options->timeout($this->timeout);
260
                $options = $options->expiry($expire);
261
                $this->collection->upsert($key, $value, $options);
262
263
                return true;
264
            } catch (\Couchbase\Exception\CouchbaseException $e) {
265
                // SDK >=4.0
266
                return false;
267
            } catch (\Couchbase\BaseException $e) {
268
                // SDK >=3.0 & <4.0
269
                return false;
270
            }
271
        }
272
273
        // SDK <3.0
274
        try {
275
            $options = array('expiry' => $expire);
276
            $result = $this->collection->upsert($key, $value, $options);
277
        } catch (\CouchbaseException $e) {
278
            return false;
279
        }
280
281
        return !$result->error;
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function setMulti(array $items, $expire = 0)
288
    {
289
        if ($this->collection instanceof \Couchbase\Collection) {
290
            // SDK >=3.0 no longer provides *multi operations
291
            $success = array();
292
            foreach ($items as $key => $value) {
293
                $success[$key] = $this->set($key, $value, $expire);
294
            }
295
296
            return $success;
297
        }
298
299
        // SDK <3.0
300
        array_map(array($this, 'assertValidKey'), array_keys($items));
301
302
        if (empty($items)) {
303
            return array();
304
        }
305
306
        $keys = array_keys($items);
307
        if ($this->deleteIfExpired($keys, $expire)) {
308
            return array_fill_keys($keys, true);
309
        }
310
311
        // attempting to insert integer keys (e.g. '0' as key is automatically
312
        // cast to int, if it's an array key) fails with a segfault, so we'll
313
        // have to do those piecemeal
314
        $integers = array_filter(array_keys($items), 'is_int');
315
        if ($integers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $integers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
316
            $success = array();
317
            $integers = array_intersect_key($items, array_fill_keys($integers, null));
318
            foreach ($integers as $k => $v) {
319
                $success[$k] = $this->set((string) $k, $v, $expire);
320
            }
321
322
            $items = array_diff_key($items, $integers);
323
324
            return array_merge($success, $this->setMulti($items, $expire));
325
        }
326
327
        foreach ($items as $key => $value) {
328
            $items[$key] = array(
329
                'value' => $this->serialize($value),
330
                'expiry' => $expire,
331
            );
332
        }
333
334
        try {
335
            $results = $this->collection->upsert($items);
0 ignored issues
show
Bug introduced by
The call to Couchbase\Bucket::upsert() has too few arguments starting with value. ( Ignorable by Annotation )

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

335
            /** @scrutinizer ignore-call */ 
336
            $results = $this->collection->upsert($items);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
336
        } catch (\CouchbaseException $e) {
337
            return array_fill_keys(array_keys($items), false);
338
        }
339
340
        $success = array();
341
        foreach ($results as $key => $result) {
342
            $success[$key] = !$result->error;
343
        }
344
345
        return $success;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function delete($key)
352
    {
353
        $this->assertValidKey($key);
354
355
        if ($this->collection instanceof \Couchbase\Collection) {
356
            // SDK >=3.0
357
            try {
358
                $options = new \Couchbase\RemoveOptions();
0 ignored issues
show
Bug introduced by
The type Couchbase\RemoveOptions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
359
                $options = null === $this->timeout ? $options : $options->timeout($this->timeout);
360
                $this->collection->remove($key, $options);
0 ignored issues
show
Bug introduced by
It seems like $options can also be of type Couchbase\RemoveOptions; however, parameter $options of Couchbase\Bucket::remove() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

360
                $this->collection->remove($key, /** @scrutinizer ignore-type */ $options);
Loading history...
361
362
                return true;
363
            } catch (\Couchbase\Exception\CouchbaseException $e) {
364
                // SDK >=4.0
365
                return false;
366
            } catch (\Couchbase\BaseException $e) {
367
                // SDK >=3.0 & <4.0
368
                return false;
369
            }
370
        }
371
372
        // SDK <3.0
373
        try {
374
            $result = $this->collection->remove($key);
375
        } catch (\CouchbaseException $e) {
376
            return false;
377
        }
378
379
        return !$result->error;
380
    }
381
382
    /**
383
     * {@inheritdoc}
384
     */
385
    public function deleteMulti(array $keys)
386
    {
387
        if ($this->collection instanceof \Couchbase\Collection) {
388
            // SDK >=3.0 no longer provides *multi operations
389
            $success = array();
390
            foreach ($keys as $key) {
391
                $success[$key] = $this->delete($key);
392
            }
393
394
            return $success;
395
        }
396
397
        // SDK <3.0
398
        array_map(array($this, 'assertValidKey'), $keys);
399
400
        if (empty($keys)) {
401
            return array();
402
        }
403
404
        try {
405
            $results = $this->collection->remove($keys);
406
        } catch (\CouchbaseException $e) {
407
            return array_fill_keys($keys, false);
408
        }
409
410
        $success = array();
411
        foreach ($results as $key => $result) {
412
            $success[$key] = !$result->error;
413
        }
414
415
        return $success;
416
    }
417
418
    /**
419
     * {@inheritdoc}
420
     */
421
    public function add($key, $value, $expire = 0)
422
    {
423
        $this->assertValidKey($key);
424
425
        $value = $this->serialize($value);
426
427
        if ($this->collection instanceof \Couchbase\Collection) {
428
            // SDK >=3.0
429
            try {
430
                $options = new \Couchbase\InsertOptions();
0 ignored issues
show
Bug introduced by
The type Couchbase\InsertOptions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
431
                $options = null === $this->timeout ? $options : $options->timeout($this->timeout);
432
                $options = $options->expiry($expire);
433
                $this->collection->insert($key, $value, $options);
434
435
                $this->deleteIfExpired($key, $expire);
436
437
                return true;
438
            } catch (\Couchbase\Exception\CouchbaseException $e) {
439
                // SDK >=4.0
440
                return false;
441
            } catch (\Couchbase\BaseException $e) {
442
                // SDK >=3.0 & <4.0
443
                return false;
444
            }
445
        }
446
447
        // SDK <3.0
448
        try {
449
            $options = array('expiry' => $expire);
450
            $result = $this->collection->insert($key, $value, $options);
451
        } catch (\CouchbaseException $e) {
452
            return false;
453
        }
454
455
        $success = !$result->error;
456
457
        // Couchbase is imprecise in its expiration handling, so we can clean up
458
        // stuff that is already expired (assuming the `add` succeeded)
459
        if ($success) {
460
            $this->deleteIfExpired($key, $expire);
461
        }
462
463
        return $success;
464
    }
465
466
    /**
467
     * {@inheritdoc}
468
     */
469
    public function replace($key, $value, $expire = 0)
470
    {
471
        $this->assertValidKey($key);
472
473
        $value = $this->serialize($value);
474
475
        if ($this->collection instanceof \Couchbase\Collection) {
476
            // SDK >=3.0
477
            try {
478
                $options = new \Couchbase\ReplaceOptions();
0 ignored issues
show
Bug introduced by
The type Couchbase\ReplaceOptions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
479
                $options = null === $this->timeout ? $options : $options->timeout($this->timeout);
480
                $options = $options->expiry($expire);
481
                $this->collection->replace($key, $value, $options);
482
483
                $this->deleteIfExpired($key, $expire);
484
485
                return true;
486
            } catch (\Couchbase\Exception\CouchbaseException $e) {
487
                // SDK >=4.0
488
                return false;
489
            } catch (\Couchbase\BaseException $e) {
490
                // SDK >=3.0 & <4.0
491
                return false;
492
            }
493
        }
494
495
        // SDK <3.0
496
        try {
497
            $options = array('expiry' => $expire);
498
            $result = $this->collection->replace($key, $value, $options);
499
        } catch (\CouchbaseException $e) {
500
            return false;
501
        }
502
503
        $success = !$result->error;
504
505
        // Couchbase is imprecise in its expiration handling, so we can clean up
506
        // stuff that is already expired (assuming the `replace` succeeded)
507
        if ($success) {
508
            $this->deleteIfExpired($key, $expire);
509
        }
510
511
        return $success;
512
    }
513
514
    /**
515
     * {@inheritdoc}
516
     */
517
    public function cas($token, $key, $value, $expire = 0)
518
    {
519
        $this->assertValidKey($key);
520
521
        $value = $this->serialize($value);
522
523
        if ($this->collection instanceof \Couchbase\Collection) {
524
            // SDK >=3.0
525
            if (null === $token) {
526
                return false;
527
            }
528
            try {
529
                $options = new \Couchbase\ReplaceOptions();
530
                $options = null === $this->timeout ? $options : $options->timeout($this->timeout);
531
                $options = $options->expiry($expire);
532
                $options = $options->cas($token);
533
                $this->collection->replace($key, $value, $options);
534
535
                $this->deleteIfExpired($key, $expire);
536
537
                return true;
538
            } catch (\Couchbase\Exception\CouchbaseException $e) {
539
                // SDK >=4.0
540
                return false;
541
            } catch (\Couchbase\BaseException $e) {
542
                // SDK >=3.0 & <4.0
543
                return false;
544
            }
545
        }
546
547
        // SDK <3.0
548
        try {
549
            $options = array('expiry' => $expire, 'cas' => $token);
550
            $result = $this->collection->replace($key, $value, $options);
551
        } catch (\CouchbaseException $e) {
552
            return false;
553
        }
554
555
        $success = !$result->error;
556
557
        // Couchbase is imprecise in its expiration handling, so we can clean up
558
        // stuff that is already expired (assuming the `cas` succeeded)
559
        if ($success) {
560
            $this->deleteIfExpired($key, $expire);
561
        }
562
563
        return $success;
564
    }
565
566
    /**
567
     * {@inheritdoc}
568
     */
569
    public function increment($key, $offset = 1, $initial = 0, $expire = 0)
570
    {
571
        $this->assertValidKey($key);
572
573
        if ($offset <= 0 || $initial < 0) {
574
            return false;
575
        }
576
577
        return $this->doIncrement($key, $offset, $initial, $expire);
578
    }
579
580
    /**
581
     * {@inheritdoc}
582
     */
583
    public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
584
    {
585
        $this->assertValidKey($key);
586
587
        if ($offset <= 0 || $initial < 0) {
588
            return false;
589
        }
590
591
        return $this->doIncrement($key, -$offset, $initial, $expire);
592
    }
593
594
    /**
595
     * {@inheritdoc}
596
     */
597
    public function touch($key, $expire)
598
    {
599
        $this->assertValidKey($key);
600
601
        if ($this->deleteIfExpired($key, $expire)) {
602
            return true;
603
        }
604
605
        if ($this->collection instanceof \Couchbase\Collection) {
606
            // SDK >=3.0
607
            try {
608
                $options = new \Couchbase\GetAndTouchOptions();
0 ignored issues
show
Bug introduced by
The type Couchbase\GetAndTouchOptions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
609
                $options = null === $this->timeout ? $options : $options->timeout($this->timeout);
610
                $this->collection->getAndTouch($key, $expire, $options);
0 ignored issues
show
Bug introduced by
It seems like $options can also be of type Couchbase\GetAndTouchOptions; however, parameter $options of Couchbase\Bucket::getAndTouch() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

610
                $this->collection->getAndTouch($key, $expire, /** @scrutinizer ignore-type */ $options);
Loading history...
611
612
                return true;
613
            } catch (\Couchbase\Exception\CouchbaseException $e) {
614
                // SDK >=4.0
615
                return false;
616
            } catch (\Couchbase\BaseException $e) {
617
                // SDK >=3.0 & <4.0
618
                return false;
619
            }
620
        }
621
622
        // SDK <3.0
623
        try {
624
            $result = $this->collection->getAndTouch($key, $expire);
625
        } catch (\CouchbaseException $e) {
626
            return false;
627
        }
628
629
        return !$result->error;
630
    }
631
632
    /**
633
     * {@inheritdoc}
634
     */
635
    public function flush()
636
    {
637
        if ($this->collection instanceof \Couchbase\Collection) {
638
            // SDK >=3.0
639
            $bucketSettings = $this->bucketManager->getBucket($this->bucket->name());
0 ignored issues
show
Bug introduced by
The method getBucket() does not exist on Couchbase\BucketManager. ( Ignorable by Annotation )

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

639
            /** @scrutinizer ignore-call */ 
640
            $bucketSettings = $this->bucketManager->getBucket($this->bucket->name());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method name() does not exist on Couchbase\Bucket. ( Ignorable by Annotation )

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

639
            $bucketSettings = $this->bucketManager->getBucket($this->bucket->/** @scrutinizer ignore-call */ name());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
640
            if (!$bucketSettings->flushEnabled()) {
641
                // `enableFlush` exists, but whether or not it is enabled is config
642
                // that doesn't belong here; Scrapbook shouldn't alter that
643
                return false;
644
            }
645
646
            $this->bucketManager->flush($this->bucket->name());
0 ignored issues
show
Unused Code introduced by
The call to Couchbase\BucketManager::flush() has too many arguments starting with $this->bucket->name(). ( Ignorable by Annotation )

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

646
            $this->bucketManager->/** @scrutinizer ignore-call */ 
647
                                  flush($this->bucket->name());

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
647
648
            return true;
649
        }
650
651
        // SDK <3.0
652
        // depending on config & client version, flush may not be available
653
        try {
654
            /*
655
             * Flush wasn't always properly implemented[1] in the client, plus
656
             * it depends on server config[2] to be enabled. Return status has
657
             * been null in both success & failure cases.
658
             * Flush is a very pervasive function that's likely not called
659
             * lightly. Since it's probably more important to know whether or
660
             * not it succeeded, than having it execute as fast as possible, I'm
661
             * going to add some calls and test if flush succeeded.
662
             *
663
             * 1: https://forums.couchbase.com/t/php-flush-isnt-doing-anything/1886/8
664
             * 2: http://docs.couchbase.com/admin/admin/CLI/CBcli/cbcli-bucket-flush.html
665
             */
666
            $this->collection->upsert('cb-flush-tester', '');
667
668
            if ($this->collection instanceof \Couchbase\Collection) {
669
                // SDK >=3.0
670
                $this->bucketManager->flush($this->bucket->name());
671
            } elseif (method_exists($this->bucketManager, 'flush')) {
672
                // SDK >=2.0.6 and <3.0
673
                $this->bucketManager->flush();
674
            } elseif (method_exists($this->collection, 'flush')) {
675
                // SDK <2.0.6
676
                $this->collection->flush();
677
            } else {
678
                return false;
679
            }
680
        } catch (\CouchbaseException $e) {
681
            return false;
682
        }
683
684
        try {
685
            // cleanup in case flush didn't go through; but if it did, we won't
686
            // be able to remove it and know flush succeeded
687
            $result = $this->collection->remove('cb-flush-tester');
688
689
            return (bool) $result->error;
690
        } catch (\CouchbaseException $e) {
691
            // exception: "The key does not exist on the server"
692
            return true;
693
        }
694
    }
695
696
    /**
697
     * {@inheritdoc}
698
     */
699
    public function getCollection($name)
700
    {
701
        return new Collection($this, $name);
702
    }
703
704
    /**
705
     * We could use `$this->collection->counter()`, but it doesn't seem to respect
706
     * data types and stores the values as strings instead of integers.
707
     *
708
     * Shared between increment/decrement: both have mostly the same logic
709
     * (decrement just increments a negative value), but need their validation
710
     * split up (increment won't accept negative values).
711
     *
712
     * @param string $key
713
     * @param int    $offset
714
     * @param int    $initial
715
     * @param int    $expire
716
     *
717
     * @return int|bool
718
     */
719
    protected function doIncrement($key, $offset, $initial, $expire)
720
    {
721
        $this->assertValidKey($key);
722
723
        $value = $this->get($key, $token);
724
        if (false === $value) {
725
            $success = $this->add($key, $initial, $expire);
726
727
            return $success ? $initial : false;
728
        }
729
730
        if (!is_numeric($value) || $value < 0) {
731
            return false;
732
        }
733
734
        $value += $offset;
735
        // value can never be lower than 0
736
        $value = max(0, $value);
737
        $success = $this->cas($token, $key, $value, $expire);
738
739
        return $success ? $value : false;
740
    }
741
742
    /**
743
     * Couchbase doesn't properly remember the data type being stored:
744
     * arrays and objects are turned into stdClass instances, or the
745
     * other way around.
746
     *
747
     * @param mixed $value
748
     *
749
     * @return string|mixed
750
     */
751
    protected function serialize($value)
752
    {
753
        // binary data doesn't roundtrip well
754
        if (is_string($value) && !preg_match('//u', $value)) {
755
            return serialize(base64_encode($value));
756
        }
757
758
        // and neither do arrays/objects
759
        if (is_array($value) || is_object($value)) {
760
            return serialize($value);
761
        }
762
763
        return $value;
764
    }
765
766
    /**
767
     * Restore serialized data.
768
     *
769
     * @param mixed $value
770
     *
771
     * @return mixed|int|float
772
     */
773
    protected function unserialize($value)
774
    {
775
        // more efficient quick check whether value is unserializable
776
        if (!is_string($value) || !preg_match('/^[saOC]:[0-9]+:/', $value)) {
777
            return $value;
778
        }
779
780
        $unserialized = @unserialize($value);
781
        if (false === $unserialized) {
782
            return $value;
783
        }
784
785
        if (is_string($unserialized)) {
786
            return base64_decode($unserialized);
787
        }
788
789
        return $unserialized;
790
    }
791
792
    /**
793
     * Couchbase seems to not timely purge items the way it should when
794
     * storing it with an expired timestamp, so we'll detect that and
795
     * delete it (instead of performing the already expired operation).
796
     *
797
     * @param string|string[] $key
798
     * @param int             $expire
799
     *
800
     * @return bool True if expired
801
     */
802
    protected function deleteIfExpired($key, $expire)
803
    {
804
        if ($expire < 0 || ($expire > 2592000 && $expire < time())) {
805
            $this->deleteMulti((array) $key);
806
807
            return true;
808
        }
809
810
        return false;
811
    }
812
813
    /**
814
     * @param string $key
815
     *
816
     * @throws InvalidKey
817
     */
818
    protected function assertValidKey($key)
819
    {
820
        if (strlen($key) > 255) {
821
            throw new InvalidKey("Invalid key: $key. Couchbase keys can not exceed 255 chars.");
822
        }
823
    }
824
}
825