CouchbaseBucketCache   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 2 Features 3
Metric Value
eloc 68
c 3
b 2
f 3
dl 0
loc 174
ccs 0
cts 103
cp 0
rs 10
wmc 22

9 Methods

Rating   Name   Duplication   Size   Complexity  
A doFlush() 0 17 2
A doDelete() 0 15 3
A doFetch() 0 15 4
A doContains() 0 15 3
A normalizeExpiry() 0 7 2
A normalizeKey() 0 9 2
A doSave() 0 21 3
A __construct() 0 8 2
A doGetStats() 0 14 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Common\Cache;
6
7
use Couchbase\Bucket;
8
use Couchbase\Document;
9
use Couchbase\Exception;
10
use RuntimeException;
11
use function phpversion;
12
use function serialize;
13
use function sprintf;
14
use function substr;
15
use function time;
16
use function unserialize;
17
use function version_compare;
18
19
/**
20
 * Couchbase ^2.3.0 cache provider.
21
 */
22
final class CouchbaseBucketCache extends CacheProvider
23
{
24
    private const MINIMUM_VERSION = '2.3.0';
25
26
    private const KEY_NOT_FOUND = 13;
27
28
    private const MAX_KEY_LENGTH = 250;
29
30
    private const THIRTY_DAYS_IN_SECONDS = 2592000;
31
32
    /** @var Bucket */
33
    private $bucket;
34
35
    public function __construct(Bucket $bucket)
36
    {
37
        if (version_compare(phpversion('couchbase'), self::MINIMUM_VERSION) < 0) {
38
            // Manager is required to flush cache and pull stats.
39
            throw new RuntimeException(sprintf('ext-couchbase:^%s is required.', self::MINIMUM_VERSION));
40
        }
41
42
        $this->bucket = $bucket;
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    protected function doFetch($id)
49
    {
50
        $id = $this->normalizeKey($id);
51
52
        try {
53
            $document = $this->bucket->get($id);
54
        } catch (Exception $e) {
55
            return false;
56
        }
57
58
        if ($document instanceof Document && $document->value !== false) {
59
            return unserialize($document->value);
60
        }
61
62
        return false;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    protected function doContains($id)
69
    {
70
        $id = $this->normalizeKey($id);
71
72
        try {
73
            $document = $this->bucket->get($id);
74
        } catch (Exception $e) {
75
            return false;
76
        }
77
78
        if ($document instanceof Document) {
79
            return ! $document->error;
80
        }
81
82
        return false;
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    protected function doSave($id, $data, $lifeTime = 0)
89
    {
90
        $id = $this->normalizeKey($id);
91
92
        $lifeTime = $this->normalizeExpiry($lifeTime);
93
94
        try {
95
            $encoded = serialize($data);
96
97
            $document = $this->bucket->upsert($id, $encoded, [
98
                'expiry' => (int) $lifeTime,
99
            ]);
100
        } catch (Exception $e) {
101
            return false;
102
        }
103
104
        if ($document instanceof Document) {
105
            return ! $document->error;
106
        }
107
108
        return false;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    protected function doDelete($id)
115
    {
116
        $id = $this->normalizeKey($id);
117
118
        try {
119
            $document = $this->bucket->remove($id);
120
        } catch (Exception $e) {
121
            return $e->getCode() === self::KEY_NOT_FOUND;
122
        }
123
124
        if ($document instanceof Document) {
125
            return ! $document->error;
126
        }
127
128
        return false;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    protected function doFlush()
135
    {
136
        $manager = $this->bucket->manager();
137
138
        // Flush does not return with success or failure, and must be enabled per bucket on the server.
139
        // Store a marker item so that we will know if it was successful.
140
        $this->doSave(__METHOD__, true, 60);
141
142
        $manager->flush();
143
144
        if ($this->doContains(__METHOD__)) {
145
            $this->doDelete(__METHOD__);
146
147
            return false;
148
        }
149
150
        return true;
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    protected function doGetStats()
157
    {
158
        $manager          = $this->bucket->manager();
159
        $stats            = $manager->info();
160
        $nodes            = $stats['nodes'];
161
        $node             = $nodes[0];
162
        $interestingStats = $node['interestingStats'];
163
164
        return [
165
            Cache::STATS_HITS   => $interestingStats['get_hits'],
166
            Cache::STATS_MISSES => $interestingStats['cmd_get'] - $interestingStats['get_hits'],
167
            Cache::STATS_UPTIME => $node['uptime'],
168
            Cache::STATS_MEMORY_USAGE     => $interestingStats['mem_used'],
169
            Cache::STATS_MEMORY_AVAILABLE => $node['memoryFree'],
170
        ];
171
    }
172
173
    private function normalizeKey(string $id) : string
174
    {
175
        $normalized = substr($id, 0, self::MAX_KEY_LENGTH);
176
177
        if ($normalized === false) {
178
            return $id;
179
        }
180
181
        return $normalized;
182
    }
183
184
    /**
185
     * Expiry treated as a unix timestamp instead of an offset if expiry is greater than 30 days.
186
     *
187
     * @src https://developer.couchbase.com/documentation/server/4.1/developer-guide/expiry.html
188
     */
189
    private function normalizeExpiry(int $expiry) : int
190
    {
191
        if ($expiry > self::THIRTY_DAYS_IN_SECONDS) {
192
            return time() + $expiry;
193
        }
194
195
        return $expiry;
196
    }
197
}
198