Skip to content

Commit 6e636cb

Browse files
committed
Merge pull request #304
2 parents 9dfb2c5 + 049a77c commit 6e636cb

File tree

6 files changed

+74
-45
lines changed

6 files changed

+74
-45
lines changed

src/GridFS/Bucket.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ public function __construct(Manager $manager, $databaseName, array $options = []
8080
throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
8181
}
8282

83+
if (isset($options['chunkSizeBytes']) && $options['chunkSizeBytes'] < 1) {
84+
throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
85+
}
86+
8387
if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
8488
throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
8589
}

src/GridFS/StreamWrapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public function stream_write($data)
150150
}
151151

152152
try {
153-
return $this->stream->insertChunks($data);
153+
return $this->stream->writeBytes($data);
154154
} catch (Exception $e) {
155155
trigger_error(sprintf('%s: %s', get_class($e), $e->getMessage()), \E_USER_WARNING);
156156
return false;

src/GridFS/WritableStream.php

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
use MongoDB\BSON\Binary;
66
use MongoDB\BSON\ObjectId;
77
use MongoDB\BSON\UTCDateTime;
8+
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
89
use MongoDB\Exception\InvalidArgumentException;
10+
use MongoDB\Exception\RuntimeException;
911

1012
/**
1113
* WritableStream abstracts the process of writing a GridFS file.
@@ -16,8 +18,7 @@ class WritableStream
1618
{
1719
private static $defaultChunkSizeBytes = 261120;
1820

19-
private $buffer;
20-
private $bufferLength = 0;
21+
private $buffer = '';
2122
private $chunkOffset = 0;
2223
private $chunkSize;
2324
private $collectionWrapper;
@@ -66,6 +67,10 @@ public function __construct(CollectionWrapper $collectionWrapper, $filename, arr
6667
throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
6768
}
6869

70+
if (isset($options['chunkSizeBytes']) && $options['chunkSizeBytes'] < 1) {
71+
throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
72+
}
73+
6974
if (isset($options['contentType']) && ! is_string($options['contentType'])) {
7075
throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string');
7176
}
@@ -76,15 +81,13 @@ public function __construct(CollectionWrapper $collectionWrapper, $filename, arr
7681

7782
$this->chunkSize = $options['chunkSizeBytes'];
7883
$this->collectionWrapper = $collectionWrapper;
79-
$this->buffer = fopen('php://memory', 'w+b');
8084
$this->ctx = hash_init('md5');
8185

8286
$this->file = [
8387
'_id' => $options['_id'],
8488
'chunkSize' => $this->chunkSize,
8589
'filename' => (string) $filename,
86-
// TODO: This is necessary until PHPC-536 is implemented
87-
'uploadDate' => new UTCDateTime((int) floor(microtime(true) * 1000)),
90+
'uploadDate' => new UTCDateTime,
8891
] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
8992
}
9093

@@ -113,14 +116,10 @@ public function close()
113116
return;
114117
}
115118

116-
rewind($this->buffer);
117-
$cached = stream_get_contents($this->buffer);
118-
119-
if (strlen($cached) > 0) {
120-
$this->insertChunk($cached);
119+
if (strlen($this->buffer) > 0) {
120+
$this->insertChunkFromBuffer();
121121
}
122122

123-
fclose($this->buffer);
124123
$this->fileCollectionInsert();
125124
$this->isClosed = true;
126125
}
@@ -151,77 +150,87 @@ public function getSize()
151150
* Inserts binary data into GridFS via chunks.
152151
*
153152
* Data will be buffered internally until chunkSizeBytes are accumulated, at
154-
* which point a chunk's worth of data will be inserted and the buffer
155-
* reset.
153+
* which point a chunk document will be inserted and the buffer reset.
156154
*
157-
* @param string $toWrite Binary data to write
155+
* @param string $data Binary data to write
158156
* @return integer
159157
*/
160-
public function insertChunks($toWrite)
158+
public function writeBytes($data)
161159
{
162160
if ($this->isClosed) {
163161
// TODO: Should this be an error condition? e.g. BadMethodCallException
164162
return;
165163
}
166164

167-
$readBytes = 0;
165+
$bytesRead = 0;
168166

169-
while ($readBytes != strlen($toWrite)) {
170-
$addToBuffer = substr($toWrite, $readBytes, $this->chunkSize - $this->bufferLength);
171-
fwrite($this->buffer, $addToBuffer);
172-
$readBytes += strlen($addToBuffer);
173-
$this->bufferLength += strlen($addToBuffer);
167+
while ($bytesRead != strlen($data)) {
168+
$initialBufferLength = strlen($this->buffer);
169+
$this->buffer .= substr($data, $bytesRead, $this->chunkSize - $initialBufferLength);
170+
$bytesRead += strlen($this->buffer) - $initialBufferLength;
174171

175-
if ($this->bufferLength == $this->chunkSize) {
176-
rewind($this->buffer);
177-
$this->insertChunk(stream_get_contents($this->buffer));
178-
ftruncate($this->buffer, 0);
179-
$this->bufferLength = 0;
172+
if (strlen($this->buffer) == $this->chunkSize) {
173+
$this->insertChunkFromBuffer();
180174
}
181175
}
182176

183-
return $readBytes;
177+
return $bytesRead;
184178
}
185179

186180
private function abort()
187181
{
188-
$this->collectionWrapper->deleteChunksByFilesId($this->file['_id']);
182+
try {
183+
$this->collectionWrapper->deleteChunksByFilesId($this->file['_id']);
184+
} catch (DriverRuntimeException $e) {
185+
// We are already handling an error if abort() is called, so suppress this
186+
}
187+
189188
$this->isClosed = true;
190189
}
191190

192191
private function fileCollectionInsert()
193192
{
194-
if ($this->isClosed) {
195-
// TODO: Should this be an error condition? e.g. BadMethodCallException
196-
return;
197-
}
198-
199193
$md5 = hash_final($this->ctx);
200194

201195
$this->file['length'] = $this->length;
202196
$this->file['md5'] = $md5;
203197

204-
$this->collectionWrapper->insertFile($this->file);
198+
try {
199+
$this->collectionWrapper->insertFile($this->file);
200+
} catch (DriverRuntimeException $e) {
201+
$this->abort();
202+
203+
throw $e;
204+
}
205205

206206
return $this->file['_id'];
207207
}
208208

209-
private function insertChunk($data)
209+
private function insertChunkFromBuffer()
210210
{
211-
if ($this->isClosed) {
212-
// TODO: Should this be an error condition? e.g. BadMethodCallException
211+
if (strlen($this->buffer) == 0) {
213212
return;
214213
}
215214

216-
$toUpload = [
215+
$data = $this->buffer;
216+
$this->buffer = '';
217+
218+
$chunk = [
217219
'files_id' => $this->file['_id'],
218220
'n' => $this->chunkOffset,
219221
'data' => new Binary($data, Binary::TYPE_GENERIC),
220222
];
221223

222224
hash_update($this->ctx, $data);
223225

224-
$this->collectionWrapper->insertChunk($toUpload);
226+
try {
227+
$this->collectionWrapper->insertChunk($chunk);
228+
} catch (DriverRuntimeException $e) {
229+
$this->abort();
230+
231+
throw $e;
232+
}
233+
225234
$this->length += strlen($data);
226235
$this->chunkOffset++;
227236
}

tests/GridFS/BucketFunctionalTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ public function provideInvalidConstructorOptions()
6868
return $options;
6969
}
7070

71+
/**
72+
* @expectedException MongoDB\Exception\InvalidArgumentException
73+
* @expectedExceptionMessage Expected "chunkSizeBytes" option to be >= 1, 0 given
74+
*/
75+
public function testConstructorShouldRequireChunkSizeBytesOptionToBePositive()
76+
{
77+
new Bucket($this->manager, $this->getDatabaseName(), ['chunkSizeBytes' => 0]);
78+
}
79+
7180
/**
7281
* @dataProvider provideInputDataAndExpectedChunks
7382
*/

tests/GridFS/SpecFunctionalTest.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,7 @@ private function convertTypes(array $data, $createBinary = true)
150150
}
151151

152152
if (isset($value['$date'])) {
153-
// TODO: This is necessary until PHPC-536 is implemented
154-
$milliseconds = floor((new DateTime($value['$date']))->format('U.u') * 1000);
155-
$value = new UTCDateTime((int) $milliseconds);
153+
$value = new UTCDateTime(new DateTime($value['$date']));
156154
return;
157155
}
158156

tests/GridFS/WritableStreamFunctionalTest.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,22 @@ public function provideInvalidConstructorOptions()
5252
return $options;
5353
}
5454

55+
/**
56+
* @expectedException MongoDB\Exception\InvalidArgumentException
57+
* @expectedExceptionMessage Expected "chunkSizeBytes" option to be >= 1, 0 given
58+
*/
59+
public function testConstructorShouldRequireChunkSizeBytesOptionToBePositive()
60+
{
61+
new WritableStream($this->collectionWrapper, 'filename', ['chunkSizeBytes' => 0]);
62+
}
63+
5564
/**
5665
* @dataProvider provideInputDataAndExpectedMD5
5766
*/
58-
public function testInsertChunksCalculatesMD5($input, $expectedMD5)
67+
public function testWriteBytesCalculatesMD5($input, $expectedMD5)
5968
{
6069
$stream = new WritableStream($this->collectionWrapper, 'filename');
61-
$stream->insertChunks($input);
70+
$stream->writeBytes($input);
6271
$stream->close();
6372

6473
$fileDocument = $this->filesCollection->findOne(

0 commit comments

Comments
 (0)