diff --git a/Form/FormFlow.php b/Form/FormFlow.php
index 3927c39e..b9c2fbc3 100644
--- a/Form/FormFlow.php
+++ b/Form/FormFlow.php
@@ -70,6 +70,16 @@ abstract class FormFlow implements FormFlowInterface {
*/
protected $handleFileUploads = true;
+ /**
+ * @var bool If file uploads should be handled with Gaufrette
+ */
+ protected $handleFileUploadsWithGaufrette = false;
+
+ /**
+ * @var string Filesystem that Gaufrette will use
+ */
+ protected $gaufretteFilesystem = null;
+
/**
* @var string|null Directory for storing temporary files while handling uploads. If null, the system's default will be used.
*/
@@ -384,6 +394,20 @@ public function getHandleFileUploadsTempDir() {
return $this->handleFileUploadsTempDir;
}
+ /**
+ * {@inheritDoc}
+ */
+ public function isHandleFileUploadsWithGaufrette() {
+ return $this->handleFileUploadsWithGaufrette;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getGaufretteFilesystem() {
+ return $this->gaufretteFilesystem;
+ }
+
public function setAllowRedirectAfterSubmit($allowRedirectAfterSubmit) {
$this->allowRedirectAfterSubmit = (bool) $allowRedirectAfterSubmit;
}
@@ -459,6 +483,10 @@ protected function applySkipping($stepNumber, $direction = 1) {
* {@inheritDoc}
*/
public function reset() {
+ if(!empty($this->currentStepNumber) && $this->getCurrentStepNumber() >= $this->getLastStepNumber()){
+ $this->getDataManager()->cleanup($this);
+ }
+
$this->dataManager->drop($this);
$this->currentStepNumber = $this->getFirstStepNumber();
$this->newInstance = true;
diff --git a/Form/FormFlowInterface.php b/Form/FormFlowInterface.php
index 57f4121c..bb0e3868 100644
--- a/Form/FormFlowInterface.php
+++ b/Form/FormFlowInterface.php
@@ -66,6 +66,16 @@ function isHandleFileUploads();
*/
function getHandleFileUploadsTempDir();
+ /**
+ * @var bool If file uploads should be handled with Gaufrette
+ */
+ function isHandleFileUploadsWithGaufrette();
+
+ /**
+ * @return string|null Filesystem that Gaufrette will use
+ */
+ function getGaufretteFilesystem();
+
/**
* @return bool
*/
diff --git a/README.md b/README.md
index 065196e3..1f5206c0 100644
--- a/README.md
+++ b/README.md
@@ -588,6 +588,23 @@ class CreateVehicleFlow extends FormFlow {
}
```
+File uploads can be handled by Gaufrette, a library that provides a filesystem abstraction layer.This can be very useful when yo dou a cluster deployment or you store sessions on Redis.
+Just define a Gaufrette filesystem and add this 2 lines:
+
+```php
+// in src/MyCompany/MyBundle/Form/CreateVehicleFlow.php
+class CreateVehicleFlow extends FormFlow {
+
+ protected $handleFileUploadsWithGaufrette = true;
+ protected $gaufretteFilesystem = 'tmp_uploads';
+
+ // ...
+
+}
+```
+More info here:
+https://github.com/KnpLabs/KnpGaufretteBundle#configuring-the-filesystems
+
## Enabling redirect after submit
This feature will allow performing a redirect after submitting a step to load the page containing the next step using a GET request.
diff --git a/Resources/config/form_flow.xml b/Resources/config/form_flow.xml
index 38308211..75fcdd08 100644
--- a/Resources/config/form_flow.xml
+++ b/Resources/config/form_flow.xml
@@ -12,6 +12,7 @@
Craue\FormFlowBundle\Form\FormFlow
Craue\FormFlowBundle\Storage\SessionStorage
+ Craue\FormFlowBundle\Storage\GaufretteStorage
Craue\FormFlowBundle\EventListener\PreviousStepInvalidEventListener
Craue\FormFlowBundle\Form\FormFlowEvents::PREVIOUS_STEP_INVALID
Craue\FormFlowBundle\EventListener\FlowExpiredEventListener
@@ -25,8 +26,15 @@
+
+
+
+
+
+
+
diff --git a/Storage/DataManager.php b/Storage/DataManager.php
index 893820b0..2ad9b13a 100644
--- a/Storage/DataManager.php
+++ b/Storage/DataManager.php
@@ -3,6 +3,7 @@
namespace Craue\FormFlowBundle\Storage;
use Craue\FormFlowBundle\Form\FormFlowInterface;
+use Gaufrette\File;
/**
* Manages data of flows and their steps.
@@ -34,11 +35,18 @@ class DataManager implements ExtendedDataManagerInterface {
*/
private $storage;
+ /**
+ * @var GaufretteStorage
+ */
+ private $gaufretteStorage;
+
/**
* @param StorageInterface $storage
+ * @param GaufretteStorage $gaufretteStorage
*/
- public function __construct(StorageInterface $storage) {
+ public function __construct(StorageInterface $storage, GaufretteStorage $gaufretteStorage) {
$this->storage = $storage;
+ $this->gaufretteStorage = $gaufretteStorage;
}
/**
@@ -48,15 +56,32 @@ public function getStorage() {
return $this->storage;
}
+ /**
+ * {@inheritDoc}
+ */
+ public function getGaufretteStorage() {
+ return $this->gaufretteStorage;
+ }
+
/**
* {@inheritDoc}
*/
public function save(FormFlowInterface $flow, array $data) {
// handle file uploads
if ($flow->isHandleFileUploads()) {
- array_walk_recursive($data, function(&$value, $key) {
- if (SerializableFile::isSupported($value)) {
- $value = new SerializableFile($value);
+ array_walk_recursive($data, function(&$value, $key) use ($flow) {
+ if(!$flow->isHandleFileUploadsWithGaufrette()){
+ if (SerializableFile::isSupported($value)) {
+ $value = new SerializableFile($value);
+ }
+ }else{
+ if (GaufretteFile::isSupported($value)) {
+ $fileName = $value->getClientOriginalName();
+ if(!$this->gaufretteStorage->hasFile($flow->getGaufretteFilesystem(), $fileName)){
+ $fileName = $this->gaufretteStorage->doUpload($flow->getGaufretteFilesystem(), $value);
+ }
+ $value = new GaufretteFile($fileName, $value);
+ }
}
});
}
@@ -93,9 +118,16 @@ public function load(FormFlowInterface $flow) {
// handle file uploads
if ($flow->isHandleFileUploads()) {
$tempDir = $flow->getHandleFileUploadsTempDir();
- array_walk_recursive($data, function(&$value, $key) use ($tempDir) {
- if ($value instanceof SerializableFile) {
- $value = $value->getAsFile($tempDir);
+ array_walk_recursive($data, function(&$value, $key) use ($flow, $tempDir) {
+ if(!$flow->isHandleFileUploadsWithGaufrette()){
+ if ($value instanceof SerializableFile) {
+ $value = $value->getAsFile($tempDir);
+ }
+ }else{
+ if ($value instanceof GaufretteFile) {
+ $downloadedFile = $this->gaufretteStorage->doDownload($flow->getGaufretteFilesystem(), $value);
+ $value = $value->getAsUploadedFile($downloadedFile);
+ }
}
});
}
@@ -111,6 +143,30 @@ public function exists(FormFlowInterface $flow) {
return isset($savedFlows[$flow->getName()][$flow->getInstanceId()][self::DATA_KEY]);
}
+ /**
+ * {@inheritDoc}
+ */
+ public function cleanup(FormFlowInterface $flow) {
+ $data = [];
+
+ // try to find data for the given flow
+ $savedFlows = $this->storage->get(DataManagerInterface::STORAGE_ROOT, []);
+ if (isset($savedFlows[$flow->getName()][$flow->getInstanceId()][self::DATA_KEY])) {
+ $data = $savedFlows[$flow->getName()][$flow->getInstanceId()][self::DATA_KEY];
+ }
+
+ // look for Gaufrette files to cleanup
+ if ($flow->isHandleFileUploads()) {
+ array_walk_recursive($data, function(&$value, $key) use ($flow) {
+ if($flow->isHandleFileUploadsWithGaufrette()){
+ if ($value instanceof GaufretteFile) {
+ $this->gaufretteStorage->doRemove($flow->getGaufretteFilesystem(), $value);
+ }
+ }
+ });
+ }
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/Storage/DataManagerInterface.php b/Storage/DataManagerInterface.php
index 07555428..e983b136 100644
--- a/Storage/DataManagerInterface.php
+++ b/Storage/DataManagerInterface.php
@@ -21,6 +21,11 @@ interface DataManagerInterface {
*/
function getStorage();
+ /**
+ * @return GaufretteStorage
+ */
+ function getGaufretteStorage();
+
/**
* Saves data of the given flow.
* @param FormFlowInterface $flow
@@ -42,6 +47,13 @@ function exists(FormFlowInterface $flow);
*/
function load(FormFlowInterface $flow);
+ /**
+ * Cleanups Gaufrette temp data of the given flow.
+ * @param FormFlowInterface $flow
+ * @return bool
+ */
+ function cleanup(FormFlowInterface $flow);
+
/**
* Drops data of the given flow.
* @param FormFlowInterface $flow
diff --git a/Storage/GaufretteFile.php b/Storage/GaufretteFile.php
new file mode 100644
index 00000000..88da3cdb
--- /dev/null
+++ b/Storage/GaufretteFile.php
@@ -0,0 +1,79 @@
+UploadedFile currently.
+ *
+ * @author Kevin Cerro
+ * @copyright 2020 Kevin Cerro
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ */
+class GaufretteFile
+{
+ /**
+ * @var string Name of the file provided by Gaufrette on upload
+ */
+ private $fileName;
+
+ private $clientMimeType;
+
+ /**
+ * @param string $filename
+ * @param $originalFile
+ */
+ public function __construct(string $filename, $originalFile)
+ {
+ if (!self::isSupported($originalFile)) {
+ throw new InvalidTypeException($originalFile, UploadedFile::class);
+ }
+
+ //Filename of uploaded file with Gaufrette
+ $this->fileName = $filename;
+
+ //Keep client original mime type
+ $this->clientMimeType = $originalFile->getClientMimeType();
+ }
+
+ /**
+ * @param File $file
+ * @return mixed The file retrieved from Gaufrette converted to UploadedFile
+ */
+ public function getAsUploadedFile(File $file)
+ {
+ $tempDir = sys_get_temp_dir();
+
+ // create a temporary file with its original content
+ $tempFile = tempnam($tempDir, 'craue_form_flow_serialized_file');
+ file_put_contents($tempFile, $file->getContent());
+
+ TempFileUtil::addTempFile($tempFile);
+
+ // avoid a deprecation notice regarding "passing a size as 4th argument to the constructor"
+ // TODO remove as soon as Symfony >= 4.1 is required
+ if (property_exists(UploadedFile::class, 'size')) {
+ return new UploadedFile($tempFile, $this->fileName, $this->clientMimeType, null, null, true);
+ }
+
+ return new UploadedFile($tempFile, $this->fileName, $this->clientMimeType, null, true);
+ }
+
+ public function getFileName()
+ {
+ return $this->fileName;
+ }
+
+ /**
+ * @param mixed $file
+ * @return bool
+ */
+ public static function isSupported($file)
+ {
+ return $file instanceof UploadedFile;
+ }
+}
diff --git a/Storage/GaufretteStorage.php b/Storage/GaufretteStorage.php
new file mode 100644
index 00000000..c86dea80
--- /dev/null
+++ b/Storage/GaufretteStorage.php
@@ -0,0 +1,88 @@
+
+ * @copyright 2020 Kevin Cerro
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ */
+class GaufretteStorage
+{
+ private $filesystemMap;
+
+ /**
+ * Constructs a new instance of GaufretteStorage.
+ *
+ * @param FilesystemMapInterface $filesystemMap
+ */
+ public function __construct(FilesystemMapInterface $filesystemMap)
+ {
+ $this->filesystemMap = $filesystemMap;
+ }
+
+ public function doUpload(string $filesystem, UploadedFile $file)
+ {
+ $filesystem = $this->getFilesystem($filesystem);
+ $randomName = $this->generateRandomName();
+
+ if ($filesystem->getAdapter() instanceof MetadataSupporter) {
+ $filesystem->getAdapter()->setMetadata($randomName, ['contentType' => $file->getMimeType()]);
+ }
+
+ $filesystem->write($randomName, file_get_contents($file->getPathname()), true);
+ return $randomName;
+ }
+
+ public function doDownload(string $filesystem, GaufretteFile $gaufretteFile)
+ {
+ $filesystem = $this->getFilesystem($filesystem);
+ return $filesystem->get($gaufretteFile->getFileName());
+ }
+
+ public function doRemove(string $filesystem, GaufretteFile $gaufretteFile)
+ {
+ $filesystem = $this->getFilesystem($filesystem);
+
+ try {
+ return $filesystem->delete($gaufretteFile->getFileName());
+ } catch (FileNotFound $e) {
+ return false;
+ }
+ }
+
+ public function hasFile(string $filesystem, string $fileName)
+ {
+ return $this->getFilesystem($filesystem)->has($fileName);
+ }
+
+ /**
+ * Get filesystem adapter from the property mapping.
+ * @param string $filesystem
+ * @return FilesystemInterface
+ */
+ private function getFilesystem(string $filesystem): FilesystemInterface
+ {
+ return $this->filesystemMap->get($filesystem);
+ }
+
+ /**
+ * Generates random name
+ * TODO May we can improve this by setting the extension on the random name
+ * @return string|string[]
+ */
+ private function generateRandomName()
+ {
+ return str_replace('.', '', \uniqid('', true));
+ }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 758d49c6..3acccd3e 100644
--- a/composer.json
+++ b/composer.json
@@ -17,6 +17,7 @@
],
"require": {
"php": "~7.0",
+ "knplabs/knp-gaufrette-bundle": "^0.7.1",
"symfony/config": "~3.4|~4.2|~5.0",
"symfony/dependency-injection": "~3.4|~4.2|~5.0",
"symfony/event-dispatcher": "~3.4|~4.2|~5.0",