Passed
Push — v7 ( 4b7cde...9c98f2 )
by Georges
02:08
created

Driver::buildConnectionURI()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 1
nop 1
dl 0
loc 15
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 *
4
 * This file is part of phpFastCache.
5
 *
6
 * @license MIT License (MIT)
7
 *
8
 * For full copyright and license information, please see the docs/CREDITS.txt file.
9
 *
10
 * @author Khoa Bui (khoaofgod)  <[email protected]> http://www.phpfastcache.com
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Fabio Covolo Mazzo (fabiocmazzo) <[email protected]>
13
 *
14
 */
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Drivers\Mongodb;
18
19
use LogicException;
20
use MongoDB\{
21
  Client, Database, BSON\Binary, BSON\UTCDateTime, Collection, DeleteResult, Driver\Command, Driver\Exception\Exception as MongoDBException
0 ignored issues
show
Bug introduced by
The type MongoDB\Client 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...
Bug introduced by
The type MongoDB\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...
Bug introduced by
The type MongoDB\Database 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...
Bug introduced by
The type MongoDB\DeleteResult 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
};
23
use Phpfastcache\Core\Pool\{
24
  DriverBaseTrait, ExtendedCacheItemPoolInterface
25
};
26
use Phpfastcache\Entities\DriverStatistic;
27
use Phpfastcache\Exceptions\{
28
  PhpfastcacheDriverException, PhpfastcacheInvalidArgumentException
29
};
30
31
use Psr\Cache\CacheItemInterface;
32
33
/**
34
 * Class Driver
35
 * @package phpFastCache\Drivers
36
 * @property Client $instance Instance of driver service
37
 * @property Config $config Config object
38
 * @method Config getConfig() Return the config object
39
 */
