Skip to content

Commit aef27f0

Browse files
committed
Merge pull request #57
2 parents 298624b + 67c450b commit aef27f0

21 files changed

+3990
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace MongoDB\Exception;
4+
5+
class GridFSCorruptFileException extends \MongoDB\Driver\Exception\RuntimeException implements Exception
6+
{
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace MongoDB\Exception;
4+
5+
class GridFSFileNotFoundException extends \MongoDB\Driver\Exception\RuntimeException implements Exception
6+
{
7+
public function __construct($filename, $namespace)
8+
{
9+
parent::__construct(sprintf('Unable to find file "%s" in namespace "%s"', $filename, $namespace));
10+
}
11+
}

src/GridFS/Bucket.php

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
<?php
2+
3+
namespace MongoDB\GridFS;
4+
5+
use MongoDB\BSON\ObjectId;
6+
use MongoDB\Driver\Cursor;
7+
use MongoDB\Driver\Manager;
8+
use MongoDB\Exception\GridFSFileNotFoundException;
9+
use MongoDB\Exception\InvalidArgumentTypeException;
10+
use MongoDB\Operation\Find;
11+
12+
/**
13+
* Bucket provides a public API for interacting with the GridFS files and chunks
14+
* collections.
15+
*
16+
* @api
17+
*/
18+
class Bucket
19+
{
20+
private static $streamWrapper;
21+
22+
private $collectionsWrapper;
23+
private $databaseName;
24+
private $options;
25+
26+
/**
27+
* Constructs a GridFS bucket.
28+
*
29+
* Supported options:
30+
*
31+
* * bucketName (string): The bucket name, which will be used as a prefix
32+
* for the files and chunks collections. Defaults to "fs".
33+
*
34+
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
35+
* 261120 (i.e. 255 KiB).
36+
*
37+
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
38+
*
39+
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
40+
*
41+
* @param Manager $manager Manager instance from the driver
42+
* @param string $databaseName Database name
43+
* @param array $options Bucket options
44+
* @throws InvalidArgumentException
45+
*/
46+
public function __construct(Manager $manager, $databaseName, array $options = [])
47+
{
48+
$options += [
49+
'bucketName' => 'fs',
50+
'chunkSizeBytes' => 261120,
51+
];
52+
53+
if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
54+
throw new InvalidArgumentTypeException('"bucketName" option', $options['bucketName'], 'string');
55+
}
56+
57+
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
58+
throw new InvalidArgumentTypeException('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
59+
}
60+
61+
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
62+
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
63+
}
64+
65+
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
66+
throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
67+
}
68+
69+
$this->databaseName = (string) $databaseName;
70+
$this->options = $options;
71+
72+
$collectionOptions = array_intersect_key($options, ['readPreference' => 1, 'writeConcern' => 1]);
73+
74+
$this->collectionsWrapper = new GridFSCollectionsWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
75+
$this->registerStreamWrapper($manager);
76+
}
77+
78+
/**
79+
* Delete a file from the GridFS bucket.
80+
*
81+
* If the files collection document is not found, this method will still
82+
* attempt to delete orphaned chunks.
83+
*
84+
* @param ObjectId $id ObjectId of the file
85+
* @throws GridFSFileNotFoundException
86+
*/
87+
public function delete(ObjectId $id)
88+
{
89+
$file = $this->collectionsWrapper->getFilesCollection()->findOne(['_id' => $id]);
90+
$this->collectionsWrapper->getFilesCollection()->deleteOne(['_id' => $id]);
91+
$this->collectionsWrapper->getChunksCollection()->deleteMany(['files_id' => $id]);
92+
93+
if ($file === null) {
94+
throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
95+
}
96+
97+
}
98+
99+
/**
100+
* Writes the contents of a GridFS file to a writable stream.
101+
*
102+
* @param ObjectId $id ObjectId of the file
103+
* @param resource $destination Writable Stream
104+
* @throws GridFSFileNotFoundException
105+
*/
106+
public function downloadToStream(ObjectId $id, $destination)
107+
{
108+
$file = $this->collectionsWrapper->getFilesCollection()->findOne(
109+
['_id' => $id],
110+
['typeMap' => ['root' => 'stdClass']]
111+
);
112+
113+
if ($file === null) {
114+
throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
115+
}
116+
117+
$gridFsStream = new GridFSDownload($this->collectionsWrapper, $file);
118+
$gridFsStream->downloadToStream($destination);
119+
}
120+
121+
/**
122+
* Writes the contents of a GridFS file, which is selected by name and
123+
* revision, to a writable stream.
124+
*
125+
* Supported options:
126+
*
127+
* * revision (integer): Which revision (i.e. documents with the same
128+
* filename and different uploadDate) of the file to retrieve. Defaults
129+
* to -1 (i.e. the most recent revision).
130+
*
131+
* Revision numbers are defined as follows:
132+
*
133+
* * 0 = the original stored file
134+
* * 1 = the first revision
135+
* * 2 = the second revision
136+
* * etc…
137+
* * -2 = the second most recent revision
138+
* * -1 = the most recent revision
139+
*
140+
* @param string $filename File name
141+
* @param resource $destination Writable Stream
142+
* @param array $options Download options
143+
* @throws GridFSFileNotFoundException
144+
*/
145+
public function downloadToStreamByName($filename, $destination, array $options = [])
146+
{
147+
$options += ['revision' => -1];
148+
$file = $this->findFileRevision($filename, $options['revision']);
149+
$gridFsStream = new GridFSDownload($this->collectionsWrapper, $file);
150+
$gridFsStream->downloadToStream($destination);
151+
}
152+
153+
/**
154+
* Drops the files and chunks collection associated with GridFS this bucket
155+
*
156+
*/
157+
158+
public function drop()
159+
{
160+
$this->collectionsWrapper->dropCollections();
161+
}
162+
163+
/**
164+
* Find files from the GridFS bucket's files collection.
165+
*
166+
* @see Find::__construct() for supported options
167+
* @param array|object $filter Query by which to filter documents
168+
* @param array $options Additional options
169+
* @return Cursor
170+
*/
171+
public function find($filter, array $options = [])
172+
{
173+
return $this->collectionsWrapper->getFilesCollection()->find($filter, $options);
174+
}
175+
176+
public function getCollectionsWrapper()
177+
{
178+
return $this->collectionsWrapper;
179+
}
180+
181+
public function getDatabaseName()
182+
{
183+
return $this->databaseName;
184+
}
185+
186+
/**
187+
* Gets the ID of the GridFS file associated with a stream.
188+
*
189+
* @param resource $stream GridFS stream
190+
* @return mixed
191+
*/
192+
public function getIdFromStream($stream)
193+
{
194+
$metadata = stream_get_meta_data($stream);
195+
196+
if ($metadata['wrapper_data'] instanceof StreamWrapper) {
197+
return $metadata['wrapper_data']->getId();
198+
}
199+
200+
return;
201+
}
202+
203+
/**
204+
* Opens a readable stream for reading a GridFS file.
205+
*
206+
* @param ObjectId $id ObjectId of the file
207+
* @return resource
208+
* @throws GridFSFileNotFoundException
209+
*/
210+
public function openDownloadStream(ObjectId $id)
211+
{
212+
$file = $this->collectionsWrapper->getFilesCollection()->findOne(
213+
['_id' => $id],
214+
['typeMap' => ['root' => 'stdClass']]
215+
);
216+
217+
if ($file === null) {
218+
throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
219+
}
220+
221+
return $this->openDownloadStreamByFile($file);
222+
}
223+
224+
/**
225+
* Opens a readable stream stream to read a GridFS file, which is selected
226+
* by name and revision.
227+
*
228+
* Supported options:
229+
*
230+
* * revision (integer): Which revision (i.e. documents with the same
231+
* filename and different uploadDate) of the file to retrieve. Defaults
232+
* to -1 (i.e. the most recent revision).
233+
*
234+
* Revision numbers are defined as follows:
235+
*
236+
* * 0 = the original stored file
237+
* * 1 = the first revision
238+
* * 2 = the second revision
239+
* * etc…
240+
* * -2 = the second most recent revision
241+
* * -1 = the most recent revision
242+
*
243+
* @param string $filename File name
244+
* @param array $options Download options
245+
* @return resource
246+
* @throws GridFSFileNotFoundException
247+
*/
248+
public function openDownloadStreamByName($filename, array $options = [])
249+
{
250+
$options += ['revision' => -1];
251+
$file = $this->findFileRevision($filename, $options['revision']);
252+
253+
return $this->openDownloadStreamByFile($file);
254+
}
255+
256+
/**
257+
* Opens a writable stream for writing a GridFS file.
258+
*
259+
* Supported options:
260+
*
261+
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
262+
* bucket's chunk size.
263+
*
264+
* @param string $filename File name
265+
* @param array $options Stream options
266+
* @return resource
267+
*/
268+
public function openUploadStream($filename, array $options = [])
269+
{
270+
$options += ['chunkSizeBytes' => $this->options['chunkSizeBytes']];
271+
272+
$streamOptions = [
273+
'collectionsWrapper' => $this->collectionsWrapper,
274+
'uploadOptions' => $options,
275+
];
276+
277+
$context = stream_context_create(['gridfs' => $streamOptions]);
278+
279+
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $filename), 'w', false, $context);
280+
}
281+
282+
/**
283+
* Renames the GridFS file with the specified ID.
284+
*
285+
* @param ObjectId $id ID of the file to rename
286+
* @param string $newFilename New file name
287+
* @throws GridFSFileNotFoundException
288+
*/
289+
public function rename(ObjectId $id, $newFilename)
290+
{
291+
$filesCollection = $this->collectionsWrapper->getFilesCollection();
292+
$result = $filesCollection->updateOne(['_id' => $id], ['$set' => ['filename' => $newFilename]]);
293+
if($result->getModifiedCount() == 0) {
294+
throw new GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
295+
}
296+
}
297+
298+
/**
299+
* Writes the contents of a readable stream to a GridFS file.
300+
*
301+
* Supported options:
302+
*
303+
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
304+
* bucket's chunk size.
305+
*
306+
* @param string $filename File name
307+
* @param resource $source Readable stream
308+
* @param array $options Stream options
309+
* @return ObjectId
310+
*/
311+
public function uploadFromStream($filename, $source, array $options = [])
312+
{
313+
$options += ['chunkSizeBytes' => $this->options['chunkSizeBytes']];
314+
$gridFsStream = new GridFSUpload($this->collectionsWrapper, $filename, $options);
315+
316+
return $gridFsStream->uploadFromStream($source);
317+
}
318+
319+
private function findFileRevision($filename, $revision)
320+
{
321+
if ($revision < 0) {
322+
$skip = abs($revision) - 1;
323+
$sortOrder = -1;
324+
} else {
325+
$skip = $revision;
326+
$sortOrder = 1;
327+
}
328+
329+
$filesCollection = $this->collectionsWrapper->getFilesCollection();
330+
$file = $filesCollection->findOne(
331+
['filename' => $filename],
332+
[
333+
'skip' => $skip,
334+
'sort' => ['uploadDate' => $sortOrder],
335+
'typeMap' => ['root' => 'stdClass'],
336+
]
337+
);
338+
339+
if ($file === null) {
340+
throw new GridFSFileNotFoundException($filename, $filesCollection->getNameSpace());
341+
}
342+
343+
return $file;
344+
}
345+
346+
private function openDownloadStreamByFile($file)
347+
{
348+
$options = [
349+
'collectionsWrapper' => $this->collectionsWrapper,
350+
'file' => $file,
351+
];
352+
353+
$context = stream_context_create(['gridfs' => $options]);
354+
355+
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $file->filename), 'r', false, $context);
356+
}
357+
358+
private function registerStreamWrapper(Manager $manager)
359+
{
360+
if (isset(self::$streamWrapper)) {
361+
return;
362+
}
363+
364+
self::$streamWrapper = new StreamWrapper();
365+
self::$streamWrapper->register($manager);
366+
}
367+
}

0 commit comments

Comments
 (0)