Passed
Push — v9 ( e0c8c6...58e932 )
by Georges
11:46
created

Driver   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 135
dl 0
loc 282
rs 10
c 1
b 0
f 0
wmc 21

9 Methods

Rating   Name   Duplication   Size   Complexity  
A driverWrite() 0 46 2
A driverDelete() 0 31 2
A getHelp() 0 3 1
A getStats() 0 18 1
A driverClear() 0 17 2
A driverCheck() 0 3 2
A driverRead() 0 23 5
A driverConnect() 0 64 4
A getCompatibleExecutionOptionsArgument() 0 7 2
1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Drivers\Cassandra;
18
19
use Cassandra;
20
use Cassandra\Exception;
21
use Cassandra\Exception\InvalidArgumentException;
22
use Cassandra\Session as CassandraSession;
23
use DateTime;
24
use Phpfastcache\Cluster\AggregatablePoolInterface;
25
use Phpfastcache\Core\Item\ExtendedCacheItemInterface;
26
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
27
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait;
28
use Phpfastcache\Entities\DriverStatistic;
29
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
30
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
31
32
/**
33
 * Class Driver
34
 * @property CassandraSession|null $instance Instance of driver service
35
 * @method Config getConfig()
36
 */
37
class Driver implements AggregatablePoolInterface
38
{
39
    use TaggableCacheItemPoolTrait;
40
41
    protected const CASSANDRA_KEY_SPACE = 'phpfastcache';
42
    protected const CASSANDRA_TABLE = 'cacheItems';
43
44
    /**
45
     * @return bool
46
     */
47
    public function driverCheck(): bool
48
    {
49
        return extension_loaded('Cassandra') && class_exists(Cassandra::class);
50
    }
51
52
    /**
53
     * @return bool
54
     * @throws PhpfastcacheLogicException
55
     * @throws Exception
56
     */
57
    protected function driverConnect(): bool
58
    {
59
        $clientConfig = $this->getConfig();
60
61
        $clusterBuilder = Cassandra::cluster()
62
            ->withContactPoints($clientConfig->getHost())
63
            ->withPort($clientConfig->getPort());
64
65
        if (!empty($clientConfig->isSslEnabled())) {
66
            $sslBuilder = Cassandra::ssl();
67
            if (!empty($clientConfig->isSslVerify())) {
68
                $sslBuilder->withVerifyFlags(Cassandra::VERIFY_PEER_CERT);
69
            } else {
70
                $sslBuilder->withVerifyFlags(Cassandra::VERIFY_NONE);
71
            }
72
73
            $clusterBuilder->withSSL($sslBuilder->build());
74
        }
75
76
        $clusterBuilder->withConnectTimeout($clientConfig->getTimeout());
77
78
        if ($clientConfig->getUsername()) {
79
            $clusterBuilder->withCredentials($clientConfig->getUsername(), $clientConfig->getPassword());
80
        }
81
82
        $this->instance = $clusterBuilder->build()->connect('');
83
84
        /**
85
         * In case of emergency:
86
         * $this->instance->execute(
87
         *      new Cassandra\SimpleStatement(\sprintf("DROP KEYSPACE %s;", self::CASSANDRA_KEY_SPACE))
88
         * );
89
         */
90
91
        $this->instance->execute(
92
            new Cassandra\SimpleStatement(
93
                sprintf(
94
                    "CREATE KEYSPACE IF NOT EXISTS %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };",
95
                    self::CASSANDRA_KEY_SPACE
96
                )
97
            ),
98
            []
99
        );
100
        $this->instance->execute(new Cassandra\SimpleStatement(sprintf('USE %s;', self::CASSANDRA_KEY_SPACE)), []);
101
        $this->instance->execute(
102
            new Cassandra\SimpleStatement(
103
                sprintf(
104
                    '
105
                CREATE TABLE IF NOT EXISTS %s (
106
                    cache_uuid uuid,
107
                    cache_id varchar,
108
                    cache_data text,
109
                    cache_creation_date timestamp,
110
                    cache_expiration_date timestamp,
111
                    cache_length int,
112
                    PRIMARY KEY (cache_id)
113
                );',
114
                    self::CASSANDRA_TABLE
115
                )
116
            ),
117
            []
118
        );
119
120
        return true;
121
    }
122
123
    /**
124
     * @param ExtendedCacheItemInterface $item
125
     * @return ?array<string, mixed>
126
     */
127
    protected function driverRead(ExtendedCacheItemInterface $item): ?array
128
    {
129
        try {
130
            $options = $this->getCompatibleExecutionOptionsArgument(
131
                [
132
                    'arguments' => ['cache_id' => $item->getKey()],
133
                    'page_size' => 1,
134
                ]
135
            );
136
            $query = sprintf(
137
                'SELECT cache_data FROM %s.%s WHERE cache_id = :cache_id;',
138
                self::CASSANDRA_KEY_SPACE,
139
                self::CASSANDRA_TABLE
140
            );
141
            $results = $this->instance->execute(new Cassandra\SimpleStatement($query), $options);
0 ignored issues
show
Bug introduced by
The method execute() does not exist on null. ( Ignorable by Annotation )

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

141
            /** @scrutinizer ignore-call */ 
142
            $results = $this->instance->execute(new Cassandra\SimpleStatement($query), $options);

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...
142
143
            if ($results instanceof Cassandra\Rows && $results->count() === 1) {
144
                return $this->decode($results->first()['cache_data']) ?: null;
145
            }
146
147
            return null;
148
        } catch (Exception $e) {
149
            return null;
150
        }
151
    }
152
153
    /**
154
     * @param ExtendedCacheItemInterface $item
155
     * @return bool
156
     * @throws PhpfastcacheInvalidArgumentException
157
     */
158
    protected function driverWrite(ExtendedCacheItemInterface $item): bool
159
    {
160
        $this->assertCacheItemType($item, Item::class);
161
162
        try {
163
            $cacheData = $this->encode($this->driverPreWrap($item));
164
            $options = $this->getCompatibleExecutionOptionsArgument(
165
                [
166
                    'arguments' => [
167
                        'cache_uuid' => new Cassandra\Uuid(''),
168
                        'cache_id' => $item->getKey(),
169
                        'cache_data' => $cacheData,
170
                        'cache_creation_date' => new Cassandra\Timestamp((new DateTime())->getTimestamp(), 0),
171
                        'cache_expiration_date' => new Cassandra\Timestamp($item->getExpirationDate()->getTimestamp(), 0),
172
                        'cache_length' => strlen($cacheData),
173
                    ],
174
                    'consistency' => Cassandra::CONSISTENCY_ALL,
175
                    'serial_consistency' => Cassandra::CONSISTENCY_SERIAL,
176
                ]
177
            );
178
179
            $query = sprintf(
180
                'INSERT INTO %s.%s
181
                    (
182
                      cache_uuid, 
183
                      cache_id, 
184
                      cache_data, 
185
                      cache_creation_date, 
186
                      cache_expiration_date,
187
                      cache_length
188
                    )
189
                  VALUES (:cache_uuid, :cache_id, :cache_data, :cache_creation_date, :cache_expiration_date, :cache_length);
190
            ',
191
                self::CASSANDRA_KEY_SPACE,
192
                self::CASSANDRA_TABLE
193
            );
194
195
            $result = $this->instance->execute(new Cassandra\SimpleStatement($query), $options);
196
            /**
197
             * There's no real way atm
198
             * to know if the item has
199
             * been really upserted
200
             */
201
            return $result instanceof Cassandra\Rows;
202
        } catch (InvalidArgumentException $e) {
203
            throw new PhpfastcacheInvalidArgumentException($e->getMessage(), 0, $e);
204
        }
205
    }
206
207
    /**
208
     * @param ExtendedCacheItemInterface $item
209
     * @return bool
210
     * @throws PhpfastcacheInvalidArgumentException
211
     */
212
    protected function driverDelete(ExtendedCacheItemInterface $item): bool
213
    {
214
        $this->assertCacheItemType($item, Item::class);
215
216
        try {
217
            $options = $this->getCompatibleExecutionOptionsArgument(
218
                [
219
                    'arguments' => [
220
                        'cache_id' => $item->getKey(),
221
                    ],
222
                ]
223
            );
224
            $result = $this->instance->execute(
225
                new Cassandra\SimpleStatement(
226
                    sprintf(
227
                        'DELETE FROM %s.%s WHERE cache_id = :cache_id;',
228
                        self::CASSANDRA_KEY_SPACE,
229
                        self::CASSANDRA_TABLE
230
                    )
231
                ),
232
                $options
233
            );
234
235
            /**
236
             * There's no real way atm
237
             * to know if the item has
238
             * been really deleted
239
             */
240
            return $result instanceof Cassandra\Rows;
241
        } catch (Exception $e) {
242
            return false;
243
        }
244
    }
245
246
    /**
247
     * @return bool
248
     */
249
    protected function driverClear(): bool
250
    {
251
        try {
252
            $this->instance->execute(
253
                new Cassandra\SimpleStatement(
254
                    sprintf(
255
                        'TRUNCATE %s.%s;',
256
                        self::CASSANDRA_KEY_SPACE,
257
                        self::CASSANDRA_TABLE
258
                    )
259
                ),
260
                null
261
            );
262
263
            return true;
264
        } catch (Exception) {
265
            return false;
266
        }
267
    }
268
269
    /**
270
     * @return string
271
     */
272
    public function getHelp(): string
273
    {
274
        return <<<HELP
275
<p>
276
To install the php Cassandra extension via Pecl:
277
<code>sudo pecl install cassandra</code>
278
More information on: https://github.com/datastax/php-driver
279
Please note that this repository only provide php stubs and C/C++ sources, it does NOT provide php client.
280
</p>
281
HELP;
282
    }
283
284
    /**
285
     * @return DriverStatistic
286
     * @throws Exception
287
     */
288
    public function getStats(): DriverStatistic
289
    {
290
        $result = $this->instance->execute(
291
            new Cassandra\SimpleStatement(
292
                sprintf(
293
                    'SELECT SUM(cache_length) as cache_size FROM %s.%s',
294
                    self::CASSANDRA_KEY_SPACE,
295
                    self::CASSANDRA_TABLE
296
                )
297
            ),
298
            null
299
        );
300
301
        return (new DriverStatistic())
302
            ->setSize($result->first()['cache_size'])
303
            ->setRawData([])
304
            ->setData(implode(', ', array_keys($this->itemInstances)))
305
            ->setInfo('The cache size represents only the cache data itself without counting data structures associated to the cache entries.');
306
    }
307
308
    /**
309
     * @param array<string, mixed> $options
310
     * @return array<string, mixed>|Cassandra\ExecutionOptions
311
     */
312
    protected function getCompatibleExecutionOptionsArgument(array $options): mixed
313
    {
314
        if ($this->getConfig()->isUseLegacyExecutionOptions()) {
315
            return new Cassandra\ExecutionOptions($options);
316
        }
317
318
        return $options;
319
    }
320
}
321