Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Form/FormFlow.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>null</code>, the system's default will be used.
*/
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions Form/FormFlowInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions Resources/config/form_flow.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<parameters>
<parameter key="craue.form.flow.class">Craue\FormFlowBundle\Form\FormFlow</parameter>
<parameter key="craue.form.flow.storage.class">Craue\FormFlowBundle\Storage\SessionStorage</parameter>
<parameter key="craue.form.flow.gaufrette_storage.class">Craue\FormFlowBundle\Storage\GaufretteStorage</parameter>
<parameter key="craue.form.flow.event_listener.previous_step_invalid.class">Craue\FormFlowBundle\EventListener\PreviousStepInvalidEventListener</parameter>
<parameter key="craue.form.flow.event_listener.previous_step_invalid.event" type="constant">Craue\FormFlowBundle\Form\FormFlowEvents::PREVIOUS_STEP_INVALID</parameter>
<parameter key="craue.form.flow.event_listener.flow_expired.class">Craue\FormFlowBundle\EventListener\FlowExpiredEventListener</parameter>
Expand All @@ -25,8 +26,15 @@

<service id="craue.form.flow.storage" alias="craue.form.flow.storage_default" public="true" />

<service id="craue.form.flow.gaufrette_storage_default" class="%craue.form.flow.gaufrette_storage.class%" public="false">
<argument type="service" id="knp_gaufrette.filesystem_map" />
</service>

<service id="craue.form.flow.gaufrette_storage" alias="craue.form.flow.gaufrette_storage_default" public="true" />

<service id="craue.form.flow.data_manager_default" class="Craue\FormFlowBundle\Storage\DataManager" public="false">
<argument type="service" id="craue.form.flow.storage" />
<argument type="service" id="craue.form.flow.gaufrette_storage" />
</service>

<service id="craue.form.flow.data_manager" alias="craue.form.flow.data_manager_default" />
Expand Down
70 changes: 63 additions & 7 deletions Storage/DataManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Craue\FormFlowBundle\Storage;

use Craue\FormFlowBundle\Form\FormFlowInterface;
use Gaufrette\File;

/**
* Manages data of flows and their steps.
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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);
}
}
});
}
Expand Down Expand Up @@ -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);
}
}
});
}
Expand All @@ -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}
*/
Expand Down
12 changes: 12 additions & 0 deletions Storage/DataManagerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ interface DataManagerInterface {
*/
function getStorage();

/**
* @return GaufretteStorage
*/
function getGaufretteStorage();

/**
* Saves data of the given flow.
* @param FormFlowInterface $flow
Expand All @@ -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
Expand Down
79 changes: 79 additions & 0 deletions Storage/GaufretteFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Craue\FormFlowBundle\Storage;

use Craue\FormFlowBundle\Exception\InvalidTypeException;
use Craue\FormFlowBundle\Util\TempFileUtil;
use Gaufrette\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;

/**
* Representation of a file handled with Gaufrette. Only supports <code>UploadedFile</code> currently.
*
* @author Kevin Cerro <kevincerro1997@gmail.com>
* @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;
}
}
Loading