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 HashContext; 21 use MongoDB\BSON\Binary; 22 use MongoDB\BSON\ObjectId; 23 use MongoDB\BSON\UTCDateTime; 24 use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; 25 use MongoDB\Exception\InvalidArgumentException; 26 27 use function array_intersect_key; 28 use function hash_final; 29 use function hash_init; 30 use function hash_update; 31 use function is_array; 32 use function is_bool; 33 use function is_integer; 34 use function is_object; 35 use function is_string; 36 use function MongoDB\is_string_array; 37 use function sprintf; 38 use function strlen; 39 use function substr; 40 41 /** 42 * WritableStream abstracts the process of writing a GridFS file. 43 * 44 * @internal 45 */ 46 class WritableStream 47 { 48 /** @var integer */ 49 private static $defaultChunkSizeBytes = 261120; 50 51 /** @var string */ 52 private $buffer = ''; 53 54 /** @var integer */ 55 private $chunkOffset = 0; 56 57 /** @var integer */ 58 private $chunkSize; 59 60 /** @var boolean */ 61 private $disableMD5; 62 63 /** @var CollectionWrapper */ 64 private $collectionWrapper; 65 66 /** @var array */ 67 private $file; 68 69 /** @var HashContext|null */ 70 private $hashCtx; 71 72 /** @var boolean */ 73 private $isClosed = false; 74 75 /** @var integer */ 76 private $length = 0; 77 78 /** 79 * Constructs a writable GridFS stream. 80 * 81 * Supported options: 82 * 83 * * _id (mixed): File document identifier. Defaults to a new ObjectId. 84 * 85 * * aliases (array of strings): DEPRECATED An array of aliases. 86 * Applications wishing to store aliases should add an aliases field to 87 * the metadata document instead. 88 * 89 * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to 90 * 261120 (i.e. 255 KiB). 91 * 92 * * disableMD5 (boolean): When true, no MD5 sum will be generated. 93 * Defaults to "false". 94 * 95 * * contentType (string): DEPRECATED content type to be stored with the 96 * file. This information should now be added to the metadata. 97 * 98 * * metadata (document): User data for the "metadata" field of the files 99 * collection document. 100 * 101 * @param CollectionWrapper $collectionWrapper GridFS collection wrapper 102 * @param string $filename Filename 103 * @param array $options Upload options 104 * @throws InvalidArgumentException 105 */ 106 public function __construct(CollectionWrapper $collectionWrapper, string $filename, array $options = []) 107 { 108 $options += [ 109 '_id' => new ObjectId(), 110 'chunkSizeBytes' => self::$defaultChunkSizeBytes, 111 'disableMD5' => false, 112 ]; 113 114 if (isset($options['aliases']) && ! is_string_array($options['aliases'])) { 115 throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings'); 116 } 117 118 if (! is_integer($options['chunkSizeBytes'])) { 119 throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer'); 120 } 121 122 if ($options['chunkSizeBytes'] < 1) { 123 throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes'])); 124 } 125 126 if (! is_bool($options['disableMD5'])) { 127 throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean'); 128 } 129 130 if (isset($options['contentType']) && ! is_string($options['contentType'])) { 131 throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string'); 132 } 133 134 if (isset($options['metadata']) && ! is_array($options['metadata']) && ! is_object($options['metadata'])) { 135 throw InvalidArgumentException::invalidType('"metadata" option', $options['metadata'], 'array or object'); 136 } 137 138 $this->chunkSize = $options['chunkSizeBytes']; 139 $this->collectionWrapper = $collectionWrapper; 140 $this->disableMD5 = $options['disableMD5']; 141 142 if (! $this->disableMD5) { 143 $this->hashCtx = hash_init('md5'); 144 } 145 146 $this->file = [ 147 '_id' => $options['_id'], 148 'chunkSize' => $this->chunkSize, 149 'filename' => $filename, 150 ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]); 151 } 152 153 /** 154 * Return internal properties for debugging purposes. 155 * 156 * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo 157 */ 158 public function __debugInfo(): array 159 { 160 return [ 161 'bucketName' => $this->collectionWrapper->getBucketName(), 162 'databaseName' => $this->collectionWrapper->getDatabaseName(), 163 'file' => $this->file, 164 ]; 165 } 166 167 /** 168 * Closes an active stream and flushes all buffered data to GridFS. 169 */ 170 public function close(): void 171 { 172 if ($this->isClosed) { 173 // TODO: Should this be an error condition? e.g. BadMethodCallException 174 return; 175 } 176 177 if (strlen($this->buffer) > 0) { 178 $this->insertChunkFromBuffer(); 179 } 180 181 $this->fileCollectionInsert(); 182 $this->isClosed = true; 183 } 184 185 /** 186 * Return the stream's file document. 187 */ 188 public function getFile(): object 189 { 190 return (object) $this->file; 191 } 192 193 /** 194 * Return the stream's size in bytes. 195 * 196 * Note: this value will increase as more data is written to the stream. 197 */ 198 public function getSize(): int 199 { 200 return $this->length + strlen($this->buffer); 201 } 202 203 /** 204 * Return the current position of the stream. 205 * 206 * This is the offset within the stream where the next byte would be 207 * written. Since seeking is not supported and writes are appended, this is 208 * always the end of the stream. 209 * 210 * @see WritableStream::getSize() 211 */ 212 public function tell(): int 213 { 214 return $this->getSize(); 215 } 216 217 /** 218 * Inserts binary data into GridFS via chunks. 219 * 220 * Data will be buffered internally until chunkSizeBytes are accumulated, at 221 * which point a chunk document will be inserted and the buffer reset. 222 * 223 * @param string $data Binary data to write 224 */ 225 public function writeBytes(string $data): int 226 { 227 if ($this->isClosed) { 228 // TODO: Should this be an error condition? e.g. BadMethodCallException 229 return 0; 230 } 231 232 $bytesRead = 0; 233 234 while ($bytesRead != strlen($data)) { 235 $initialBufferLength = strlen($this->buffer); 236 $this->buffer .= substr($data, $bytesRead, $this->chunkSize - $initialBufferLength); 237 $bytesRead += strlen($this->buffer) - $initialBufferLength; 238 239 if (strlen($this->buffer) == $this->chunkSize) { 240 $this->insertChunkFromBuffer(); 241 } 242 } 243 244 return $bytesRead; 245 } 246 247 private function abort(): void 248 { 249 try { 250 $this->collectionWrapper->deleteChunksByFilesId($this->file['_id']); 251 } catch (DriverRuntimeException $e) { 252 // We are already handling an error if abort() is called, so suppress this 253 } 254 255 $this->isClosed = true; 256 } 257 258 /** 259 * @return mixed 260 */ 261 private function fileCollectionInsert() 262 { 263 $this->file['length'] = $this->length; 264 $this->file['uploadDate'] = new UTCDateTime(); 265 266 if (! $this->disableMD5 && $this->hashCtx) { 267 $this->file['md5'] = hash_final($this->hashCtx); 268 } 269 270 try { 271 $this->collectionWrapper->insertFile($this->file); 272 } catch (DriverRuntimeException $e) { 273 $this->abort(); 274 275 throw $e; 276 } 277 278 return $this->file['_id']; 279 } 280 281 private function insertChunkFromBuffer(): void 282 { 283 if (strlen($this->buffer) == 0) { 284 return; 285 } 286 287 $data = $this->buffer; 288 $this->buffer = ''; 289 290 $chunk = [ 291 'files_id' => $this->file['_id'], 292 'n' => $this->chunkOffset, 293 'data' => new Binary($data, Binary::TYPE_GENERIC), 294 ]; 295 296 if (! $this->disableMD5 && $this->hashCtx) { 297 hash_update($this->hashCtx, $data); 298 } 299 300 try { 301 $this->collectionWrapper->insertChunk($chunk); 302 } catch (DriverRuntimeException $e) { 303 $this->abort(); 304 305 throw $e; 306 } 307 308 $this->length += strlen($data); 309 $this->chunkOffset++; 310 } 311 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body