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