Skip to content

Commit f03a992

Browse files
committed
Merge pull request #18 from daniel-sc/master
Integration with MongoDB
2 parents de7151c + a8b6021 commit f03a992

File tree

6 files changed

+288
-1
lines changed

6 files changed

+288
-1
lines changed

src/Flow/File.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class File
77
/**
88
* @var RequestInterface
99
*/
10-
private $request;
10+
protected $request;
1111

1212
/**
1313
* @var ConfigInterface

src/Flow/Mongo/MongoConfig.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\Config;
6+
7+
class MongoConfig extends Config implements MongoConfigInterface
8+
{
9+
private $gridFs;
10+
11+
/**
12+
* @param \MongoGridFS $gridFS storage of the upload (and chunks)
13+
*/
14+
function __construct(\MongoGridFS $gridFS)
15+
{
16+
parent::__construct();
17+
$this->gridFs = $gridFS;
18+
}
19+
20+
21+
/**
22+
* @return \MongoGridFS
23+
*/
24+
public function getGridFs()
25+
{
26+
return $this->gridFs;
27+
}
28+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\ConfigInterface;
6+
7+
/**
8+
* @package Flow
9+
*/
10+
interface MongoConfigInterface extends ConfigInterface
11+
{
12+
13+
/**
14+
* @return \MongoGridFS
15+
*/
16+
public function getGridFs();
17+
18+
}

src/Flow/Mongo/MongoFile.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\File;
6+
use Flow\Request;
7+
use Flow\RequestInterface;
8+
9+
10+
/**
11+
* Notes:
12+
*
13+
* - One should ensure indices on the gridfs collection on the property 'flowIdentifier'.
14+
* - Chunk preprocessor not supported (must not modify chunks size)!
15+
* - Must use 'forceChunkSize=true' on client side.
16+
*
17+
* @package Flow
18+
*/
19+
class MongoFile extends File
20+
{
21+
private $uploadGridFsFile;
22+
23+
/**
24+
* @var MongoConfigInterface
25+
*/
26+
private $config;
27+
28+
function __construct(MongoConfigInterface $config, RequestInterface $request = null)
29+
{
30+
if ($request === null) {
31+
$request = new Request();
32+
}
33+
parent::__construct($config, $request);
34+
$this->config = $config;
35+
}
36+
37+
/**
38+
* return array
39+
*/
40+
protected function getGridFsFile()
41+
{
42+
if (!$this->uploadGridFsFile) {
43+
$gridFsFileQuery = $this->getGridFsFileQuery();
44+
$changed = $gridFsFileQuery;
45+
$changed['flowUpdated'] = new \MongoDate();
46+
$this->uploadGridFsFile = $this->config->getGridFs()->findAndModify($gridFsFileQuery, $changed, null,
47+
['upsert' => true]);
48+
}
49+
50+
return $this->uploadGridFsFile;
51+
}
52+
53+
/**
54+
* @param $index int|string 1-based
55+
* @return bool
56+
*/
57+
public function chunkExists($index)
58+
{
59+
return $this->config->getGridFs()->chunks->find([
60+
'files_id' => $this->getGridFsFile()['_id'],
61+
'n' => (intval($index) - 1)
62+
])->limit(1)->hasNext();
63+
}
64+
65+
public function checkChunk()
66+
{
67+
return $this->chunkExists($this->request->getCurrentChunkNumber());
68+
}
69+
70+
/**
71+
* Save chunk
72+
*
73+
* @return bool
74+
*/
75+
public function saveChunk()
76+
{
77+
try {
78+
$file = $this->request->getFile();
79+
80+
$chunkQuery = [
81+
'files_id' => $this->getGridFsFile()['_id'],
82+
'n' => intval($this->request->getCurrentChunkNumber()) - 1,
83+
];
84+
$chunk = $chunkQuery;
85+
$data = file_get_contents($file['tmp_name']);
86+
$actualChunkSize = strlen($data);
87+
if ($actualChunkSize > $this->request->getDefaultChunkSize() ||
88+
($actualChunkSize < $this->request->getDefaultChunkSize() &&
89+
$this->request->getCurrentChunkNumber() != $this->request->getTotalChunks())
90+
) {
91+
throw new \Exception("Invalid upload! (size: {$actualChunkSize})");
92+
}
93+
$chunk['data'] = new \MongoBinData($data);
94+
$this->config->getGridFs()->chunks->findAndModify($chunkQuery, $chunk, [], ['upsert' => true]);
95+
unlink($file['tmp_name']);
96+
97+
return true;
98+
} catch (\Exception $e) {
99+
return false;
100+
}
101+
}
102+
103+
/**
104+
* @return bool
105+
*/
106+
public function validateFile()
107+
{
108+
$totalChunks = $this->request->getTotalChunks();
109+
110+
for ($i = 1; $i <= $totalChunks; $i++) {
111+
if (!$this->chunkExists($i)) {
112+
return false;
113+
}
114+
}
115+
116+
return true;
117+
}
118+
119+
120+
/**
121+
* Merge all chunks to single file
122+
* @param $metadata array additional metadata for final file
123+
* @return \MongoId|bool of saved file or false if file was already saved
124+
* @throws \Exception
125+
*/
126+
public function saveToGridFs($metadata = null)
127+
{
128+
$file = $this->getGridFsFile();
129+
$file['flowStatus'] = 'finished';
130+
$file['metadata'] = $metadata;
131+
$result = $this->config->getGridFs()->findAndModify($this->getGridFsFileQuery(), $file);
132+
// on second invocation no more file can be found, as the flowStatus changed:
133+
if (is_null($result)) {
134+
return false;
135+
} else {
136+
return $file['_id'];
137+
}
138+
}
139+
140+
public function save($destination)
141+
{
142+
throw new \Exception("Must not use 'save' on MongoFile - use 'saveToGridFs'!");
143+
}
144+
145+
public function deleteChunks()
146+
{
147+
// nothing to do, as chunks are directly part of the final file
148+
}
149+
150+
/**
151+
* @return array
152+
*/
153+
protected function getGridFsFileQuery()
154+
{
155+
return [
156+
'flowIdentifier' => $this->request->getIdentifier(),
157+
'flowStatus' => 'uploading',
158+
'filename' => $this->request->getFileName(),
159+
'chunkSize' => intval($this->request->getDefaultChunkSize()),
160+
'length' => intval($this->request->getTotalSize())
161+
];
162+
}
163+
}

