Skip to content

Commit 56023b0

Browse files
author
Will Banfield
committed
PHPLIB-146: Implement GridFS upload, plus initial GridFS commit
1 parent 6faa366 commit 56023b0

18 files changed

+2837
-3
lines changed

src/GridFS/Bucket.php

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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 Bucket
20+
{
21+
private $databaseName;
22+
private $options;
23+
private $filesCollection;
24+
private $chunksCollection;
25+
private $indexChecker;
26+
private $ensuredIndexes = false;
27+
/**
28+
* Constructs a GridFS bucket.
29+
*
30+
* Supported options:
31+
*
32+
* * bucketName (string): The bucket name, which will be used as a prefix
33+
* for the files and chunks collections. Defaults to "fs".
34+
*
35+
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
36+
* 261120 (i.e. 255 KiB).
37+
*
38+
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
39+
*
40+
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
41+
*
42+
* @param Manager $manager Manager instance from the driver
43+
* @param string $databaseName Database name
44+
* @param array $options Bucket options
45+
* @throws InvalidArgumentException
46+
*/
47+
public function __construct(Manager $manager, $databaseName, array $options = [])
48+
{
49+
$collectionOptions = [];
50+
$options += [
51+
'bucketName' => 'fs',
52+
'chunkSizeBytes' => 261120,
53+
];
54+
if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
55+
throw new InvalidArgumentTypeException('"bucketName" option', $options['bucketName'], 'string');
56+
}
57+
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
58+
throw new InvalidArgumentTypeException('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
59+
}
60+
if (isset($options['readPreference'])) {
61+
if (! $options['readPreference'] instanceof ReadPreference) {
62+
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
63+
} else {
64+
$collectionOptions['readPreference'] = $options['readPreference'];
65+
}
66+
}
67+
if (isset($options['writeConcern'])) {
68+
if (! $options['writeConcern'] instanceof WriteConcern) {
69+
throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
70+
} else {
71+
$collectionOptions['writeConcern'] = $options['writeConcern'];
72+
}
73+
}
74+
$this->databaseName = (string) $databaseName;
75+
$this->options = $options;
76+
77+
$this->filesCollection = new Collection(
78+
$manager,
79+
sprintf('%s.%s.files', $this->databaseName, $options['bucketName']),
80+
$collectionOptions
81+
);
82+
$this->chunksCollection = new Collection(
83+
$manager,
84+
sprintf('%s.%s.chunks', $this->databaseName, $options['bucketName']),
85+
$collectionOptions
86+
);
87+
}
88+
/**
89+
* Opens a Stream for reading the contents of a file specified by ID.
90+
*
91+
* @param ObjectId $id
92+
* @return Stream
93+
*/
94+
public function openDownloadStream(ObjectId $id)
95+
{
96+
fopen('gridfs://$this->databaseName/$id', 'r');
97+
}
98+
/**
99+
* Downloads the contents of the stored file specified by id and writes
100+
* the contents to the destination Stream.
101+
* @param ObjectId $id GridFS File Id
102+
* @param Stream $destination Destination Stream
103+
*/
104+
public function downloadToStream(ObjectId $id, $destination)
105+
{
106+
$result = $this->filesCollection->findOne(['_id' => $id]);
107+
if ($result == null) {
108+
return;
109+
}
110+
if ($result->length == 0){
111+
return;
112+
}
113+
114+
$n=0;
115+
$results = $this->chunksCollection->find(['files_id' => $result->_id]);
116+
foreach ($results as $chunk) {
117+
if ($chunk->n != $n) {
118+
return;
119+
}
120+
fwrite($destination, $chunk->data);
121+
$n++;
122+
}
123+
}
124+
/**
125+
* Return the chunkSizeBytes option for this Bucket.
126+
*
127+
* @return integer
128+
*/
129+
public function getChunkSizeBytes()
130+
{
131+
return $this->options['chunkSizeBytes'];
132+
}
133+
public function getDatabaseName()
134+
{
135+
return $this->databaseName;
136+
}
137+
public function getFilesCollection()
138+
{
139+
return $this->filesCollection;
140+
}
141+
public function getChunksCollection()
142+
{
143+
return $this->chunksCollection;
144+
}
145+
public function find($filter, array $options =[])
146+
{
147+
//add proper validation for the filter and for the options
148+
return $this->filesCollection->find($filter);
149+
}
150+
151+
public function chunkInsert($toUpload) {
152+
$this->chunksCollection->insertOne($toUpload);
153+
}
154+
155+
public function fileInsert($toUpload) {
156+
$this->filesCollection->insertOne($toUpload);
157+
}
158+
159+
public function ensureIndexes()
160+
{
161+
if ($this->ensuredIndexes) {
162+
return;
163+
}
164+
if ( ! $this->isFilesCollectionEmpty()) {
165+
return;
166+
}
167+
$this->ensureFilesIndex();
168+
$this->ensureChunksIndex();
169+
$this->ensuredIndexes = true;
170+
}
171+
172+
private function ensureChunksIndex()
173+
{
174+
foreach ($this->chunksCollection->listIndexes() as $index) {
175+
if ($index->isUnique() && $index->getKey() === ['files_id' => 1, 'n' => 1]) {
176+
return;
177+
}
178+
}
179+
$this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
180+
}
181+
182+
private function ensureFilesIndex()
183+
{
184+
foreach ($this->filesCollection->listIndexes() as $index) {
185+
if ($index->getKey() === ['filename' => 1, 'uploadDate' => 1]) {
186+
return;
187+
}
188+
}
189+
$this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
190+
}
191+
192+
private function isFilesCollectionEmpty()
193+
{
194+
return null === $this->filesCollection->findOne([], [
195+
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
196+
'projection' => ['_id' => 1],
197+
]);
198+
}
199+
200+
public function delete(ObjectId $id)
201+
{
202+
$options = ['writeConcern' => $this->writeConcern];
203+
$this->chunksCollection->deleteMany(['file_id' => $id], $options);
204+
$this->filesCollection->deleteOne(['_id' => $id], $options);
205+
}
206+
}

