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
Bug
introduced
by
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
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
|
|||||
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
|
|||||
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 |