Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 39 and 311]
1 <?php 2 /* 3 * Copyright 2016-2017 MongoDB, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 namespace MongoDB\GridFS; 19 20 use ArrayIterator; 21 use MongoDB\Collection; 22 use MongoDB\Driver\Cursor; 23 use MongoDB\Driver\Manager; 24 use MongoDB\Driver\ReadPreference; 25 use MongoDB\Exception\InvalidArgumentException; 26 use MongoDB\UpdateResult; 27 use MultipleIterator; 28 use stdClass; 29 use function abs; 30 use function count; 31 use function is_numeric; 32 use function sprintf; 33 34 /** 35 * CollectionWrapper abstracts the GridFS files and chunks collections. 36 * 37 * @internal 38 */ 39 class CollectionWrapper 40 { 41 /** @var string */ 42 private $bucketName; 43 44 /** @var Collection */ 45 private $chunksCollection; 46 47 /** @var string */ 48 private $databaseName; 49 50 /** @var boolean */ 51 private $checkedIndexes = false; 52 53 /** @var Collection */ 54 private $filesCollection; 55 56 /** 57 * Constructs a GridFS collection wrapper. 58 * 59 * @see Collection::__construct() for supported options 60 * @param Manager $manager Manager instance from the driver 61 * @param string $databaseName Database name 62 * @param string $bucketName Bucket name 63 * @param array $collectionOptions Collection options 64 * @throws InvalidArgumentException 65 */ 66 public function __construct(Manager $manager, $databaseName, $bucketName, array $collectionOptions = []) 67 { 68 $this->databaseName = (string) $databaseName; 69 $this->bucketName = (string) $bucketName; 70 71 $this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions); 72 $this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions); 73 } 74 75 /** 76 * Deletes all GridFS chunks for a given file ID. 77 * 78 * @param mixed $id 79 */ 80 public function deleteChunksByFilesId($id) 81 { 82 $this->chunksCollection->deleteMany(['files_id' => $id]); 83 } 84 85 /** 86 * Deletes a GridFS file and related chunks by ID. 87 * 88 * @param mixed $id 89 */ 90 public function deleteFileAndChunksById($id) 91 { 92 $this->filesCollection->deleteOne(['_id' => $id]); 93 $this->chunksCollection->deleteMany(['files_id' => $id]); 94 } 95 96 /** 97 * Drops the GridFS files and chunks collections. 98 */ 99 public function dropCollections() 100 { 101 $this->filesCollection->drop(['typeMap' => []]); 102 $this->chunksCollection->drop(['typeMap' => []]); 103 } 104 105 /** 106 * Finds GridFS chunk documents for a given file ID and optional offset. 107 * 108 * @param mixed $id File ID 109 * @param integer $fromChunk Starting chunk (inclusive) 110 * @return Cursor 111 */ 112 public function findChunksByFileId($id, $fromChunk = 0) 113 { 114 return $this->chunksCollection->find( 115 [ 116 'files_id' => $id, 117 'n' => ['$gte' => $fromChunk], 118 ], 119 [ 120 'sort' => ['n' => 1], 121 'typeMap' => ['root' => 'stdClass'], 122 ] 123 ); 124 } 125 126 /** 127 * Finds a GridFS file document for a given filename and revision. 128 * 129 * Revision numbers are defined as follows: 130 * 131 * * 0 = the original stored file 132 * * 1 = the first revision 133 * * 2 = the second revision 134 * * etc… 135 * * -2 = the second most recent revision 136 * * -1 = the most recent revision 137 * 138 * @see Bucket::downloadToStreamByName() 139 * @see Bucket::openDownloadStreamByName() 140 * @param string $filename 141 * @param integer $revision 142 * @return stdClass|null 143 */ 144 public function findFileByFilenameAndRevision($filename, $revision) 145 { 146 $filename = (string) $filename; 147 $revision = (integer) $revision; 148 149 if ($revision < 0) { 150 $skip = abs($revision) - 1; 151 $sortOrder = -1; 152 } else { 153 $skip = $revision; 154 $sortOrder = 1; 155 } 156 157 return $this->filesCollection->findOne( 158 ['filename' => $filename], 159 [ 160 'skip' => $skip, 161 'sort' => ['uploadDate' => $sortOrder], 162 'typeMap' => ['root' => 'stdClass'], 163 ] 164 ); 165 } 166 167 /** 168 * Finds a GridFS file document for a given ID. 169 * 170 * @param mixed $id 171 * @return stdClass|null 172 */ 173 public function findFileById($id) 174 { 175 return $this->filesCollection->findOne( 176 ['_id' => $id], 177 ['typeMap' => ['root' => 'stdClass']] 178 ); 179 } 180 181 /** 182 * Finds documents from the GridFS bucket's files collection. 183 * 184 * @see Find::__construct() for supported options 185 * @param array|object $filter Query by which to filter documents 186 * @param array $options Additional options 187 * @return Cursor 188 */ 189 public function findFiles($filter, array $options = []) 190 { 191 return $this->filesCollection->find($filter, $options); 192 } 193 194 /** 195 * Finds a single document from the GridFS bucket's files collection. 196 * 197 * @param array|object $filter Query by which to filter documents 198 * @param array $options Additional options 199 * @return array|object|null 200 */ 201 public function findOneFile($filter, array $options = []) 202 { 203 return $this->filesCollection->findOne($filter, $options); 204 } 205 206 /** 207 * Return the bucket name. 208 * 209 * @return string 210 */ 211 public function getBucketName() 212 { 213 return $this->bucketName; 214 } 215 216 /** 217 * Return the chunks collection. 218 * 219 * @return Collection 220 */ 221 public function getChunksCollection() 222 { 223 return $this->chunksCollection; 224 } 225 226 /** 227 * Return the database name. 228 * 229 * @return string 230 */ 231 public function getDatabaseName() 232 { 233 return $this->databaseName; 234 } 235 236 /** 237 * Return the files collection. 238 * 239 * @return Collection 240 */ 241 public function getFilesCollection() 242 { 243 return $this->filesCollection; 244 } 245 246 /** 247 * Inserts a document into the chunks collection. 248 * 249 * @param array|object $chunk Chunk document 250 */ 251 public function insertChunk($chunk) 252 { 253 if (! $this->checkedIndexes) { 254 $this->ensureIndexes(); 255 } 256 257 $this->chunksCollection->insertOne($chunk); 258 } 259 260 /** 261 * Inserts a document into the files collection. 262 * 263 * The file document should be inserted after all chunks have been inserted. 264 * 265 * @param array|object $file File document 266 */ 267 public function insertFile($file) 268 { 269 if (! $this->checkedIndexes) { 270 $this->ensureIndexes(); 271 } 272 273 $this->filesCollection->insertOne($file); 274 } 275 276 /** 277 * Updates the filename field in the file document for a given ID. 278 * 279 * @param mixed $id 280 * @param string $filename 281 * @return UpdateResult 282 */ 283 public function updateFilenameForId($id, $filename) 284 { 285 return $this->filesCollection->updateOne( 286 ['_id' => $id], 287 ['$set' => ['filename' => (string) $filename]] 288 ); 289 } 290 291 /** 292 * Create an index on the chunks collection if it does not already exist. 293 */ 294 private function ensureChunksIndex() 295 { 296 $expectedIndex = ['files_id' => 1, 'n' => 1]; 297 298 foreach ($this->chunksCollection->listIndexes() as $index) { 299 if ($index->isUnique() && $this->indexKeysMatch($expectedIndex, $index->getKey())) { 300 return; 301 } 302 } 303 304 $this->chunksCollection->createIndex($expectedIndex, ['unique' => true]); 305 } 306 307 /** 308 * Create an index on the files collection if it does not already exist. 309 */ 310 private function ensureFilesIndex() 311 { 312 $expectedIndex = ['filename' => 1, 'uploadDate' => 1]; 313 314 foreach ($this->filesCollection->listIndexes() as $index) { 315 if ($this->indexKeysMatch($expectedIndex, $index->getKey())) { 316 return; 317 } 318 } 319 320 $this->filesCollection->createIndex($expectedIndex); 321 } 322 323 /** 324 * Ensure indexes on the files and chunks collections exist. 325 * 326 * This method is called once before the first write operation on a GridFS 327 * bucket. Indexes are only be created if the files collection is empty. 328 */ 329 private function ensureIndexes() 330 { 331 if ($this->checkedIndexes) { 332 return; 333 } 334 335 $this->checkedIndexes = true; 336 337 if (! $this->isFilesCollectionEmpty()) { 338 return; 339 } 340 341 $this->ensureFilesIndex(); 342 $this->ensureChunksIndex(); 343 } 344 345 private function indexKeysMatch(array $expectedKeys, array $actualKeys) : bool 346 { 347 if (count($expectedKeys) !== count($actualKeys)) { 348 return false; 349 } 350 351 $iterator = new MultipleIterator(MultipleIterator::MIT_NEED_ANY); 352 $iterator->attachIterator(new ArrayIterator($expectedKeys)); 353 $iterator->attachIterator(new ArrayIterator($actualKeys)); 354 355 foreach ($iterator as $key => $value) { 356 list($expectedKey, $actualKey) = $key; 357 list($expectedValue, $actualValue) = $value; 358 359 if ($expectedKey !== $actualKey) { 360 return false; 361 } 362 363 /* Since we don't expect special indexes (e.g. text), we mark any 364 * index with a non-numeric definition as unequal. All others are 365 * compared against their int value to avoid differences due to 366 * some drivers using float values in the key specification. */ 367 if (! is_numeric($actualValue) || (int) $expectedValue !== (int) $actualValue) { 368 return false; 369 } 370 } 371 372 return true; 373 } 374 375 /** 376 * Returns whether the files collection is empty. 377 * 378 * @return boolean 379 */ 380 private function isFilesCollectionEmpty() 381 { 382 return null === $this->filesCollection->findOne([], [ 383 'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY), 384 'projection' => ['_id' => 1], 385 'typeMap' => [], 386 ]); 387 } 388 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body