10
10
use MongoDB \Exception \InvalidArgumentException ;
11
11
use MongoDB \GridFS \Exception \FileNotFoundException ;
12
12
use MongoDB \Operation \Find ;
13
+ use stdClass ;
13
14
14
15
/**
15
16
* Bucket provides a public API for interacting with the GridFS files and chunks
19
20
*/
20
21
class Bucket
21
22
{
22
- private static $ streamWrapper ;
23
23
private static $ defaultChunkSizeBytes = 261120 ;
24
+ private static $ streamWrapperProtocol = 'gridfs ' ;
24
25
25
26
private $ collectionWrapper ;
26
27
private $ databaseName ;
@@ -75,7 +76,7 @@ public function __construct(Manager $manager, $databaseName, array $options = []
75
76
$ collectionOptions = array_intersect_key ($ options , ['readPreference ' => 1 , 'writeConcern ' => 1 ]);
76
77
77
78
$ this ->collectionWrapper = new CollectionWrapper ($ manager , $ databaseName , $ options ['bucketName ' ], $ collectionOptions );
78
- $ this ->registerStreamWrapper ($ manager );
79
+ $ this ->registerStreamWrapper ();
79
80
}
80
81
81
82
/**
@@ -89,14 +90,12 @@ public function __construct(Manager $manager, $databaseName, array $options = []
89
90
*/
90
91
public function delete (ObjectId $ id )
91
92
{
92
- $ file = $ this ->collectionWrapper ->getFilesCollection ()->findOne (['_id ' => $ id ]);
93
- $ this ->collectionWrapper ->getFilesCollection ()->deleteOne (['_id ' => $ id ]);
94
- $ this ->collectionWrapper ->getChunksCollection ()->deleteMany (['files_id ' => $ id ]);
93
+ $ file = $ this ->collectionWrapper ->findFileById ($ id );
94
+ $ this ->collectionWrapper ->deleteFileAndChunksById ($ id );
95
95
96
96
if ($ file === null ) {
97
- throw FileNotFoundException::byId ($ id , $ this ->collectionWrapper -> getFilesCollection ()-> getNameSpace ());
97
+ throw FileNotFoundException::byId ($ id , $ this ->getFilesNamespace ());
98
98
}
99
-
100
99
}
101
100
102
101
/**
@@ -108,17 +107,14 @@ public function delete(ObjectId $id)
108
107
*/
109
108
public function downloadToStream (ObjectId $ id , $ destination )
110
109
{
111
- $ file = $ this ->collectionWrapper ->getFilesCollection ()->findOne (
112
- ['_id ' => $ id ],
113
- ['typeMap ' => ['root ' => 'stdClass ' ]]
114
- );
110
+ $ file = $ this ->collectionWrapper ->findFileById ($ id );
115
111
116
112
if ($ file === null ) {
117
- throw FileNotFoundException::byId ($ id , $ this ->collectionWrapper -> getFilesCollection ()-> getNameSpace ());
113
+ throw FileNotFoundException::byId ($ id , $ this ->getFilesNamespace ());
118
114
}
119
115
120
- $ gridFsStream = new GridFSDownload ($ this ->collectionWrapper , $ file );
121
- $ gridFsStream ->downloadToStream ($ destination );
116
+ $ stream = new ReadableStream ($ this ->collectionWrapper , $ file );
117
+ $ stream ->downloadToStream ($ destination );
122
118
}
123
119
124
120
/**
@@ -148,23 +144,29 @@ public function downloadToStream(ObjectId $id, $destination)
148
144
public function downloadToStreamByName ($ filename , $ destination , array $ options = [])
149
145
{
150
146
$ options += ['revision ' => -1 ];
151
- $ file = $ this ->findFileRevision ($ filename , $ options ['revision ' ]);
152
- $ gridFsStream = new GridFSDownload ($ this ->collectionWrapper , $ file );
153
- $ gridFsStream ->downloadToStream ($ destination );
147
+
148
+ $ file = $ this ->collectionWrapper ->findFileByFilenameAndRevision ($ filename , $ options ['revision ' ]);
149
+
150
+ if ($ file === null ) {
151
+ throw FileNotFoundException::byFilenameAndRevision ($ filename , $ options ['revision ' ], $ this ->getFilesNamespace ());
152
+ }
153
+
154
+ $ stream = new ReadableStream ($ this ->collectionWrapper , $ file );
155
+ $ stream ->downloadToStream ($ destination );
154
156
}
155
157
156
158
/**
157
- * Drops the files and chunks collection associated with GridFS this bucket
158
- *
159
- */
160
-
159
+ * Drops the files and chunks collections associated with this GridFS
160
+ * bucket.
161
+ */
161
162
public function drop ()
162
163
{
163
164
$ this ->collectionWrapper ->dropCollections ();
164
165
}
165
166
166
167
/**
167
- * Find files from the GridFS bucket's files collection.
168
+ * Finds documents from the GridFS bucket's files collection matching the
169
+ * query.
168
170
*
169
171
* @see Find::__construct() for supported options
170
172
* @param array|object $filter Query by which to filter documents
@@ -173,10 +175,10 @@ public function drop()
173
175
*/
174
176
public function find ($ filter , array $ options = [])
175
177
{
176
- return $ this ->collectionWrapper ->getFilesCollection ()-> find ($ filter , $ options );
178
+ return $ this ->collectionWrapper ->findFiles ($ filter , $ options );
177
179
}
178
180
179
- public function getCollectionsWrapper ()
181
+ public function getCollectionWrapper ()
180
182
{
181
183
return $ this ->collectionWrapper ;
182
184
}
@@ -200,7 +202,7 @@ public function getIdFromStream($stream)
200
202
return $ metadata ['wrapper_data ' ]->getId ();
201
203
}
202
204
203
- return ;
205
+ // TODO: Throw if we cannot access the ID
204
206
}
205
207
206
208
/**
@@ -212,13 +214,10 @@ public function getIdFromStream($stream)
212
214
*/
213
215
public function openDownloadStream (ObjectId $ id )
214
216
{
215
- $ file = $ this ->collectionWrapper ->getFilesCollection ()->findOne (
216
- ['_id ' => $ id ],
217
- ['typeMap ' => ['root ' => 'stdClass ' ]]
218
- );
217
+ $ file = $ this ->collectionWrapper ->findFileById ($ id );
219
218
220
219
if ($ file === null ) {
221
- throw FileNotFoundException::byId ($ id , $ this ->collectionWrapper -> getFilesCollection ()-> getNameSpace ());
220
+ throw FileNotFoundException::byId ($ id , $ this ->getFilesNamespace ());
222
221
}
223
222
224
223
return $ this ->openDownloadStreamByFile ($ file );
@@ -251,7 +250,12 @@ public function openDownloadStream(ObjectId $id)
251
250
public function openDownloadStreamByName ($ filename , array $ options = [])
252
251
{
253
252
$ options += ['revision ' => -1 ];
254
- $ file = $ this ->findFileRevision ($ filename , $ options ['revision ' ]);
253
+
254
+ $ file = $ this ->collectionWrapper ->findFileByFilenameAndRevision ($ filename , $ options ['revision ' ]);
255
+
256
+ if ($ file === null ) {
257
+ throw FileNotFoundException::byFilenameAndRevision ($ filename , $ options ['revision ' ], $ this ->getFilesNamespace ());
258
+ }
255
259
256
260
return $ this ->openDownloadStreamByFile ($ file );
257
261
}
@@ -265,36 +269,51 @@ public function openDownloadStreamByName($filename, array $options = [])
265
269
* bucket's chunk size.
266
270
*
267
271
* @param string $filename File name
268
- * @param array $options Stream options
272
+ * @param array $options Upload options
269
273
* @return resource
270
274
*/
271
275
public function openUploadStream ($ filename , array $ options = [])
272
276
{
273
277
$ options += ['chunkSizeBytes ' => $ this ->options ['chunkSizeBytes ' ]];
274
278
275
- $ streamOptions = [
276
- 'collectionWrapper ' => $ this ->collectionWrapper ,
277
- 'uploadOptions ' => $ options ,
278
- ];
279
-
280
- $ context = stream_context_create (['gridfs ' => $ streamOptions ]);
279
+ $ path = $ this ->createPathForUpload ();
280
+ $ context = stream_context_create ([
281
+ self ::$ streamWrapperProtocol => [
282
+ 'collectionWrapper ' => $ this ->collectionWrapper ,
283
+ 'filename ' => $ filename ,
284
+ 'options ' => $ options ,
285
+ ],
286
+ ]);
281
287
282
- return fopen (sprintf ( ' gridfs://%s/%s ' , $ this -> databaseName , $ filename ) , 'w ' , false , $ context );
288
+ return fopen ($ path , 'w ' , false , $ context );
283
289
}
284
290
285
291
/**
286
292
* Renames the GridFS file with the specified ID.
287
293
*
288
294
* @param ObjectId $id ID of the file to rename
289
295
* @param string $newFilename New file name
290
- * @throws GridFSFileNotFoundException
296
+ * @throws FileNotFoundException
291
297
*/
292
298
public function rename (ObjectId $ id , $ newFilename )
293
299
{
294
- $ filesCollection = $ this ->collectionWrapper ->getFilesCollection ();
295
- $ result = $ filesCollection ->updateOne (['_id ' => $ id ], ['$set ' => ['filename ' => $ newFilename ]]);
296
- if ($ result ->getModifiedCount () == 0 ) {
297
- throw FileNotFoundException::byId ($ id , $ this ->collectionWrapper ->getFilesCollection ()->getNameSpace ());
300
+ $ updateResult = $ this ->collectionWrapper ->updateFilenameForId ($ id , $ newFilename );
301
+
302
+ if ($ updateResult ->getModifiedCount () === 1 ) {
303
+ return ;
304
+ }
305
+
306
+ /* If the update resulted in no modification, it's possible that the
307
+ * file did not exist, in which case we must raise an error. Checking
308
+ * the write result's matched count will be most efficient, but fall
309
+ * back to a findOne operation if necessary (i.e. legacy writes).
310
+ */
311
+ $ found = $ updateResult ->getMatchedCount () !== null
312
+ ? $ updateResult ->getMatchedCount () === 1
313
+ : $ this ->collectionWrapper ->findFileById ($ id ) !== null ;
314
+
315
+ if ( ! $ found ) {
316
+ throw FileNotFoundException::byId ($ id , $ this ->getFilesNamespace ());
298
317
}
299
318
}
300
319
@@ -310,61 +329,93 @@ public function rename(ObjectId $id, $newFilename)
310
329
* @param resource $source Readable stream
311
330
* @param array $options Stream options
312
331
* @return ObjectId
332
+ * @throws InvalidArgumentException
313
333
*/
314
334
public function uploadFromStream ($ filename , $ source , array $ options = [])
315
335
{
316
336
$ options += ['chunkSizeBytes ' => $ this ->options ['chunkSizeBytes ' ]];
317
- $ gridFsStream = new GridFSUpload ($ this ->collectionWrapper , $ filename , $ options );
318
337
319
- return $ gridFsStream ->uploadFromStream ($ source );
338
+ $ stream = new WritableStream ($ this ->collectionWrapper , $ filename , $ options );
339
+
340
+ return $ stream ->uploadFromStream ($ source );
320
341
}
321
342
322
- private function findFileRevision ($ filename , $ revision )
343
+ /**
344
+ * Creates a path for an existing GridFS file.
345
+ *
346
+ * @param stdClass $file GridFS file document
347
+ * @return string
348
+ */
349
+ private function createPathForFile (stdClass $ file )
323
350
{
324
- if ($ revision < 0 ) {
325
- $ skip = abs ($ revision ) - 1 ;
326
- $ sortOrder = -1 ;
351
+ if ( ! is_object ($ file ->_id ) || method_exists ($ file ->_id , '__toString ' )) {
352
+ $ id = (string ) $ file ->_id ;
327
353
} else {
328
- $ skip = $ revision ;
329
- $ sortOrder = 1 ;
354
+ $ id = \MongoDB \BSON \toJSON (\MongoDB \BSON \fromPHP (['_id ' => $ file ->_id ]));
330
355
}
331
356
332
- $ filesCollection = $ this ->collectionWrapper ->getFilesCollection ();
333
- $ file = $ filesCollection ->findOne (
334
- ['filename ' => $ filename ],
335
- [
336
- 'skip ' => $ skip ,
337
- 'sort ' => ['uploadDate ' => $ sortOrder ],
338
- 'typeMap ' => ['root ' => 'stdClass ' ],
339
- ]
357
+ return sprintf (
358
+ '%s://%s/%s.files/%s ' ,
359
+ self ::$ streamWrapperProtocol ,
360
+ urlencode ($ this ->databaseName ),
361
+ urlencode ($ this ->options ['bucketName ' ]),
362
+ urlencode ($ id )
340
363
);
341
-
342
- if ($ file === null ) {
343
- throw FileNotFoundException::byFilenameAndRevision ($ filename , $ revision , $ filesCollection ->getNameSpace ());
344
- }
345
-
346
- return $ file ;
347
364
}
348
365
349
- private function openDownloadStreamByFile ($ file )
366
+ /**
367
+ * Creates a path for a new GridFS file, which does not yet have an ID.
368
+ *
369
+ * @return string
370
+ */
371
+ private function createPathForUpload ()
350
372
{
351
- $ options = [
352
- 'collectionWrapper ' => $ this ->collectionWrapper ,
353
- 'file ' => $ file ,
354
- ];
373
+ return sprintf (
374
+ '%s://%s/%s.files ' ,
375
+ self ::$ streamWrapperProtocol ,
376
+ urlencode ($ this ->databaseName ),
377
+ urlencode ($ this ->options ['bucketName ' ])
378
+ );
379
+ }
355
380
356
- $ context = stream_context_create (['gridfs ' => $ options ]);
381
+ /**
382
+ * Returns the names of the files collection.
383
+ *
384
+ * @return string
385
+ */
386
+ private function getFilesNamespace ()
387
+ {
388
+ return sprintf ('%s.%s.files ' , $ this ->databaseName , $ this ->options ['bucketName ' ]);
389
+ }
357
390
358
- return fopen (sprintf ('gridfs://%s/%s ' , $ this ->databaseName , $ file ->filename ), 'r ' , false , $ context );
391
+ /**
392
+ * Opens a readable stream for the GridFS file.
393
+ *
394
+ * @param stdClass $file GridFS file document
395
+ * @return resource
396
+ */
397
+ private function openDownloadStreamByFile (stdClass $ file )
398
+ {
399
+ $ path = $ this ->createPathForFile ($ file );
400
+ $ context = stream_context_create ([
401
+ self ::$ streamWrapperProtocol => [
402
+ 'collectionWrapper ' => $ this ->collectionWrapper ,
403
+ 'file ' => $ file ,
404
+ ],
405
+ ]);
406
+
407
+ return fopen ($ path , 'r ' , false , $ context );
359
408
}
360
409
361
- private function registerStreamWrapper (Manager $ manager )
410
+ /**
411
+ * Registers the GridFS stream wrapper if it is not already registered.
412
+ */
413
+ private function registerStreamWrapper ()
362
414
{
363
- if (isset (self ::$ streamWrapper )) {
415
+ if (in_array (self ::$ streamWrapperProtocol , stream_get_wrappers () )) {
364
416
return ;
365
417
}
366
418
367
- self ::$ streamWrapper = new StreamWrapper ();
368
- self ::$ streamWrapper ->register ($ manager );
419
+ StreamWrapper::register (self ::$ streamWrapperProtocol );
369
420
}
370
421
}
0 commit comments