40
class Driver implements ExtendedCacheItemPoolInterface
41
{
42
    const MONGODB_DEFAULT_DB_NAME = 'phpfastcache';
43
44
    use DriverBaseTrait;
45
46
    /**
47
     * @var Collection
48
     */
49
    public $collection;
50
51
    /**
52
     * @var Database
53
     */
54
    public $database;
55
56
    /**
57
     * @return bool
58
     */
59
    public function driverCheck(): bool
60
    {
61
        $mongoExtensionExists = class_exists(\MongoDB\Driver\Manager::class);
62
63
        if (!$mongoExtensionExists && class_exists(\MongoClient::class)) {
64
            trigger_error('This driver is used to support the pecl MongoDb extension with mongo-php-library.
65
            For MongoDb with Mongo PECL support use Mongo Driver.', E_USER_ERROR);
66
        }
67
68
        return $mongoExtensionExists && class_exists(\MongoDB\Collection::class);
69
    }
70
71
    /**
72
     * @param \Psr\Cache\CacheItemInterface $item
73
     * @return null|array
74
     */
75
    protected function driverRead(CacheItemInterface $item)
76
    {
77
        $document = $this->getCollection()->findOne(['_id' => $item->getEncodedKey()]);
78
79
        if ($document) {
80
            $return = [
81
              self::DRIVER_DATA_WRAPPER_INDEX => $this->decode($document[ self::DRIVER_DATA_WRAPPER_INDEX ]->getData()),
82
              self::DRIVER_TAGS_WRAPPER_INDEX => $this->decode($document[ self::DRIVER_TAGS_WRAPPER_INDEX ]->getData()),
83
              self::DRIVER_EDATE_WRAPPER_INDEX => (new \DateTime())->setTimestamp($document[ self::DRIVER_EDATE_WRAPPER_INDEX ]->toDateTime()->getTimestamp()),
84
            ];
85
86
            if(!empty($this->getConfigOption('itemDetailedDate'))){
87
                $return += [
88
                  self::DRIVER_MDATE_WRAPPER_INDEX => (new \DateTime())->setTimestamp($document[ self::DRIVER_MDATE_WRAPPER_INDEX ]->toDateTime()
89
                    ->getTimestamp()),
90
                  self::DRIVER_CDATE_WRAPPER_INDEX => (new \DateTime())->setTimestamp($document[ self::DRIVER_CDATE_WRAPPER_INDEX ]->toDateTime()
91
                    ->getTimestamp()),
92
                ];
93
            }
94
95
            return $return;
96
        }
97
98
        return null;
99
    }
100
101
    /**
102
     * @param \Psr\Cache\CacheItemInterface $item
103
     * @return mixed
104
     * @throws PhpfastcacheInvalidArgumentException
105
     * @throws PhpfastcacheDriverException
106
     */
107
    protected function driverWrite(CacheItemInterface $item): bool
108
    {
109
        /**
110
         * Check for Cross-Driver type confusion
111
         */
112
        if ($item instanceof Item) {
113
            try {
114
                $set = [
115
                  self::DRIVER_DATA_WRAPPER_INDEX => new Binary($this->encode($item->get()), Binary::TYPE_GENERIC),
116
                  self::DRIVER_TAGS_WRAPPER_INDEX => new Binary($this->encode($item->getTags()), Binary::TYPE_GENERIC),
117
                  self::DRIVER_EDATE_WRAPPER_INDEX => ($item->getTtl() > 0 ? new UTCDateTime((\time() + $item->getTtl()) * 1000) : new UTCDateTime(\time() * 1000)),
118
                ];
119
120
                if(!empty($this->getConfigOption('itemDetailedDate'))){
121
                    $set += [
122
                      self::DRIVER_MDATE_WRAPPER_INDEX => ($item->getModificationDate() ? new UTCDateTime(($item->getModificationDate()->getTimestamp()) * 1000) : new UTCDateTime(\time() * 1000)),
123
                      self::DRIVER_CDATE_WRAPPER_INDEX => ($item->getCreationDate() ? new UTCDateTime(($item->getCreationDate()->getTimestamp()) * 1000) : new UTCDateTime(\time() * 1000)),
124
                    ];
125
                }
126
                $result = (array)$this->getCollection()->updateOne(
127
                  ['_id' => $item->getEncodedKey()],
128
                  [
129
                    '$set' => $set,
130
                  ],
131
                  ['upsert' => true, 'multiple' => false]
132
                );
133
            } catch (MongoDBException $e) {
134
                throw new PhpfastcacheDriverException('Got an exception while trying to write data to MongoDB server', null, $e);
135
            }
136
137
            return isset($result[ 'ok' ]) ? $result[ 'ok' ] == 1 : true;
138
        }
139
140
        throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
141
    }
142
143
    /**
144
     * @param \Psr\Cache\CacheItemInterface $item
145
     * @return bool
146
     * @throws PhpfastcacheInvalidArgumentException
147
     */
148
    protected function driverDelete(CacheItemInterface $item): bool
149
    {
150
        /**
151
         * Check for Cross-Driver type confusion
152
         */
153
        if ($item instanceof Item) {
154
            /**
155
             * @var DeleteResult $deletionResult
156
             */
157
            $deletionResult = $this->getCollection()->deleteOne(['_id' => $item->getEncodedKey()]);
158
159
            return $deletionResult->isAcknowledged();
160
        }
161
162
        throw new PhpfastcacheInvalidArgumentException('Cross-Driver type confusion detected');
163
    }
164
165
    /**
166
     * @return bool
167
     */
168
    protected function driverClear(): bool
169
    {
170
        return $this->collection->deleteMany([])->isAcknowledged();
171
    }
172
173
    /**
174
     * @return bool
175
     * @throws MongodbException
176
     * @throws LogicException
177
     */
178
    protected function driverConnect(): bool
179
    {
180
        if ($this->instance instanceof Client) {
181
            throw new LogicException('Already connected to Mongodb server');
182
        }
183
184
        $timeout = $this->getConfigOption('timeout') * 1000;
185
        $collectionName = $this->getConfigOption('collectionName');
186
        $databaseName = $this->getConfigOption('databaseName');
187
188
        $this->instance = $this->instance ?: new Client($this->buildConnectionURI($databaseName), ['connectTimeoutMS' => $timeout]);
189
        $this->database = $this->database ?: $this->instance->selectDatabase($databaseName);
190
191
        if (!$this->collectionExists($collectionName)) {
192
            $this->database->createCollection($collectionName);
193
        }
194
195
        $this->collection = $this->database->selectCollection($collectionName);
196
197
        return true;
198
    }
199
200
    /**
201
     * Checks if a collection name exists on the Mongo database.
202
     *
203
     * @param string $collectionName The collection name to check.
204
     *
205
     * @return bool True if the collection exists, false if not.
206
     */
207
    protected function collectionExists($collectionName): bool
208
    {
209
        foreach ($this->database->listCollections() as $collection) {
210
            if ($collection->getName() === $collectionName) {
211
                return true;
212
            }
213
        }
214
215
        return false;
216
    }
217
218
    /**
219
     * Builds the connection URI from the given parameters.
220
     *
221
     * @param string $databaseName
222
     * @return string The connection URI.
223
     */
224
    protected function buildConnectionURI($databaseName = ''): string
225
    {
226
        $host = $this->getConfigOption('host');
227
        $port = $this->getConfigOption('port');
228
        $username =  $this->getConfigOption('username');
229
        $password = $this->getConfigOption('password');
230
231
        return implode('', [
232
          'mongodb://',
233
          ($username ?: ''),
234
          ($password ? ":{$password}" : ''),
235
          ($username ? '@' : ''),
236
          $host,
237
          ($port !== 27017 ? ":{$port}" : ''),
238
          ($databaseName ? "/{$databaseName}" : '')
239
        ]);
240
    }
241
242
    /**
243
     * @return Collection
244
     */
245
    protected function getCollection(): Collection
246
    {
247
        return $this->collection;
248
    }
249
250
    /********************
251
     *
252
     * PSR-6 Extended Methods
253
     *
254
     *******************/
255
256
    /**
257
     * @return DriverStatistic
258
     */
259
    public function getStats(): DriverStatistic
260
    {
261
        $serverStats = $this->instance->getManager()->executeCommand('phpFastCache', new Command([
262
          'serverStatus' => 1,
263
          'recordStats' => 0,
264
          'repl' => 0,
265
          'metrics' => 0,
266
        ]))->toArray()[ 0 ];
267
268
        $collectionStats = $this->instance->getManager()->executeCommand('phpFastCache', new Command([
269
          'collStats' => (isset($this->config[ 'collectionName' ]) ? $this->config[ 'collectionName' ] : 'Cache'),
270
          'verbose' => true,
271
        ]))->toArray()[ 0 ];
272
273
        $array_filter_recursive = function ($array, callable $callback = null) use (&$array_filter_recursive) {
274
            $array = $callback($array);
275
276
            if (\is_object($array) || \is_array($array)) {
277
                foreach ($array as &$value) {
278
                    $value = \call_user_func($array_filter_recursive, $value, $callback);
279
                }
280
            }
281
282
            return $array;
283
        };
284
285
        $callback = function ($item) {
286
            /**
287
             * Remove unserializable properties
288
             */
289
            if ($item instanceof \MongoDB\BSON\UTCDateTime) {
290
                return (string)$item;
291
            }
292
            return $item;
293
        };
294
295
        $serverStats = $array_filter_recursive($serverStats, $callback);
296
        $collectionStats = $array_filter_recursive($collectionStats, $callback);
297
298
        $stats = (new DriverStatistic())
299
          ->setInfo('MongoDB version ' . $serverStats->version . ', Uptime (in days): ' . \round($serverStats->uptime / 86400,
300
              1) . "\n For more information see RawData.")
301
          ->setSize($collectionStats->size)
302
          ->setData(\implode(', ', \array_keys($this->itemInstances)))
303
          ->setRawData([
304
            'serverStatus' => $serverStats,
305
            'collStats' => $collectionStats,
306
          ]);
307
308
        return $stats;
309
    }
310
}
311