Passed
Pull Request — master (#838)
by Georges
04:11 queued 02:01
created

Driver   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 134
dl 0
loc 273
rs 10
c 2
b 0
f 0
wmc 25

10 Methods

Rating   Name   Duplication   Size   Complexity  
A driverCheck() 0 3 1
A decode() 0 36 4
A getStats() 0 27 2
A getConfig() 0 3 1
A driverDelete() 0 11 2
A createCollection() 0 30 2
A driverClear() 0 7 2
B driverConnect() 0 51 6
A driverWrite() 0 23 2
A driverRead() 0 14 3
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
declare(strict_types=1);
15
16
namespace Phpfastcache\Drivers\Arangodb;
17
18
use ArangoDBClient\AdminHandler;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\AdminHandler 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...
19
use ArangoDBClient\Collection as ArangoCollection;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\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...
20
use ArangoDBClient\CollectionHandler as ArangoCollectionHandler;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\CollectionHandler 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...
21
use ArangoDBClient\Connection as ArangoConnection;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\Connection 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...
22
use ArangoDBClient\ConnectionOptions as ArangoConnectionOptions;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\ConnectionOptions 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...
23
use ArangoDBClient\Document as ArangoDocument;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\Document 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...
24
use ArangoDBClient\DocumentHandler as ArangoDocumentHandler;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\DocumentHandler 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...
25
use ArangoDBClient\Exception as ArangoException;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\Exception 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...
26
use ArangoDBClient\ServerException as ArangoServerException;
0 ignored issues
show
Bug introduced by
The type ArangoDBClient\ServerException 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
28
use Phpfastcache\Cluster\AggregatablePoolInterface;
29
use Phpfastcache\Core\Item\ExtendedCacheItemInterface;
30
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
31
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait;
32
use Phpfastcache\Entities\DriverStatistic;
33
use Phpfastcache\Event\EventReferenceParameter;
34
use Phpfastcache\Exceptions\PhpfastcacheDriverConnectException;
35
use Phpfastcache\Exceptions\PhpfastcacheDriverException;
36
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
37
38
/**
39
 * Class Driver
40
 * @property Config $config
41
 * @property ArangoConnection $instance
42
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
43
 */
44
class Driver implements ExtendedCacheItemPoolInterface, AggregatablePoolInterface
45
{
46
    use TaggableCacheItemPoolTrait;
47
48
    protected const TTL_FIELD_NAME = 't';
49
50
    protected ArangoDocumentHandler $documentHandler;
51
    protected ArangoCollectionHandler $collectionHandler;
52
53
    /**
54
     * @return bool
55
     */
56
    public function driverCheck(): bool
57
    {
58
        return \class_exists(ArangoConnection::class);
59
    }
60
61
    /**
62
     * @return bool
63
     * @throws ArangoException
64
     * @throws PhpfastcacheDriverConnectException
65
     */
66
    protected function driverConnect(): bool
67
    {
68
        $connectionOptions = [
69
            ArangoConnectionOptions::OPTION_DATABASE => $this->getConfig()->getDatabase(),
70
            ArangoConnectionOptions::OPTION_ENDPOINT => $this->getConfig()->getEndpoint(),
71
72
            ArangoConnectionOptions::OPTION_CONNECTION  => $this->getConfig()->getConnection(),
73
            ArangoConnectionOptions::OPTION_AUTH_TYPE   => $this->getConfig()->getAuthType(),
74
            ArangoConnectionOptions::OPTION_AUTH_USER   => $this->getConfig()->getAuthUser(),
75
            ArangoConnectionOptions::OPTION_AUTH_PASSWD => $this->getConfig()->getAuthPasswd(),
76
77
            ArangoConnectionOptions::OPTION_CONNECT_TIMEOUT => $this->getConfig()->getConnectTimeout(),
78
            ArangoConnectionOptions::OPTION_REQUEST_TIMEOUT => $this->getConfig()->getRequestTimeout(),
79
            ArangoConnectionOptions::OPTION_CREATE        => $this->getConfig()->isAutoCreate(),
80
            ArangoConnectionOptions::OPTION_UPDATE_POLICY => $this->getConfig()->getUpdatePolicy(),
81
82
            // Options below are not yet supported
83
            // ConnectionOptions::OPTION_MEMCACHED_PERSISTENT_ID => 'arangodb-php-pool',
84
            // ConnectionOptions::OPTION_MEMCACHED_SERVERS       => [ [ '127.0.0.1', 11211 ] ],
85
            // ConnectionOptions::OPTION_MEMCACHED_OPTIONS       => [ ],
86
            // ConnectionOptions::OPTION_MEMCACHED_ENDPOINTS_KEY => 'arangodb-php-endpoints'
87
            // ConnectionOptions::OPTION_MEMCACHED_TTL           => 600
88
        ];
89
90
        if ($this->getConfig()->getTraceFunction() !== null) {
91
            $connectionOptions[ArangoConnectionOptions::OPTION_TRACE] = $this->getConfig()->getTraceFunction();
92
        }
93
94
        if ($this->getConfig()->getAuthJwt() !== null) {
95
            $connectionOptions[ArangoConnectionOptions::OPTION_AUTH_JWT] = $this->getConfig()->getAuthJwt();
96
        }
97
98
        if (\str_starts_with($this->getConfig()->getAuthType(), 'ssl://')) {
99
            $connectionOptions[ArangoConnectionOptions::OPTION_VERIFY_CERT] = $this->getConfig()->isVerifyCert();
100
            $connectionOptions[ArangoConnectionOptions::OPTION_ALLOW_SELF_SIGNED] = $this->getConfig()->isSelfSigned();
101
            $connectionOptions[ArangoConnectionOptions::OPTION_CIPHERS] = $this->getConfig()->getCiphers();
102
        }
103
104
        $this->eventManager->dispatch('ArangodbConnection', $this, new EventReferenceParameter($connectionOptions));
105
106
        $this->instance = new ArangoConnection($connectionOptions);
107
        $this->documentHandler = new ArangoDocumentHandler($this->instance);
108
        $this->collectionHandler = new ArangoCollectionHandler($this->instance);
109
110
        $collectionNames = array_keys($this->collectionHandler->getAllCollections());
111
112
        if ($this->getConfig()->isAutoCreate() && !\in_array($this->getConfig()->getCollection(), $collectionNames, true)) {
113
            return $this->createCollection($this->getConfig()->getCollection());
114
        }
115
116
        return $this->collectionHandler->has($this->getConfig()->getCollection());
117
    }
118
119
    /**
120
     * @param ExtendedCacheItemInterface $item
121
     * @return null|array
122
     * @throws PhpfastcacheDriverException
123
     * @throws \Exception
124
     */
125
    protected function driverRead(ExtendedCacheItemInterface $item): ?array
126
    {
127
        try {
128
            $document = $this->documentHandler->get($this->getConfig()->getCollection(), $item->getEncodedKey());
129
        } catch (ArangoServerException $e) {
130
            if ($e->getCode() === 404) {
131
                return null;
132
            }
133
            throw new PhpfastcacheDriverException(
134
                'Got unexpeced error from Arangodb: ' . $e->getMessage()
135
            );
136
        }
137
138
        return $this->decode($document);
139
    }
140
141
    /**
142
     * @param ExtendedCacheItemInterface $item
143
     * @return bool
144
     * @throws ArangoException
145
     * @throws PhpfastcacheLogicException
146
     */
147
    protected function driverWrite(ExtendedCacheItemInterface $item): bool
148
    {
149
        $options = [
150
            'overwriteMode' => 'replace',
151
            'returnNew' => true,
152
            'returnOld' => false,
153
            'silent' => false,
154
        ];
155
156
        $document = new ArangoDocument();
157
        $document->setInternalKey($item->getEncodedKey());
158
        $document->set(self::DRIVER_KEY_WRAPPER_INDEX, $item->getKey());
159
        $document->set(self::DRIVER_DATA_WRAPPER_INDEX, $this->encode($item->getRawValue()));
160
        $document->set(self::DRIVER_TAGS_WRAPPER_INDEX, $item->getTags());
161
        $document->set(self::DRIVER_EDATE_WRAPPER_INDEX, $item->getExpirationDate());
162
        $document->set(self::TTL_FIELD_NAME, $item->getExpirationDate()->getTimestamp());
163
164
        if ($this->getConfig()->isItemDetailedDate()) {
165
            $document->set(self::DRIVER_CDATE_WRAPPER_INDEX, $item->getCreationDate());
166
            $document->set(self::DRIVER_MDATE_WRAPPER_INDEX, $item->getModificationDate());
167
        }
168
169
        return $this->documentHandler->insert($this->getConfig()->getCollection(), $document, $options) !== null;
170
    }
171
172
    /**
173
     * @param ExtendedCacheItemInterface $item
174
     * @return bool
175
     */
176
    protected function driverDelete(ExtendedCacheItemInterface $item): bool
177
    {
178
        $options = [
179
            'returnOld' => false
180
        ];
181
182
        try {
183
            $this->documentHandler->removeById($this->getConfig()->getCollection(), $item->getEncodedKey(), null, $options);
184
            return true;
185
        } catch (ArangoException) {
186
            return false;
187
        }
188
    }
189
190
    /**
191
     * @return bool
192
     */
193
    protected function driverClear(): bool
194
    {
195
        try {
196
            $this->collectionHandler->truncate($this->getConfig()->getCollection());
197
            return true;
198
        } catch (ArangoException) {
199
            return false;
200
        }
201
    }
202
203
    /**
204
     * @throws PhpfastcacheDriverConnectException
205
     * @throws ArangoException
206
     */
207
    protected function createCollection($collectionName): bool
208
    {
209
        $collection = new ArangoCollection($collectionName);
210
211
        try {
212
            $params = [
213
                'type' => ArangoCollection::TYPE_DOCUMENT,
214
                'waitForSync' => false
215
            ];
216
217
            $this->eventManager->dispatch('ArangodbCollectionParams', $this, new EventReferenceParameter($params));
218
219
            $this->collectionHandler->create($collection, $params);
220
221
            $this->collectionHandler->createIndex($collection, [
222
                'type'         => 'ttl',
223
                'name'         => 'expires_at',
224
                'fields'       => [self::TTL_FIELD_NAME],
225
                'unique'       => false,
226
                'sparse'       => true,
227
                'inBackground' => true,
228
                'expireAfter' => 1
229
            ]);
230
            return true;
231
        } catch (\Throwable $e) {
232
            throw new PhpfastcacheDriverConnectException(
233
                sprintf(
234
                    'Unable to automatically create the collection, error returned from ArangoDB: [%d] %s',
235
                    $e->getCode(),
236
                    $e->getMessage(),
237
                )
238
            );
239
        }
240
    }
241
242
    /**
243
     * @param ArangoDocument $document
244
     * @return mixed
245
     * @throws \Exception
246
     */
247
    protected function decode(ArangoDocument $document): mixed
248
    {
249
        $value = [
250
            self::DRIVER_KEY_WRAPPER_INDEX => $document->get(self::DRIVER_KEY_WRAPPER_INDEX),
251
            self::DRIVER_TAGS_WRAPPER_INDEX => $document->get(self::DRIVER_TAGS_WRAPPER_INDEX),
252
            self::DRIVER_DATA_WRAPPER_INDEX => \unserialize(
253
                $document->get(self::DRIVER_DATA_WRAPPER_INDEX),
254
                ['allowed_classes' => true]
255
            ),
256
        ];
257
258
        $eDate = $document->get(self::DRIVER_EDATE_WRAPPER_INDEX);
259
        $value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX] = new \DateTime(
260
            $eDate['date'],
261
            new \DateTimeZone($eDate['timezone'])
262
        );
263
264
        if ($this->getConfig()->isItemDetailedDate()) {
265
            $cDate = $document->get(self::DRIVER_CDATE_WRAPPER_INDEX);
266
            if (isset($cDate['date'], $cDate['timezone'])) {
267
                $value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX] = new \DateTime(
268
                    $cDate['date'],
269
                    new \DateTimeZone($cDate['timezone'])
270
                );
271
            }
272
273
            $mDate = $document->get(self::DRIVER_MDATE_WRAPPER_INDEX);
274
            if (isset($mDate['date'], $cDate['timezone'])) {
275
                $value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX] = new \DateTime(
276
                    $mDate['date'],
277
                    new \DateTimeZone($mDate['timezone'])
278
                );
279
            }
280
        }