src/Flow/Mongo/MongoUploader.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\FileOpenException;
6+
7+
class MongoUploader
8+
{
9+
/**
10+
* Delete chunks older than expiration time.
11+
*
12+
* @param \MongoGridFS $gridFs
13+
* @param int $expirationTime seconds
14+
*
15+
* @throws FileOpenException
16+
*/
17+
public static function pruneChunks($gridFs, $expirationTime = 172800)
18+
{
19+
$result = $gridFs->remove([
20+
'flowUpdated' => ['$lt' => new \MongoDate(time() - $expirationTime)],
21+
'flowStatus' => 'uploading'
22+
]);
23+
24+
if (!$result) {
25+
throw new FileOpenException("Could not remove chunks!");
26+
}
27+
}
28+
}

src/Flow/Mongo/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Usage
2+
--------------
3+
4+
* Must use 'forceChunkSize=true' on client side.
5+
* Chunk preprocessor not supported.
6+
* One should ensure indices on the gridfs collection on the property 'flowIdentifier'.
7+
8+
Besides the points above, the usage is analogous to the 'normal' flow-php:
9+
10+
```php
11+
$config = new \Flow\Mongo\MongoConfig($yourGridFs);
12+
$file = new \Flow\Mongo\MongoFile($config);
13+
14+
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
15+
if ($file->checkChunk()) {
16+
header("HTTP/1.1 200 Ok");
17+
} else {
18+
header("HTTP/1.1 204 No Content");
19+
return ;
20+
}
21+
} else {
22+
if ($file->validateChunk()) {
23+
$file->saveChunk();
24+
} else {
25+
// error, invalid chunk upload request, retry
26+
header("HTTP/1.1 400 Bad Request");
27+
return ;
28+
}
29+
}
30+
if ($file->validateFile()) {
31+
// File upload was completed
32+
$id = $file->saveToGridFs(['your metadata'=>'value']);
33+
if($id) {
34+
//do custom post processing here, $id is the MongoId of the gridfs file
35+
}
36+
} else {
37+
// This is not a final chunk, continue to upload
38+
}
39+
```
40+
41+
Delete unfinished files
42+
-----------------------
43+
44+
For this you should setup cron, which would check each chunk upload time.
45+
If chunk is uploaded long time ago, then chunk should be deleted.
46+
47+
Helper method for checking this:
48+
```php
49+
\Flow\Mongo\MongoUploader::pruneChunks($yourGridFs);
50+
```

0 commit comments

Comments
 (0)