Passed
Push — v9 ( 467cbb...1836cb )
by Georges
03:07 queued 24s
created

Driver::getConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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 ExtendedCacheItemPoolInterface, 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
            if (!empty($clientConfig->isSslVerify())) {
67
                $sslBuilder = Cassandra::ssl()->withVerifyFlags(Cassandra::VERIFY_PEER_CERT);
68
            } else {
69
                $sslBuilder = Cassandra::ssl()->withVerifyFlags(Cassandra::VERIFY_NONE);
70
            }
71
72
            $clusterBuilder->withSSL($sslBuilder->build());
0 ignored issues
show
Bug introduced by
$sslBuilder->build() of type Cassandra\Cluster is incompatible with the type Cassandra\SSLOptions expected by parameter $options of Cassandra\Cluster\Builder::withSSL(). ( Ignorable by Annotation )

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

72
            $clusterBuilder->withSSL(/** @scrutinizer ignore-type */ $sslBuilder->build());
Loading history...
73
        }
74
75
        $clusterBuilder->withConnectTimeout($clientConfig->getTimeout());
76
77
        if ($clientConfig->getUsername()) {
78
            $clusterBuilder->withCredentials($clientConfig->getUsername(), $clientConfig->getPassword());
79
        }
80
81
        $this->instance = $clusterBuilder->build()->connect('');
82
83
        /**
84
         * In case of emergency:
85
         * $this->instance->execute(
86
         *      new Cassandra\SimpleStatement(\sprintf("DROP KEYSPACE %s;", self::CASSANDRA_KEY_SPACE))
87
         * );
88
         */
89
90
        $this->instance->execute(
91
            new Cassandra\SimpleStatement(
92
                sprintf(
93
                    "CREATE KEYSPACE IF NOT EXISTS %s WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };",
94
                    self::CASSANDRA_KEY_SPACE
95
                )
96
            ),
97
            []
98
        );
99
        $this->instance->execute(new Cassandra\SimpleStatement(sprintf('USE %s;', self::CASSANDRA_KEY_SPACE)), []);
100
        $this->instance->execute(
101
            new Cassandra\SimpleStatement(
102
                sprintf(
103
                    '
104
                CREATE TABLE IF NOT EXISTS %s (
105
                    cache_uuid uuid,
106
                    cache_id varchar,
107
                    cache_data text,
108
                    cache_creation_date timestamp,
109
                    cache_expiration_date timestamp,
110
                    cache_length int,
111
                    PRIMARY KEY (cache_id)
112
                );',
113
                    self::CASSANDRA_TABLE
114
                )
115
            ),
116
            []
117
        );
118
119
        return true;
120
    }
121
122
    /**
123
     * @param ExtendedCacheItemInterface $item
124
     * @return null|array
125
     */
126
    protected function driverRead(ExtendedCacheItemInterface $item): ?array
127
    {
128
        try {
129
            $options = new Cassandra\ExecutionOptions(
130
                [
131
                    'arguments' => ['cache_id' => $item->getKey()],
132
                    'page_size' => 1,
133
                ]
134
            );
135
            $query = sprintf(
136
                'SELECT cache_data FROM %s.%s WHERE cache_id = :cache_id;',
137
                self::CASSANDRA_KEY_SPACE,
138
                self::CASSANDRA_TABLE
139
            );
140
            $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

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