281
282
        return $value;
283
    }
284
285
    public function getStats(): DriverStatistic
286
    {
287
        $rawData = [];
288
289
        $rawData['collectionCount'] = $this->collectionHandler->count($this->getConfig()->getCollection(), false);
290
        $rawData['collectionInfo'] = $this->collectionHandler->get($this->getConfig()->getCollection());
291
292
        try {
293
            $adminHandler = new AdminHandler($this->instance);
294
            $rawData['adminInfo'] = $adminHandler->getServerVersion(true);
295
            $infoText = \sprintf(
296
                '%s server v%s "%s" edition (%s/%s).',
297
                \ucfirst($rawData['adminInfo']['server']),
298
                $rawData['adminInfo']['version'] ?? 'unknown version',
299
                $rawData['adminInfo']['license'] ?? 'unknown licence',
300
                $rawData['adminInfo']['details']['architecture'] ?? 'unknown architecture',
301
                $rawData['adminInfo']['details']['platform'] ?? 'unknown platform',
302
            );
303
        } catch (ArangoException $e) {
304
            $infoText = 'No readable human data, encountered an error while trying to get details: ' . $e->getMessage();
305
        }
306
307
        return (new DriverStatistic())
308
            ->setData(implode(', ', array_keys($this->itemInstances)))
309
            ->setInfo($infoText)
310
            ->setRawData($rawData)
311
            ->setSize($rawData['collectionCount']);
312
    }
313
314
    public function getConfig(): Config
315
    {
316
        return $this->config;
317
    }
318
}
319