Skip to content

Commit ba3efc0

Browse files
Will Banfieldjmikola
authored andcommitted
Response to PR comments to mongodb-php-library pull 57
1 parent edb5dd6 commit ba3efc0

File tree

7 files changed

+308
-178
lines changed

7 files changed

+308
-178
lines changed

src/Exception/GridFSFileNotFoundException.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class GridFSFileNotFoundException extends \MongoDB\Driver\Exception\RuntimeException implements Exception
66
{
7-
public function __construct($fname, $bucketName, $databaseName){
8-
parent::__construct(sprintf('Unable to find file by: %s in %s.%s', $fname,$databaseName, $bucketName));
7+
public function __construct($fname, $nameSpace){
8+
parent::__construct(sprintf('Unable to find file by: %s in %s', $fname,$nameSpace));
99
}
1010
}

src/GridFS/Bucket.php

Lines changed: 103 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@
1919
class Bucket
2020
{
2121
private $databaseName;
22+
private $collectionsWrapper;
2223
private $options;
23-
private $filesCollection;
24-
private $chunksCollection;
25-
private $ensuredIndexes = false;
2624
/**
2725
* Constructs a GridFS bucket.
2826
*
@@ -50,122 +48,124 @@ public function __construct(Manager $manager, $databaseName, array $options = []
5048
'bucketName' => 'fs',
5149
'chunkSizeBytes' => 261120,
5250
];
53-
if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
54-
throw new InvalidArgumentTypeException('"bucketName" option', $options['bucketName'], 'string');
55-
}
56-
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
57-
throw new InvalidArgumentTypeException('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
58-
}
59-
if (isset($options['readPreference'])) {
60-
if (! $options['readPreference'] instanceof ReadPreference) {
61-
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
62-
} else {
63-
$collectionOptions['readPreference'] = $options['readPreference'];
64-
}
65-
}
66-
if (isset($options['writeConcern'])) {
67-
if (! $options['writeConcern'] instanceof WriteConcern) {
68-
throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
69-
} else {
70-
$collectionOptions['writeConcern'] = $options['writeConcern'];
71-
}
72-
}
7351
$this->databaseName = (string) $databaseName;
7452
$this->options = $options;
75-
76-
$this->filesCollection = new Collection(
77-
$manager,
78-
sprintf('%s.%s.files', $this->databaseName, $options['bucketName']),
79-
$collectionOptions
80-
);
81-
$this->chunksCollection = new Collection(
82-
$manager,
83-
sprintf('%s.%s.chunks', $this->databaseName, $options['bucketName']),
84-
$collectionOptions
85-
);
53+
$this->collectionsWrapper = new GridFSCollectionsWrapper($manager, $databaseName, $options);
8654
}
55+
8756
/**
88-
* Return the chunkSizeBytes option for this Bucket.
57+
* Opens a Stream for writing the contents of a file.
8958
*
90-
* @return integer
59+
* @param string $filename file to upload
60+
* @param array $options Stream Options
61+
* @return Stream uploadStream
9162
*/
92-
public function getChunkSizeBytes()
93-
{
94-
return $this->options['chunkSizeBytes'];
95-
}
96-
97-
public function getDatabaseName()
63+
public function openUploadStream($filename, array $options = [])
9864
{
99-
return $this->databaseName;
65+
$options+= ['chunkSizeBytes' => $this->options['chunkSizeBytes']];
66+
$streamOptions = [
67+
'collectionsWrapper' => $this->collectionsWrapper,
68+
'uploadOptions' => $options
69+
];
70+
$context = stream_context_create(['gridfs' => $streamOptions]);
71+
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $filename), 'w', false, $context);
10072
}
101-
public function getFilesCollection()
102-
{
103-
return $this->filesCollection;
104-
}
105-
106-
public function getChunksCollection()
107-
{
108-
return $this->chunksCollection;
109-
}
110-
public function getBucketName()
73+
/**
74+
* Upload a file to this bucket by specifying the source stream file
75+
*
76+
* @param String $filename Filename To Insert
77+
* @param Stream $source Source Stream
78+
* @param array $options Stream Options
79+
* @return ObjectId
80+
*/
81+
public function uploadFromStream($filename, $source, array $options = [])
11182
{
112-
return $this->options['bucketName'];
83+
$options['chunkSizeBytes'] = $this->options['chunkSizeBytes'];
84+
$gridFsStream = new GridFsUpload($this->collectionsWrapper, $filename, $options);
85+
return $gridFsStream->uploadFromStream($source);
11386
}
114-
public function getReadConcern()
87+
/**
88+
* Opens a Stream for reading the contents of a file specified by ID.
89+
*
90+
* @param ObjectId $id
91+
* @return Stream
92+
*/
93+
public function openDownloadStream(\MongoDB\BSON\ObjectId $id)
11594
{
116-
if(isset($this->options['readPreference'])) {
117-
return $this->options['readPreference'];
118-
} else{
119-
return null;
120-
}
95+
$options = [
96+
'collectionsWrapper' => $this->collectionsWrapper
97+
];
98+
$context = stream_context_create(['gridfs' => $options]);
99+
return fopen(sprintf('gridfs://%s/%s', $this->databaseName, $id), 'r', false, $context);
121100
}
122-
public function getWriteConcern()
101+
/**
102+
* Downloads the contents of the stored file specified by id and writes
103+
* the contents to the destination Stream.
104+
* @param ObjectId $id GridFS File Id
105+
* @param Stream $destination Destination Stream
106+
*/
107+
public function downloadToStream(\MongoDB\BSON\ObjectId $id, $destination)
123108
{
124-
if(isset($this->options['writeConcern'])) {
125-
return $this->options['writeConcern'];
126-
} else{
127-
return null;
128-
}
109+
$gridFsStream = new GridFsDownload($this->collectionsWrapper, $id);
110+
$gridFsStream->downloadToStream($destination);
129111
}
130-
131-
private function ensureIndexes()
112+
/**
113+
* Delete a file from the GridFS bucket. If the file collection entry is not found, still attempts to delete orphaned chunks
114+
*
115+
* @param ObjectId $id file id
116+
* @throws GridFSFileNotFoundException
117+
*/
118+
public function delete(\MongoDB\BSON\ObjectId $id)
132119
{
133-
if ($this->ensuredIndexes) {
134-
return;
135-
}
136-
if ( ! $this->isFilesCollectionEmpty()) {
137-
return;
120+
$file = $this->collectionsWrapper->getFilesCollection()->findOne(['_id' => $id]);
121+
$this->collectionsWrapper->getChunksCollection()->deleteMany(['files_id' => $id]);
122+
if (is_null($file)) {
123+
throw new \MongoDB\Exception\GridFSFileNotFoundException($id, $this->collectionsWrapper->getFilesCollection()->getNameSpace());
138124
}
139-
$this->ensureFilesIndex();
140-
$this->ensureChunksIndex();
141-
$this->ensuredIndexes = true;
125+
126+
$this->collectionsWrapper->getFilesCollection()->deleteOne(['_id' => $id]);
142127
}
143-
private function ensureChunksIndex()
128+
/**
129+
* Open a stream to download a file from the GridFS bucket. Searches for the file by the specified name then returns a stream to the specified file
130+
* @param string $filename name of the file to download
131+
* @param int $revision the revision of the file to download
132+
* @throws GridFSFileNotFoundException
133+
*/
134+
public function openDownloadStreamByName($filename, $revision = -1)
144135
{
145-
foreach ($this->chunksCollection->listIndexes() as $index) {
146-
if ($index->isUnique() && $index->getKey() === ['files_id' => 1, 'n' => 1]) {
147-
return;
148-
}
149-
}
150-
$this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
136+
$file = $this->bucket->findFileRevision($filename, $revision);
137+
$options = ['bucket' => $this->bucket,
138+
'file' => $file
139+
];
140+
$context = stream_context_create(['gridfs' => $options]);
141+
return fopen(sprintf('gridfs://%s/%s', $this->bucket->getDatabaseName(), $filename), 'r', false, $context);
151142
}
152-
private function ensureFilesIndex()
143+
/**
144+
* Download a file from the GridFS bucket by name. Searches for the file by the specified name then loads data into the stream
145+
*
146+
* @param string $filename name of the file to download
147+
* @param int $revision the revision of the file to download
148+
* @throws GridFSFileNotFoundException
149+
*/
150+
public function downloadToStreamByName($filename, $destination, $revision=-1)
153151
{
154-
foreach ($this->filesCollection->listIndexes() as $index) {
155-
if ($index->getKey() === ['filename' => 1, 'uploadDate' => 1]) {
156-
return;
157-
}
158-
}
159-
$this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
152+
$file = $this->findFileRevision($filename, $revision);
153+
$gridFsStream = new GridFsDownload($this->collectionsWrapper, null, $file);
154+
$gridFsStream->downloadToStream($destination);
160155
}
161-
private function isFilesCollectionEmpty()
156+
/**
157+
* Find files from the GridFS bucket files collection.
158+
*
159+
* @param array $filter filter to find by
160+
* @param array $options options to
161+
*/
162+
public function find($filter, array $options =[])
162163
{
163-
return null === $this->filesCollection->findOne([], [
164-
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
165-
'projection' => ['_id' => 1],
166-
]);
164+
return $this->collectionsWrapper->getFilesCollection()->find($filter, $options);
167165
}
168-
public function findFileRevision($filename, $revision)
166+
167+
168+
private function findFileRevision($filename, $revision)
169169
{
170170
if ($revision < 0) {
171171
$skip = abs($revision) -1;
@@ -174,10 +174,15 @@ public function findFileRevision($filename, $revision)
174174
$skip = $revision;
175175
$sortOrder = 1;
176176
}
177-
$file = $this->filesCollection->findOne(["filename"=> $filename], ["sort" => ["uploadDate"=> $sortOrder], "limit"=>1, "skip" => $skip]);
177+
$filesCollection = $this->collectionsWrapper->getFilesCollection();
178+
$file = $filesCollection->findOne(["filename"=> $filename], ["sort" => ["uploadDate"=> $sortOrder], "limit"=>1, "skip" => $skip]);
178179
if(is_null($file)) {
179-
throw new \MongoDB\Exception\GridFSFileNotFoundException($filename, $this->getBucketName(), $this->getDatabaseName());;
180+
throw new \MongoDB\Exception\GridFSFileNotFoundException($filename, $filesCollection->getNameSpace());
180181
}
181182
return $file;
182183
}
184+
public function getCollectionsWrapper()
185+
{
186+
return $this->collectionsWrapper;
187+
}
183188
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
namespace MongoDB\GridFS;
3+
4+
use MongoDB\Collection;
5+
use MongoDB\Database;
6+
use MongoDB\BSON\ObjectId;
7+
use MongoDB\Driver\ReadPreference;
8+
use MongoDB\Driver\WriteConcern;
9+
use MongoDB\Driver\Manager;
10+
use MongoDB\Exception\InvalidArgumentException;
11+
use MongoDB\Exception\InvalidArgumentTypeException;
12+
use MongoDB\Exception\RuntimeException;
13+
use MongoDB\Exception\UnexpectedValueException;
14+
/**
15+
* Bucket abstracts the GridFS files and chunks collections.
16+
*
17+
* @api
18+
*/
19+
class GridFSCollectionsWrapper
20+
{
21+
private $filesCollection;
22+
private $chunksCollection;
23+
private $ensuredIndexes = false;
24+
/**
25+
* Constructs a GridFS bucket.
26+
*
27+
* Supported options:
28+
*
29+
* * bucketName (string): The bucket name, which will be used as a prefix
30+
* for the files and chunks collections. Defaults to "fs".
31+
*
32+
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
33+
* 261120 (i.e. 255 KiB).
34+
*
35+
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
36+
*
37+
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
38+
*
39+
* @param Manager $manager Manager instance from the driver
40+
* @param string $databaseName Database name
41+
* @param array $options Bucket options
42+
* @throws InvalidArgumentException
43+
*/
44+
public function __construct(Manager $manager, $databaseName, $options)
45+
{
46+
$collectionOptions = [];
47+
$options += [
48+
'bucketName' => 'fs',
49+
];
50+
if (isset($options['readPreference'])) {
51+
if (! $options['readPreference'] instanceof ReadPreference) {
52+
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
53+
} else {
54+
$collectionOptions['readPreference'] = $options['readPreference'];
55+
}
56+
}
57+
if (isset($options['writeConcern'])) {
58+
if (! $options['writeConcern'] instanceof WriteConcern) {
59+
throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
60+
} else {
61+
$collectionOptions['writeConcern'] = $options['writeConcern'];
62+
}
63+
}
64+
$this->filesCollection = new Collection(
65+
$manager,
66+
sprintf('%s.%s.files', $databaseName, $options['bucketName']),
67+
$collectionOptions
68+
);
69+
$this->chunksCollection = new Collection(
70+
$manager,
71+
sprintf('%s.%s.chunks', $databaseName, $options['bucketName']),
72+
$collectionOptions
73+
);
74+
}
75+
76+
public function chunkInsert($toUpload)
77+
{
78+
$this->ensureIndexes();
79+
$this->chunksCollection->insertOne($toUpload);
80+
}
81+
public function fileInsert($toUpload)
82+
{
83+
$this->ensureIndexes();
84+
$this->filesCollection->insertOne($toUpload);
85+
}
86+
public function getChunksCollection()
87+
{
88+
return $this->chunksCollection;
89+
}
90+
public function getFilesCollection()
91+
{
92+
return $this->filesCollection;
93+
}
94+
95+
96+
private function ensureIndexes()
97+
{
98+
if ($this->ensuredIndexes) {
99+
return;
100+
}
101+
if ( ! $this->isFilesCollectionEmpty()) {
102+
return;
103+
}
104+
$this->ensureFilesIndex();
105+
$this->ensureChunksIndex();
106+
$this->ensuredIndexes = true;
107+
}
108+
private function ensureChunksIndex()
109+
{
110+
foreach ($this->chunksCollection->listIndexes() as $index) {
111+
if ($index->isUnique() && $index->getKey() === ['files_id' => 1, 'n' => 1]) {
112+
return;
113+
}
114+
}
115+
$this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
116+
}
117+
private function ensureFilesIndex()
118+
{
119+
foreach ($this->filesCollection->listIndexes() as $index) {
120+
if ($index->getKey() === ['filename' => 1, 'uploadDate' => 1]) {
121+
return;
122+
}
123+
}
124+
$this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
125+
}
126+
private function isFilesCollectionEmpty()
127+
{
128+
return null === $this->filesCollection->findOne([], [
129+
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
130+
'projection' => ['_id' => 1],
131+
]);
132+
}
133+
}

0 commit comments

Comments
 (0)