Passed
Pull Request — master (#856)
by
unknown
10:26
created

Driver::decode()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 22
nc 5
nop 1
dl 0
loc 36
rs 9.568
c 1
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
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\Event;
34
use Phpfastcache\Event\EventReferenceParameter;
35
use Phpfastcache\Exceptions\PhpfastcacheDriverConnectException;
36
use Phpfastcache\Exceptions\PhpfastcacheDriverException;
37
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
38
39
/**
40
 * Class Driver
41
 * @property Config $config
42
 * @property ArangoConnection $instance
43
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
44
 */
45
class Driver implements ExtendedCacheItemPoolInterface, AggregatablePoolInterface
46
{
47
    use TaggableCacheItemPoolTrait;
48
49
    protected const TTL_FIELD_NAME = 't';
50
51
    protected ArangoDocumentHandler $documentHandler;
52
    protected ArangoCollectionHandler $collectionHandler;
53
54
    /**
55
     * @return bool
56
     */
57
    public function driverCheck(): bool
58
    {
59
        return \class_exists(ArangoConnection::class);
60
    }
61
62
    /**
63
     * @return bool
64
     * @throws ArangoException
65
     * @throws PhpfastcacheDriverConnectException
66
     */
67
    protected function driverConnect(): bool
68
    {
69
        $connectionOptions = [
70
            ArangoConnectionOptions::OPTION_DATABASE => $this->getConfig()->getDatabase(),
71
            ArangoConnectionOptions::OPTION_ENDPOINT => $this->getConfig()->getEndpoint(),
72
73
            ArangoConnectionOptions::OPTION_CONNECTION  => $this->getConfig()->getConnection(),
74
            ArangoConnectionOptions::OPTION_AUTH_TYPE   => $this->getConfig()->getAuthType(),
75
            ArangoConnectionOptions::OPTION_AUTH_USER   => $this->getConfig()->getAuthUser(),
76
            ArangoConnectionOptions::OPTION_AUTH_PASSWD => $this->getConfig()->getAuthPasswd(),
77
78
            ArangoConnectionOptions::OPTION_CONNECT_TIMEOUT => $this->getConfig()->getConnectTimeout(),
79
            ArangoConnectionOptions::OPTION_REQUEST_TIMEOUT => $this->getConfig()->getRequestTimeout(),
80
            ArangoConnectionOptions::OPTION_CREATE        => $this->getConfig()->isAutoCreate(),
81
            ArangoConnectionOptions::OPTION_UPDATE_POLICY => $this->getConfig()->getUpdatePolicy(),
82
83
            // Options below are not yet supported
84
            // ConnectionOptions::OPTION_MEMCACHED_PERSISTENT_ID => 'arangodb-php-pool',
85
            // ConnectionOptions::OPTION_MEMCACHED_SERVERS       => [ [ '127.0.0.1', 11211 ] ],
86
            // ConnectionOptions::OPTION_MEMCACHED_OPTIONS       => [ ],
87
            // ConnectionOptions::OPTION_MEMCACHED_ENDPOINTS_KEY => 'arangodb-php-endpoints'
88
            // ConnectionOptions::OPTION_MEMCACHED_TTL           => 600
89
        ];
90
91
        if ($this->getConfig()->getTraceFunction() !== null) {
92
            $connectionOptions[ArangoConnectionOptions::OPTION_TRACE] = $this->getConfig()->getTraceFunction();
93
        }
94
95
        if ($this->getConfig()->getAuthJwt() !== null) {
96
            $connectionOptions[ArangoConnectionOptions::OPTION_AUTH_JWT] = $this->getConfig()->getAuthJwt();
97
        }
98
99
        if (\str_starts_with($this->getConfig()->getAuthType(), 'ssl://')) {
100
            $connectionOptions[ArangoConnectionOptions::OPTION_VERIFY_CERT] = $this->getConfig()->isVerifyCert();
101
            $connectionOptions[ArangoConnectionOptions::OPTION_ALLOW_SELF_SIGNED] = $this->getConfig()->isSelfSigned();
102
            $connectionOptions[ArangoConnectionOptions::OPTION_CIPHERS] = $this->getConfig()->getCiphers();
103
        }
104
105
        $this->eventManager->dispatch(Event::ARANGODB_CONNECTION, $this, new EventReferenceParameter($connectionOptions));
106
107
        $this->instance = new ArangoConnection($connectionOptions);
108
        $this->documentHandler = new ArangoDocumentHandler($this->instance);
109
        $this->collectionHandler = new ArangoCollectionHandler($this->instance);
110
111
        $collectionNames = array_keys($this->collectionHandler->getAllCollections());
112
113
        if ($this->getConfig()->isAutoCreate() && !\in_array($this->getConfig()->getCollection(), $collectionNames, true)) {
114
            return $this->createCollection($this->getConfig()->getCollection());
115
        }
116
117
        return $this->collectionHandler->has($this->getConfig()->getCollection());
118
    }
119
120
    /**
121
     * @param ExtendedCacheItemInterface $item
122
     * @return null|array
123
     * @throws PhpfastcacheDriverException
124
     * @throws \Exception
125
     */
126
    protected function driverRead(ExtendedCacheItemInterface $item): ?array
127
    {
128
        try {
129
            $document = $this->documentHandler->get($this->getConfig()->getCollection(), $item->getEncodedKey());
130
        } catch (ArangoServerException $e) {
131
            if ($e->getCode() === 404) {
132
                return null;
133
            }
134
            throw new PhpfastcacheDriverException(
135
                'Got unexpeced error from Arangodb: ' . $e->getMessage()
136
            );
137
        }
138
139
        return $this->decodeDocument($document);
140
    }
141
142
    /**
143
     * @param ExtendedCacheItemInterface $item
144
     * @return bool
145
     * @throws ArangoException
146
     * @throws PhpfastcacheLogicException
147
     */
148
    protected function driverWrite(ExtendedCacheItemInterface $item): bool
149
    {
150
        $options = [
151
            'overwriteMode' => 'replace',
152
            'returnNew' => true,
153
            'returnOld' => false,
154
            'silent' => false,
155
        ];
156
157
        $document = new ArangoDocument();
158
        $document->setInternalKey($item->getEncodedKey());
159
        $document->set(self::DRIVER_KEY_WRAPPER_INDEX, $item->getKey());
160
        $document->set(self::DRIVER_DATA_WRAPPER_INDEX, $this->encode($item->getRawValue()));
161
        $document->set(self::DRIVER_TAGS_WRAPPER_INDEX, $item->getTags());
162
        $document->set(self::DRIVER_EDATE_WRAPPER_INDEX, $item->getExpirationDate());
163
        $document->set(self::TTL_FIELD_NAME, $item->getExpirationDate()->getTimestamp());
164
165
        if ($this->getConfig()->isItemDetailedDate()) {
166
            $document->set(self::DRIVER_CDATE_WRAPPER_INDEX, $item->getCreationDate());
167
            $document->set(self::DRIVER_MDATE_WRAPPER_INDEX, $item->getModificationDate());
168
        }
169
170
        return $this->documentHandler->insert($this->getConfig()->getCollection(), $document, $options) !== null;
171
    }
172
173
    /**
174
     * @param ExtendedCacheItemInterface $item
175
     * @return bool
176
     */
177
    protected function driverDelete(ExtendedCacheItemInterface $item): bool
178
    {
179
        $options = [
180
            'returnOld' => false
181
        ];
182
183
        try {
184
            $this->documentHandler->removeById($this->getConfig()->getCollection(), $item->getEncodedKey(), null, $options);
185
            return true;
186
        } catch (ArangoException) {
187
            return false;
188
        }
189
    }
190
191
    /**
192
     * @return bool
193
     */
194
    protected function driverClear(): bool
195
    {
196
        try {
197
            $this->collectionHandler->truncate($this->getConfig()->getCollection());
198
            return true;
199
        } catch (ArangoException) {
200
            return false;
201
        }
202
    }
203
204
    /**
205
     * @throws PhpfastcacheDriverConnectException
206
     * @throws ArangoException
207
     */
208
    protected function createCollection($collectionName): bool
209
    {
210
        $collection = new ArangoCollection($collectionName);
211
212
        try {
213
            $params = [
214
                'type' => ArangoCollection::TYPE_DOCUMENT,
215
                'waitForSync' => false
216
            ];
217
218
            $this->eventManager->dispatch(Event::ARANGODB_COLLECTION_PARAMS, $this, new EventReferenceParameter($params));
219
220
            $this->collectionHandler->create($collection, $params);
221
222
            $this->collectionHandler->createIndex($collection, [
223
                'type'         => 'ttl',
224
                'name'         => 'expires_at',
225
                'fields'       => [self::TTL_FIELD_NAME],
226
                'unique'       => false,
227
                'sparse'       => true,
228
                'inBackground' => true,
229
                'expireAfter' => 1
230
            ]);
231
            return true;
232
        } catch (\Throwable $e) {
233
            throw new PhpfastcacheDriverConnectException(
234
                sprintf(
235
                    'Unable to automatically create the collection, error returned from ArangoDB: [%d] %s',
236
                    $e->getCode(),
237
                    $e->getMessage(),
238
                )
239
            );
240
        }
241
    }
242
243
    /**
244
     * @param ArangoDocument $document
245
     * @return array
246
     * @throws \Exception
247
     */
248
    protected function decodeDocument(ArangoDocument $document): array
249
    {
250
        $value = [
251
            self::DRIVER_KEY_WRAPPER_INDEX => $document->get(self::DRIVER_KEY_WRAPPER_INDEX),
252
            self::DRIVER_TAGS_WRAPPER_INDEX => $document->get(self::DRIVER_TAGS_WRAPPER_INDEX),
253
            self::DRIVER_DATA_WRAPPER_INDEX => $this->decode(
254
                $document->get(self::DRIVER_DATA_WRAPPER_INDEX),
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