Passed
Push — master ( b2988e...9f380c )
by Jonathan
33:05
created

ExtMongoDBCache::doGetStats()   B

Complexity

Conditions 3
Paths 9

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.054

Importance

Changes 0
Metric Value
cc 3
eloc 22
nc 9
nop 0
dl 0
loc 29
ccs 9
cts 11
cp 0.8182
crap 3.054
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
6
namespace Doctrine\Common\Cache;
7
8
use MongoDB\BSON\Binary;
9
use MongoDB\BSON\UTCDateTime;
10
use MongoDB\Collection;
11
use MongoDB\Database;
12
use MongoDB\Driver\Exception\Exception;
13
use MongoDB\Model\BSONDocument;
14
use function serialize;
15
use function time;
16
use function unserialize;
17
18
/**
19
 * MongoDB cache provider for ext-mongodb
20
 *
21
 * @internal Do not use - will be removed in 2.0. Use MongoDBCache instead
22
 */
23
class ExtMongoDBCache extends CacheProvider
24
{
25
    /** @var Database */
26
    private $database;
27
28
    /** @var Collection */
29
    private $collection;
30
31
    /** @var bool */
32
    private $expirationIndexCreated = false;
33
34
    /**
35
     * This provider will default to the write concern and read preference
36
     * options set on the Database instance (or inherited from MongoDB or
37
     * Client). Using an unacknowledged write concern (< 1) may make the return
38
     * values of delete() and save() unreliable. Reading from secondaries may
39
     * make contain() and fetch() unreliable.
40
     *
41
     * @see http://www.php.net/manual/en/mongo.readpreferences.php
42
     * @see http://www.php.net/manual/en/mongo.writeconcerns.php
43
     */
44
    public function __construct(Collection $collection)
45
    {
46
        // Ensure there is no typemap set - we want to use our own
47
        $this->collection = $collection->withOptions(['typeMap' => null]);
48
        $this->database   = new Database($collection->getManager(), $collection->getDatabaseName());
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    protected function doFetch($id)
55
    {
56
        $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]);
57
58
        if ($document === null) {
59
            return false;
60
        }
61
62
        if ($this->isExpired($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type array; however, parameter $document of Doctrine\Common\Cache\ExtMongoDBCache::isExpired() does only seem to accept MongoDB\Model\BSONDocument, maybe add an additional type check? ( Ignorable by Annotation )

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

62
        if ($this->isExpired(/** @scrutinizer ignore-type */ $document)) {
Loading history...
63
            $this->createExpirationIndex();
64
            $this->doDelete($id);
65
            return false;
66
        }
67 78
68
        return unserialize($document[MongoDBCache::DATA_FIELD]->getData());
69
    }
70 78
71 78
    /**
72 78
     * {@inheritdoc}
73
     */
74
    protected function doContains($id)
75
    {
76
        $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]);
77 76
78
        if ($document === null) {
79 76
            return false;
80
        }
81 76
82 76
        if ($this->isExpired($document)) {
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type array; however, parameter $document of Doctrine\Common\Cache\ExtMongoDBCache::isExpired() does only seem to accept MongoDB\Model\BSONDocument, maybe add an additional type check? ( Ignorable by Annotation )

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

82
        if ($this->isExpired(/** @scrutinizer ignore-type */ $document)) {
Loading history...
83
            $this->createExpirationIndex();
84
            $this->doDelete($id);
85 65
            return false;
86
        }
87
88
        return true;
89
    }
90
91 65
    /**
92
     * {@inheritdoc}
93
     */
94
    protected function doSave($id, $data, $lifeTime = 0)
95
    {
96
        try {
97 73
            $this->collection->updateOne(
98
                ['_id' => $id],
99 73
                [
100
                    '$set' => [
101 73
                        MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new UTCDateTime((time() + $lifeTime) * 1000): null),
102 52
                        MongoDBCache::DATA_FIELD => new Binary(serialize($data), Binary::TYPE_GENERIC),
103
                    ],
104
                ],
105 69
                ['upsert' => true]
106 1
            );
107 1
        } catch (Exception $e) {
108 1
            return false;
109
        }
110
111 69
        return true;
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 74
    protected function doDelete($id)
118
    {
119
        try {
120 74
            $this->collection->deleteOne(['_id' => $id]);
121 74
        } catch (Exception $e) {
122 74
            return false;
123 74
        }
124 74
125
        return true;
126 74
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    protected function doFlush()
132 74
    {
133
        try {
134
            // Use remove() in lieu of drop() to maintain any collection indexes
135
            $this->collection->deleteMany([]);
136
        } catch (Exception $e) {
137
            return false;
138 46
        }
139
140
        return true;
141 46
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 46
    protected function doGetStats()
147
    {
148
        $uptime      = null;
149
        $memoryUsage = null;
150
151
        try {
152 2
            $serverStatus = $this->database->command([
153
                'serverStatus' => 1,
154
                'locks' => 0,
155
                'metrics' => 0,
156 2
                'recordStats' => 0,
157
                'repl' => 0,
158
            ])->toArray()[0];
159
            $uptime       = $serverStatus['uptime'] ?? null;
160
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
161 2
        }
162
163
        try {
164
            $collStats   = $this->database->command(['collStats' => $this->collection->getCollectionName()])->toArray()[0];
165
            $memoryUsage = $collStats['size'] ?? null;
166
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
167 1
        }
168
169 1
        return [
170 1
            Cache::STATS_HITS => null,
171
            Cache::STATS_MISSES => null,
172
            Cache::STATS_UPTIME => $uptime,
173 1
            Cache::STATS_MEMORY_USAGE => $memoryUsage,
174 1
            Cache::STATS_MEMORY_AVAILABLE  => null,
175
        ];
176
    }
177
178
    /**
179 1
     * Check if the document is expired.
180 1
     */
181
    private function isExpired(BSONDocument $document) : bool
182
    {
183
        return isset($document[MongoDBCache::EXPIRATION_FIELD]) &&
184
            $document[MongoDBCache::EXPIRATION_FIELD] instanceof UTCDateTime &&
185 1
            $document[MongoDBCache::EXPIRATION_FIELD]->toDateTime() < new \DateTime();
186
    }
187 1
188
    private function createExpirationIndex() : void
189
    {
190
        if ($this->expirationIndexCreated) {
191 1
            return;
192
        }
193 1
194 1
        $this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]);
195
    }
196
}
197