Passed
Pull Request — master (#857)
by Georges
04:15 queued 02:15
created

Driver::driverCheck()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
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
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Drivers\Arangodb;
18
19
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...
20
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...
21
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...
22
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...
23
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...
24
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...
25
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...
26
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...
27
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...
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
 * @method Config getConfig()
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(Event::ARANGODB_CONNECTION, $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->decodeDocument($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(Event::ARANGODB_COLLECTION_PARAMS, $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 array
245
     * @throws \Exception
246
     */
247
    protected function decodeDocument(ArangoDocument $document): array
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 => $this->decode(
253
                $document->get(self::DRIVER_DATA_WRAPPER_INDEX),
254
            ),
255
        ];
256
257
        $eDate = $document->get(self::DRIVER_EDATE_WRAPPER_INDEX);
258
        $value[ExtendedCacheItemPoolInterface::DRIVER_EDATE_WRAPPER_INDEX] = new \DateTime(
259
            $eDate['date'],
260
            new \DateTimeZone($eDate['timezone'])
261
        );
262
263
        if ($this->getConfig()->isItemDetailedDate()) {
264
            $cDate = $document->get(self::DRIVER_CDATE_WRAPPER_INDEX);
265
            if (isset($cDate['date'], $cDate['timezone'])) {
266
                $value[ExtendedCacheItemPoolInterface::DRIVER_CDATE_WRAPPER_INDEX] = new \DateTime(
267
                    $cDate['date'],
268
                    new \DateTimeZone($cDate['timezone'])
269
                );
270
            }
271
272
            $mDate = $document->get(self::DRIVER_MDATE_WRAPPER_INDEX);
273
            if (isset($mDate['date'], $cDate['timezone'])) {
274
                $value[ExtendedCacheItemPoolInterface::DRIVER_MDATE_WRAPPER_INDEX] = new \DateTime(
275
                    $mDate['date'],
276
                    new \DateTimeZone($mDate['timezone'])
277
                );
278
            }
279
        }
280
281
        return $value;
282
    }
283
284
    public function getStats(): DriverStatistic
285
    {
286
        $rawData = [];
287
288
        $rawData['collectionCount'] = $this->collectionHandler->count($this->getConfig()->getCollection(), false);
289
        $rawData['collectionInfo'] = $this->collectionHandler->get($this->getConfig()->getCollection());
290
291
        try {
292
            $adminHandler = new AdminHandler($this->instance);
293
            $rawData['adminInfo'] = $adminHandler->getServerVersion(true);
294
            $infoText = \sprintf(
295
                '%s server v%s "%s" edition (%s/%s).',
296
                \ucfirst($rawData['adminInfo']['server']),
297
                $rawData['adminInfo']['version'] ?? 'unknown version',
298
                $rawData['adminInfo']['license'] ?? 'unknown licence',
299
                $rawData['adminInfo']['details']['architecture'] ?? 'unknown architecture',
300
                $rawData['adminInfo']['details']['platform'] ?? 'unknown platform',
301
            );
302
        } catch (ArangoException $e) {
303
            $infoText = 'No readable human data, encountered an error while trying to get details: ' . $e->getMessage();
304
        }
305
306
        return (new DriverStatistic())
307
            ->setData(implode(', ', array_keys($this->itemInstances)))
308
            ->setInfo($infoText)
309
            ->setRawData($rawData)
310
            ->setSize($rawData['collectionCount']);
311
    }
312
}
313