src/GridFS/BucketReadWriter.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
namespace MongoDB\GridFS;
3+
4+
class BucketReadWriter
5+
{
6+
private $bucket;
7+
8+
public function __construct(Bucket $bucket)
9+
{
10+
$this->bucket = $bucket;
11+
}
12+
/**
13+
* Opens a Stream for writing the contents of a file.
14+
*
15+
* @param string $filename file to upload
16+
* @param array $options Stream Options
17+
* @return Stream uploadStream
18+
*/
19+
public function openUploadStream($filename, array $options = [])
20+
{
21+
$options = [
22+
'bucket' => $this->bucket,
23+
'uploadOptions' => $options
24+
];
25+
$context = stream_context_create(['gridfs' => $options]);
26+
return fopen(sprintf('gridfs://%s/%s', $this->bucket->getDatabaseName(), $filename), 'w', false, $context);
27+
}
28+
/**
29+
* Upload a file to this bucket by specifying the source stream file
30+
*
31+
* @param String $filename Filename To Insert
32+
* @param Stream $source Source Stream
33+
* @param array $options Stream Options
34+
* @return ObjectId
35+
*/
36+
public function uploadFromStream($filename, $source, array $options = [])
37+
{
38+
$gridFsStream = new GridFsUpload($this->bucket, $filename, $options);
39+
return $gridFsStream->uploadFromStream($source);
40+
}
41+
}

src/GridFS/GridFsStream.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
namespace MongoDB\GridFS;
3+
4+
use MongoDB\Collection;
5+
use MongoDB\Exception\RuntimeException;
6+
/**
7+
* GridFsStream holds the configuration for upload or download streams to GridFS
8+
*
9+
* @api
10+
*/
11+
class GridFsStream
12+
{
13+
protected $bucket;
14+
protected $n;
15+
protected $buffer;
16+
protected $file;
17+
/**
18+
* Constructs a GridFsStream
19+
*
20+
* @param \MongoDB\GridFS\Bucket $bucket GridFS Bucket
21+
*/
22+
public function __construct(Bucket $bucket)
23+
{
24+
$this->bucket = $bucket;
25+
$this->n = 0;
26+
$this->buffer = fopen('php://temp', 'w+');
27+
}
28+
}

0 commit comments

Comments
 (0)