[Form] Refactored FileField to FormFactory and fixed file upload mechanism
This commit is contained in:
parent
848ec01f02
commit
f2c1976da6
72
src/Symfony/Component/Form/DataProcessor/FileUploader.php
Normal file
72
src/Symfony/Component/Form/DataProcessor/FileUploader.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\DataProcessor;
|
||||
|
||||
use Symfony\Component\Form\FieldInterface;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\File\TemporaryStorage;
|
||||
|
||||
/**
|
||||
* Moves uploaded files to a temporary location
|
||||
*
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class FileUploader implements DataProcessorInterface
|
||||
{
|
||||
private $field;
|
||||
|
||||
private $storage;
|
||||
|
||||
public function __construct(FieldInterface $field, TemporaryStorage $storage)
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
public function processData($data)
|
||||
{
|
||||
// Newly uploaded file
|
||||
if ($data['file'] instanceof UploadedFile && $data['file']->isValid()) {
|
||||
$data['token'] = (string)rand(100000, 999999);
|
||||
$directory = $this->storage->getTempDir($data['token']);
|
||||
|
||||
if (!file_exists($directory)) {
|
||||
// Recursively create directories
|
||||
mkdir($directory, 0777, true);
|
||||
}
|
||||
|
||||
$data['file']->move($directory);
|
||||
$data['name'] = $data['file']->getName();
|
||||
}
|
||||
|
||||
// Existing uploaded file
|
||||
if (!$data['file'] && $data['token'] && $data['name']) {
|
||||
$path = $this->storage->getTempDir($data['token']) . DIRECTORY_SEPARATOR . $data ['name'];
|
||||
|
||||
if (file_exists($path)) {
|
||||
$data['file'] = new File($path);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear other fields if we still don't have a file, but keep
|
||||
// possible existing files of the field
|
||||
if (!$data['file']) {
|
||||
$currentData = $this->field->getNormalizedData();
|
||||
$data['file'] = $currentData['file'];
|
||||
$data['token'] = '';
|
||||
$data['name'] = '';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form;
|
||||
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
use Symfony\Component\Form\Exception\FormException;
|
||||
|
||||
/**
|
||||
* A file field to upload files.
|
||||
*/
|
||||
class FileField extends Form
|
||||
{
|
||||
/**
|
||||
* Whether the size of the uploaded file exceeds the upload_max_filesize
|
||||
* directive in php.ini
|
||||
* @var Boolean
|
||||
*/
|
||||
protected $iniSizeExceeded = false;
|
||||
|
||||
/**
|
||||
* Whether the size of the uploaded file exceeds the MAX_FILE_SIZE
|
||||
* directive specified in the HTML form
|
||||
* @var Boolean
|
||||
*/
|
||||
protected $formSizeExceeded = false;
|
||||
|
||||
/**
|
||||
* Whether the file was completely uploaded
|
||||
* @var Boolean
|
||||
*/
|
||||
protected $uploadComplete = true;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addRequiredOption('secret');
|
||||
$this->addOption('tmp_dir', sys_get_temp_dir());
|
||||
|
||||
parent::configure();
|
||||
|
||||
$this->add(new Field('file'));
|
||||
$this->add(new HiddenField('token'));
|
||||
$this->add(new HiddenField('original_name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the file to a temporary location to prevent its deletion when
|
||||
* the PHP process dies
|
||||
*
|
||||
* This way the file can survive if the form does not validate and is
|
||||
* resubmitted.
|
||||
*
|
||||
* @see Symfony\Component\Form\Form::preprocessData()
|
||||
*/
|
||||
protected function preprocessData($data)
|
||||
{
|
||||
if ($data['file']) {
|
||||
switch ($data['file']->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
$this->iniSizeExceeded = true;
|
||||
break;
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$this->formSizeExceeded = true;
|
||||
break;
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
$this->uploadComplete = false;
|
||||
break;
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
throw new FormException('Could not upload a file because a temporary directory is missing (UPLOAD_ERR_NO_TMP_DIR)');
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
throw new FormException('Could not write file to disk (UPLOAD_ERR_CANT_WRITE)');
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
throw new FormException('A PHP extension stopped the file upload (UPLOAD_ERR_EXTENSION)');
|
||||
case UPLOAD_ERR_OK:
|
||||
default:
|
||||
$data['file']->move($this->getTmpDir());
|
||||
$data['file']->rename($this->getTmpName($data['token']));
|
||||
$data['original_name'] = $data['file']->getOriginalName();
|
||||
$data['file'] = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a file path into an array of field values
|
||||
*
|
||||
* @see Symfony\Component\Form\Field::normalize()
|
||||
*/
|
||||
protected function normalize($path)
|
||||
{
|
||||
return array(
|
||||
'file' => '',
|
||||
'token' => rand(100000, 999999),
|
||||
'original_name' => '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns an array of field values into a file path
|
||||
*
|
||||
* @see Symfony\Component\Form\Field::denormalize()
|
||||
*/
|
||||
protected function denormalize($data)
|
||||
{
|
||||
$path = $this->getTmpPath($data['token']);
|
||||
|
||||
return file_exists($path) ? $path : $this->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute temporary path to the uploaded file
|
||||
*
|
||||
* @param string $token
|
||||
*/
|
||||
protected function getTmpPath($token)
|
||||
{
|
||||
return $this->getTmpDir() . DIRECTORY_SEPARATOR . $this->getTmpName($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the temporary directory where files are stored
|
||||
*
|
||||
* @param string $token
|
||||
*/
|
||||
protected function getTmpDir()
|
||||
{
|
||||
return realpath($this->getOption('tmp_dir'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the temporary file name for the given token
|
||||
*
|
||||
* @param string $token
|
||||
*/
|
||||
protected function getTmpName($token)
|
||||
{
|
||||
return md5(session_id() . $this->getOption('secret') . $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original name of the uploaded file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOriginalName()
|
||||
{
|
||||
$data = $this->getNormalizedData();
|
||||
|
||||
return $data['original_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isMultipart()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the size of the uploaded file exceeds the
|
||||
* upload_max_filesize directive in php.ini
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isIniSizeExceeded()
|
||||
{
|
||||
return $this->iniSizeExceeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the size of the uploaded file exceeds the
|
||||
* MAX_FILE_SIZE directive specified in the HTML form
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isFormSizeExceeded()
|
||||
{
|
||||
return $this->formSizeExceeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the file was completely uploaded
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isUploadComplete()
|
||||
{
|
||||
return $this->uploadComplete;
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
|
||||
use Symfony\Component\Form\DataProcessor\RadioToArrayConverter;
|
||||
use Symfony\Component\Form\DataProcessor\UrlProtocolFixer;
|
||||
use Symfony\Component\Form\DataProcessor\CollectionMerger;
|
||||
use Symfony\Component\Form\DataProcessor\FileUploader;
|
||||
use Symfony\Component\Form\FieldFactory\FieldFactoryInterface;
|
||||
use Symfony\Component\Form\Renderer\DefaultRenderer;
|
||||
use Symfony\Component\Form\Renderer\Theme\ThemeInterface;
|
||||
@ -51,8 +52,11 @@ use Symfony\Component\Form\ValueTransformer\ValueTransformerChain;
|
||||
use Symfony\Component\Form\ValueTransformer\ArrayToChoicesTransformer;
|
||||
use Symfony\Component\Form\ValueTransformer\ArrayToPartsTransformer;
|
||||
use Symfony\Component\Form\ValueTransformer\ValueToDuplicatesTransformer;
|
||||
use Symfony\Component\Form\ValueTransformer\FileToArrayTransformer;
|
||||
use Symfony\Component\Form\ValueTransformer\FileToStringTransformer;
|
||||
use Symfony\Component\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Locale\Locale;
|
||||
use Symfony\Component\HttpFoundation\File\TemporaryStorage;
|
||||
|
||||
class FormFactory
|
||||
{
|
||||
@ -64,12 +68,19 @@ class FormFactory
|
||||
|
||||
private $fieldFactory;
|
||||
|
||||
public function __construct(ThemeInterface $theme, CsrfProviderInterface $csrfProvider, ValidatorInterface $validator, FieldFactoryInterface $fieldFactory)
|
||||
private $storage;
|
||||
|
||||
public function __construct(ThemeInterface $theme,
|
||||
CsrfProviderInterface $csrfProvider,
|
||||
ValidatorInterface $validator,
|
||||
FieldFactoryInterface $fieldFactory,
|
||||
TemporaryStorage $storage)
|
||||
{
|
||||
$this->theme = $theme;
|
||||
$this->csrfProvider = $csrfProvider;
|
||||
$this->validator = $validator;
|
||||
$this->fieldFactory = $fieldFactory;
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
protected function getTheme()
|
||||
@ -866,4 +877,30 @@ class FormFactory
|
||||
->add($firstChild)
|
||||
->add($secondChild);
|
||||
}
|
||||
|
||||
public function getFileField($key, array $options = array())
|
||||
{
|
||||
$options = array_merge(array(
|
||||
'template' => 'file',
|
||||
'type' => 'string',
|
||||
), $options);
|
||||
|
||||
$field = $this->getForm($key, $options);
|
||||
|
||||
if ($options['type'] === 'string') {
|
||||
$field->setNormalizationTransformer(new ValueTransformerChain(array(
|
||||
new ReversedTransformer(new FileToStringTransformer()),
|
||||
new FileToArrayTransformer(),
|
||||
)));
|
||||
} else {
|
||||
$field->setNormalizationTransformer(new FileToArrayTransformer());
|
||||
}
|
||||
|
||||
return $field
|
||||
->setDataPreprocessor(new FileUploader($field, $this->storage))
|
||||
->setData(null) // FIXME
|
||||
->add($this->getField('file', array('type' => 'file')))
|
||||
->add($this->getHiddenField('token'))
|
||||
->add($this->getHiddenField('name'));
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\ValueTransformer;
|
||||
|
||||
use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class FileToArrayTransformer implements ValueTransformerInterface
|
||||
{
|
||||
public function transform($file)
|
||||
{
|
||||
if (null === $file || '' === $file) {
|
||||
return array(
|
||||
'file' => '',
|
||||
'token' => '',
|
||||
'name' => '',
|
||||
);
|
||||
}
|
||||
|
||||
if (!$file instanceof File) {
|
||||
throw new UnexpectedTypeException($file, 'Symfony\Component\HttpFoundation\File\File');
|
||||
}
|
||||
|
||||
return array(
|
||||
'file' => $file,
|
||||
'token' => '',
|
||||
'name' => '',
|
||||
);
|
||||
}
|
||||
|
||||
public function reverseTransform($array)
|
||||
{
|
||||
if (null === $array || '' === $array || array() === $array) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_array($array)) {
|
||||
throw new UnexpectedTypeException($array, 'array');
|
||||
}
|
||||
|
||||
if (!array_key_exists('file', $array)) {
|
||||
throw new TransformationFailedException('The key "file" is missing');
|
||||
}
|
||||
|
||||
if (!empty($array['file']) && !$array['file'] instanceof File) {
|
||||
throw new TransformationFailedException('The key "file" should be empty or instance of File');
|
||||
}
|
||||
|
||||
return $array['file'];
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\ValueTransformer;
|
||||
|
||||
use Symfony\Component\Form\ValueTransformer\TransformationFailedException;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class FileToStringTransformer implements ValueTransformerInterface
|
||||
{
|
||||
public function transform($file)
|
||||
{
|
||||
if (null === $file || '' === $file) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$file instanceof File) {
|
||||
throw new UnexpectedTypeException($file, 'Symfony\Component\HttpFoundation\File\File');
|
||||
}
|
||||
|
||||
return $file->getPath();
|
||||
}
|
||||
|
||||
public function reverseTransform($path)
|
||||
{
|
||||
if (null === $path || '' === $path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($path)) {
|
||||
throw new UnexpectedTypeException($path, 'string');
|
||||
}
|
||||
|
||||
return new File($path);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||
|
||||
class UnexpectedTypeException extends FileException
|
||||
{
|
||||
public function __construct($value, $expectedType)
|
||||
{
|
||||
parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\File\Exception;
|
||||
|
||||
/**
|
||||
* Thrown when an error occurred during file upload
|
||||
*
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class UploadException extends FileException
|
||||
{
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\File;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Session;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class SessionBasedTemporaryStorage extends TemporaryStorage
|
||||
{
|
||||
public function __construct(Session $session, $directory, $secret, $nestingLevels = 3)
|
||||
{
|
||||
parent::__construct($directory, $secret, $nestingLevels);
|
||||
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
protected function generateHashInfo($token)
|
||||
{
|
||||
$this->session->start();
|
||||
|
||||
return $this->session->getId() . parent::generateHashInfo($token);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpFoundation\File;
|
||||
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
||||
*/
|
||||
class TemporaryStorage
|
||||
{
|
||||
private $directory;
|
||||
|
||||
private $secret;
|
||||
|
||||
private $nestingLevels;
|
||||
|
||||
public function __construct($directory, $secret, $nestingLevels = 3)
|
||||
{
|
||||
$this->directory = realpath($directory);
|
||||
$this->secret = $secret;
|
||||
$this->nestingLevels = $nestingLevels;
|
||||
}
|
||||
|
||||
protected function generateHashInfo($token)
|
||||
{
|
||||
return $this->secret . $token;
|
||||
}
|
||||
|
||||
protected function generateHash($token)
|
||||
{
|
||||
return md5($this->generateHashInfo($token));
|
||||
}
|
||||
|
||||
public function getTempDir($token)
|
||||
{
|
||||
if (!is_string($token)) {
|
||||
throw new UnexpectedTypeException($token, 'string');
|
||||
}
|
||||
|
||||
$hash = $this->generateHash($token);
|
||||
|
||||
if (strlen($hash) < $this->nestingLevels) {
|
||||
throw new FileException(sprintf(
|
||||
'For %s nesting levels the hash must have at least %s characters', $this->nestingLevels, $this->nestingLevels));
|
||||
}
|
||||
|
||||
$directory = $this->directory;
|
||||
|
||||
for ($i = 0; $i < ($this->nestingLevels - 1); ++$i) {
|
||||
$directory .= DIRECTORY_SEPARATOR . $hash{$i};
|
||||
}
|
||||
|
||||
return $directory . DIRECTORY_SEPARATOR . substr($hash, $i);
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\HttpFoundation\File;
|
||||
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\UploadException;
|
||||
|
||||
/**
|
||||
* A file uploaded through a form.
|
||||
@ -68,14 +69,24 @@ class UploadedFile extends File
|
||||
throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path')));
|
||||
}
|
||||
|
||||
if (is_file($path)) {
|
||||
$this->path = realpath($path);
|
||||
}
|
||||
|
||||
if (null === $error) {
|
||||
$error = UPLOAD_ERR_OK;
|
||||
}
|
||||
|
||||
switch ($error) {
|
||||
// TESTME
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
throw new UploadException('Could not upload a file because a temporary directory is missing (UPLOAD_ERR_NO_TMP_DIR)');
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
throw new UploadException('Could not write file to disk (UPLOAD_ERR_CANT_WRITE)');
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
throw new UploadException('A PHP extension stopped the file upload (UPLOAD_ERR_EXTENSION)');
|
||||
}
|
||||
|
||||
if (is_file($path)) {
|
||||
$this->path = realpath($path);
|
||||
}
|
||||
|
||||
if (null === $mimeType) {
|
||||
$mimeType = 'application/octet-stream';
|
||||
}
|
||||
@ -123,6 +134,48 @@ class UploadedFile extends File
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the file was uploaded succesfully.
|
||||
*
|
||||
* @return Boolean True if no error occurred during uploading
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return $this->error === UPLOAD_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the size of the uploaded file exceeds the
|
||||
* upload_max_filesize directive in php.ini
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
protected function isIniSizeExceeded()
|
||||
{
|
||||
return $this->error === UPLOAD_ERR_INI_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the size of the uploaded file exceeds the
|
||||
* MAX_FILE_SIZE directive specified in the HTML form
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
protected function isFormSizeExceeded()
|
||||
{
|
||||
return $this->error === UPLOAD_ERR_FORM_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the file was completely uploaded
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
protected function isUploadComplete()
|
||||
{
|
||||
return $this->error !== UPLOAD_ERR_PARTIAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
@ -11,10 +11,12 @@
|
||||
|
||||
namespace Symfony\Tests\Component\Form;
|
||||
|
||||
require_once __DIR__.'/TestCase.php';
|
||||
|
||||
use Symfony\Component\Form\FileField;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
class FileFieldTest extends \PHPUnit_Framework_TestCase
|
||||
class FileFieldTest extends TestCase
|
||||
{
|
||||
public static $tmpFiles = array();
|
||||
|
||||
@ -24,18 +26,14 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$tmpDir = sys_get_temp_dir();
|
||||
|
||||
// we need a session ID
|
||||
@session_start();
|
||||
self::$tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'symfony-test';
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->field = new FileField('file', array(
|
||||
'secret' => '$secret$',
|
||||
'tmp_dir' => self::$tmpDir,
|
||||
));
|
||||
parent::setUp();
|
||||
|
||||
$this->field = $this->factory->getFileField('file');
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
@ -54,180 +52,119 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testSubmitUploadsNewFiles()
|
||||
{
|
||||
$tmpDir = realpath(self::$tmpDir);
|
||||
$tmpName = md5(session_id() . '$secret$' . '12345');
|
||||
$tmpPath = $tmpDir . DIRECTORY_SEPARATOR . $tmpName;
|
||||
$that = $this;
|
||||
$tmpDir = self::$tmpDir;
|
||||
$generatedToken = '';
|
||||
|
||||
$file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
|
||||
$this->storage->expects($this->atLeastOnce())
|
||||
->method('getTempDir')
|
||||
->will($this->returnCallback(function ($token) use ($tmpDir, &$generatedToken) {
|
||||
// A 6-digit token is generated by FileUploader and passed
|
||||
// to getTempDir()
|
||||
$generatedToken = $token;
|
||||
|
||||
return $tmpDir;
|
||||
}));
|
||||
|
||||
$file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$file->expects($this->once())
|
||||
->method('move')
|
||||
->with($this->equalTo($tmpDir));
|
||||
$file->expects($this->once())
|
||||
->method('rename')
|
||||
->with($this->equalTo($tmpName))
|
||||
->will($this->returnCallback(function ($directory) use ($that, $tmpPath) {
|
||||
$that->createTmpFile($tmpPath);
|
||||
}));
|
||||
$file->expects($this->any())
|
||||
->method('getOriginalName')
|
||||
->method('isValid')
|
||||
->will($this->returnValue(true));
|
||||
$file->expects($this->any())
|
||||
->method('getName')
|
||||
->will($this->returnValue('original_name.jpg'));
|
||||
$file->expects($this->any())
|
||||
->method('getPath')
|
||||
->will($this->returnValue($tmpDir.'/original_name.jpg'));
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => $file,
|
||||
'token' => '12345',
|
||||
'original_name' => '',
|
||||
'token' => '',
|
||||
'name' => '',
|
||||
));
|
||||
|
||||
$this->assertTrue(file_exists($tmpPath));
|
||||
$this->assertRegExp('/^\d{6}$/', $generatedToken);
|
||||
$this->assertEquals(array(
|
||||
'file' => '',
|
||||
'token' => '12345',
|
||||
'original_name' => 'original_name.jpg',
|
||||
'file' => $file,
|
||||
'token' => $generatedToken,
|
||||
'name' => 'original_name.jpg',
|
||||
), $this->field->getDisplayedData());
|
||||
$this->assertEquals($tmpPath, $this->field->getData());
|
||||
$this->assertFalse($this->field->isIniSizeExceeded());
|
||||
$this->assertFalse($this->field->isFormSizeExceeded());
|
||||
$this->assertTrue($this->field->isUploadComplete());
|
||||
$this->assertEquals($tmpDir.'/original_name.jpg', $this->field->getData());
|
||||
}
|
||||
|
||||
public function testSubmitKeepsUploadedFilesOnErrors()
|
||||
{
|
||||
$tmpPath = self::$tmpDir . '/' . md5(session_id() . '$secret$' . '12345');
|
||||
$tmpDir = self::$tmpDir;
|
||||
$tmpPath = $tmpDir . DIRECTORY_SEPARATOR . 'original_name.jpg';
|
||||
$this->createTmpFile($tmpPath);
|
||||
|
||||
$this->storage->expects($this->atLeastOnce())
|
||||
->method('getTempDir')
|
||||
->with($this->equalTo('123456'))
|
||||
->will($this->returnValue($tmpDir));
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => '',
|
||||
'token' => '12345',
|
||||
'original_name' => 'original_name.jpg',
|
||||
'file' => null,
|
||||
'token' => '123456',
|
||||
'name' => 'original_name.jpg',
|
||||
));
|
||||
|
||||
$this->assertTrue(file_exists($tmpPath));
|
||||
|
||||
$file = new File($tmpPath);
|
||||
|
||||
$this->assertEquals(array(
|
||||
'file' => $file,
|
||||
'token' => '123456',
|
||||
'name' => 'original_name.jpg',
|
||||
), $this->field->getDisplayedData());
|
||||
$this->assertEquals($tmpPath, $this->field->getData());
|
||||
}
|
||||
|
||||
public function testSubmitEmpty()
|
||||
{
|
||||
$this->storage->expects($this->never())
|
||||
->method('getTempDir');
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => '',
|
||||
'token' => '',
|
||||
'name' => '',
|
||||
));
|
||||
|
||||
$this->assertEquals(array(
|
||||
'file' => '',
|
||||
'token' => '12345',
|
||||
'original_name' => 'original_name.jpg',
|
||||
'token' => '',
|
||||
'name' => '',
|
||||
), $this->field->getDisplayedData());
|
||||
$this->assertEquals(realpath($tmpPath), realpath($this->field->getData()));
|
||||
$this->assertEquals(null, $this->field->getData());
|
||||
}
|
||||
|
||||
public function testSubmitKeepsOldFileIfNotOverwritten()
|
||||
public function testSubmitEmptyKeepsExistingFiles()
|
||||
{
|
||||
$oldPath = tempnam(sys_get_temp_dir(), 'FileFieldTest');
|
||||
$this->createTmpFile($oldPath);
|
||||
$tmpPath = self::$tmpDir . DIRECTORY_SEPARATOR . 'original_name.jpg';
|
||||
$this->createTmpFile($tmpPath);
|
||||
$file = new File($tmpPath);
|
||||
|
||||
$this->field->setData($oldPath);
|
||||
|
||||
$this->assertEquals($oldPath, $this->field->getData());
|
||||
$this->storage->expects($this->never())
|
||||
->method('getTempDir');
|
||||
|
||||
$this->field->setData($tmpPath);
|
||||
$this->field->submit(array(
|
||||
'file' => '',
|
||||
'token' => '12345',
|
||||
'original_name' => '',
|
||||
'token' => '',
|
||||
'name' => '',
|
||||
));
|
||||
|
||||
$this->assertTrue(file_exists($oldPath));
|
||||
$this->assertEquals(array(
|
||||
'file' => '',
|
||||
'token' => '12345',
|
||||
'original_name' => '',
|
||||
'file' => $file,
|
||||
'token' => '',
|
||||
'name' => '',
|
||||
), $this->field->getDisplayedData());
|
||||
$this->assertEquals($oldPath, $this->field->getData());
|
||||
}
|
||||
|
||||
public function testSubmitHandlesUploadErrIniSize()
|
||||
{
|
||||
$file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
|
||||
$file->expects($this->any())
|
||||
->method('getError')
|
||||
->will($this->returnValue(UPLOAD_ERR_INI_SIZE));
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => $file,
|
||||
'token' => '12345',
|
||||
'original_name' => ''
|
||||
));
|
||||
|
||||
$this->assertTrue($this->field->isIniSizeExceeded());
|
||||
}
|
||||
|
||||
public function testSubmitHandlesUploadErrFormSize()
|
||||
{
|
||||
$file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
|
||||
$file->expects($this->any())
|
||||
->method('getError')
|
||||
->will($this->returnValue(UPLOAD_ERR_FORM_SIZE));
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => $file,
|
||||
'token' => '12345',
|
||||
'original_name' => ''
|
||||
));
|
||||
|
||||
$this->assertTrue($this->field->isFormSizeExceeded());
|
||||
}
|
||||
|
||||
public function testSubmitHandlesUploadErrPartial()
|
||||
{
|
||||
$file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
|
||||
$file->expects($this->any())
|
||||
->method('getError')
|
||||
->will($this->returnValue(UPLOAD_ERR_PARTIAL));
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => $file,
|
||||
'token' => '12345',
|
||||
'original_name' => ''
|
||||
));
|
||||
|
||||
$this->assertFalse($this->field->isUploadComplete());
|
||||
}
|
||||
|
||||
public function testSubmitThrowsExceptionOnUploadErrNoTmpDir()
|
||||
{
|
||||
$file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
|
||||
$file->expects($this->any())
|
||||
->method('getError')
|
||||
->will($this->returnValue(UPLOAD_ERR_NO_TMP_DIR));
|
||||
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\FormException');
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => $file,
|
||||
'token' => '12345',
|
||||
'original_name' => ''
|
||||
));
|
||||
}
|
||||
|
||||
public function testSubmitThrowsExceptionOnUploadErrCantWrite()
|
||||
{
|
||||
$file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
|
||||
$file->expects($this->any())
|
||||
->method('getError')
|
||||
->will($this->returnValue(UPLOAD_ERR_CANT_WRITE));
|
||||
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\FormException');
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => $file,
|
||||
'token' => '12345',
|
||||
'original_name' => ''
|
||||
));
|
||||
}
|
||||
|
||||
public function testSubmitThrowsExceptionOnUploadErrExtension()
|
||||
{
|
||||
$file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
|
||||
$file->expects($this->any())
|
||||
->method('getError')
|
||||
->will($this->returnValue(UPLOAD_ERR_EXTENSION));
|
||||
|
||||
$this->setExpectedException('Symfony\Component\Form\Exception\FormException');
|
||||
|
||||
$this->field->submit(array(
|
||||
'file' => $file,
|
||||
'token' => '12345',
|
||||
'original_name' => ''
|
||||
));
|
||||
$this->assertEquals($tmpPath, $this->field->getData());
|
||||
}
|
||||
}
|
@ -23,6 +23,8 @@ class TestCase extends \PHPUnit_Framework_TestCase
|
||||
|
||||
protected $fieldFactory;
|
||||
|
||||
protected $storage;
|
||||
|
||||
protected $factory;
|
||||
|
||||
protected function setUp()
|
||||
@ -31,6 +33,10 @@ class TestCase extends \PHPUnit_Framework_TestCase
|
||||
$this->csrfProvider = $this->getMock('Symfony\Component\Form\CsrfProvider\CsrfProviderInterface');
|
||||
$this->validator = $this->getMock('Symfony\Component\Validator\ValidatorInterface');
|
||||
$this->fieldFactory = $this->getMock('Symfony\Component\Form\FieldFactory\FieldFactoryInterface');
|
||||
$this->factory = new FormFactory($this->theme, $this->csrfProvider, $this->validator, $this->fieldFactory);
|
||||
$this->storage = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\TemporaryStorage')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->factory = new FormFactory($this->theme, $this->csrfProvider,
|
||||
$this->validator, $this->fieldFactory, $this->storage);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user