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