TransactionalStore::commit()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 2
b 0
f 0
nc 2
nop 0
dl 0
loc 10
rs 10
1
<?php
2
3
namespace MatthiasMullie\Scrapbook\Buffered;
4
5
use MatthiasMullie\Scrapbook\Buffered\Utils\Buffer;
6
use MatthiasMullie\Scrapbook\Buffered\Utils\Transaction;
7
use MatthiasMullie\Scrapbook\Exception\UnbegunTransaction;
8
use MatthiasMullie\Scrapbook\KeyValueStore;
9
10
/**
11
 * In addition to buffering cache data in memory (see BufferedStore), this class
12
 * will add transactional capabilities. Writes can be deferred by starting a
13
 * transaction & all of them will only go out when you commit them.
14
 * This makes it possible to defer cache updates until we can guarantee it's
15
 * safe (e.g. until we successfully committed everything to persistent storage).
16
 *
17
 * There will be some trickery to make sure that, after we've made changes to
18
 * cache (but not yet committed), we don't read from the real cache anymore, but
19
 * instead serve the in-memory equivalent that we'll be writing to real cache
20
 * when all goes well.
21
 *
22
 * If a commit fails, all keys affected will be deleted to ensure no corrupt
23
 * data stays behind.
24
 *
25
 * @author Matthias Mullie <[email protected]>
26
 * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved
27
 * @license LICENSE MIT
28
 */
29
class TransactionalStore implements KeyValueStore
30
{
31
    /**
32
     * Array of KeyValueStore objects. Every cache action will be executed
33
     * on the last item in this array, so transactions can be nested.
34
     *
35
     * @var KeyValueStore[]
36
     */
37
    protected $transactions = array();
38
39
    /**
40
     * @param KeyValueStore $cache The real cache we'll buffer for
41
     */
42
    public function __construct(KeyValueStore $cache)
43
    {
44
        $this->transactions[] = $cache;
45
    }
46
47
    /**
48
     * Roll back uncommitted transactions.
49
     */
50
    public function __destruct()
51
    {
52
        while (count($this->transactions) > 1) {
53
            /** @var Transaction $transaction */
54
            $transaction = array_pop($this->transactions);
55
            $transaction->rollback();
56
        }
57
    }
58
59
    /**
60
     * Initiate a transaction: this will defer all writes to real cache until
61
     * commit() is called.
62
     */
63
    public function begin()
64
    {
65
        // we'll rely on buffer to respond data that has not yet committed, so
66
        // it must never evict from cache - I'd even rather see the app crash
67
        $buffer = new Buffer(ini_get('memory_limit'));
68
69
        // transactions can be nested: the previous transaction will serve as
70
        // cache backend for the new cache (so when committing a nested
71
        // transaction, it will commit to the parent transaction)
72
        $cache = end($this->transactions);
73
        $this->transactions[] = new Transaction($buffer, $cache);
74
    }
75
76
    /**
77
     * Commits all deferred updates to real cache.
78
     * If the any write fails, all subsequent writes will be aborted & all keys
79
     * that had already been written to will be deleted.
80
     *
81
     * @return bool
82
     *
83
     * @throws UnbegunTransaction
84
     */
85
    public function commit()
86
    {
87
        if (count($this->transactions) <= 1) {
88
            throw new UnbegunTransaction('Attempted to commit without having begun a transaction.');
89
        }
90
91
        /** @var Transaction $transaction */
92
        $transaction = array_pop($this->transactions);
93
94
        return $transaction->commit();
95
    }
96
97
    /**
98
     * Roll back all scheduled changes.
99
     *
100
     * @return bool
101
     *
102
     * @throws UnbegunTransaction
103
     */
104
    public function rollback()
105
    {
106
        if (count($this->transactions) <= 1) {
107
            throw new UnbegunTransaction('Attempted to rollback without having begun a transaction.');
108
        }
109
110
        /** @var Transaction $transaction */
111
        $transaction = array_pop($this->transactions);
112
113
        return $transaction->rollback();
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function get($key, &$token = null)
120
    {
121
        $cache = end($this->transactions);
122
123
        return $cache->get($key, $token);
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function getMulti(array $keys, array &$tokens = null)
130
    {
131
        $cache = end($this->transactions);
132
133
        return $cache->getMulti($keys, $tokens);
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function set($key, $value, $expire = 0)
140
    {
141
        $cache = end($this->transactions);
142
143
        return $cache->set($key, $value, $expire);
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function setMulti(array $items, $expire = 0)
150
    {
151
        $cache = end($this->transactions);
152
153
        return $cache->setMulti($items, $expire);
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function delete($key)
160
    {
161
        $cache = end($this->transactions);
162
163
        return $cache->delete($key);
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    public function deleteMulti(array $keys)
170
    {
171
        $cache = end($this->transactions);
172
173
        return $cache->deleteMulti($keys);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    public function add($key, $value, $expire = 0)
180
    {
181
        $cache = end($this->transactions);
182
183
        return $cache->add($key, $value, $expire);
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function replace($key, $value, $expire = 0)
190
    {
191
        $cache = end($this->transactions);
192
193
        return $cache->replace($key, $value, $expire);
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function cas($token, $key, $value, $expire = 0)
200
    {
201
        $cache = end($this->transactions);
202
203
        return $cache->cas($token, $key, $value, $expire);
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function increment($key, $offset = 1, $initial = 0, $expire = 0)
210
    {
211
        $cache = end($this->transactions);
212
213
        return $cache->increment($key, $offset, $initial, $expire);
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function decrement($key, $offset = 1, $initial = 0, $expire = 0)
220
    {
221
        $cache = end($this->transactions);
222
223
        return $cache->decrement($key, $offset, $initial, $expire);
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function touch($key, $expire)
230
    {
231
        $cache = end($this->transactions);
232
233
        return $cache->touch($key, $expire);
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function flush()
240
    {
241
        $cache = end($this->transactions);
242
243
        return $cache->flush();
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function getCollection($name)
250
    {
251
        $cache = end($this->transactions);
252
253
        return new static($cache->getCollection($name));
254
    }
255
}
256