Issues (36)

lib/Doctrine/Common/Cache/ExtMongoDBCache.php (4 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Common\Cache;
6
7
use DateTime;
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 78
    public function __construct(Collection $collection)
45
    {
46
        // Ensure there is no typemap set - we want to use our own
47 78
        $this->collection = $collection->withOptions(['typeMap' => null]);
48 78
        $this->database   = new Database($collection->getManager(), $collection->getDatabaseName());
49 78
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 76
    protected function doFetch($id)
55
    {
56 76
        $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]);
57
58 76
        if ($document === null) {
59 76
            return false;
60
        }
61
62 65
        if ($this->isExpired($document)) {
0 ignored issues
show
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
66
            return false;
67
        }
68
69 65
        return unserialize($document[MongoDBCache::DATA_FIELD]->getData());
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 73
    protected function doContains($id)
76
    {
77 73
        $document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]);
78
79 73
        if ($document === null) {
80 52
            return false;
81
        }
82
83 69
        if ($this->isExpired($document)) {
0 ignored issues
show
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

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