浏览代码

[Form] Added proper error handling to FileField

Bernhard Schussek 14 年之前
父节点
当前提交
242be933d5

+ 76 - 3
src/Symfony/Component/Form/FileField.php

@@ -3,6 +3,7 @@
 namespace Symfony\Component\Form;
 
 use Symfony\Component\HttpFoundation\File\File;
+use Symfony\Component\Form\Exception\FormException;
 
 /*
  * This file is part of the Symfony framework.
@@ -18,6 +19,26 @@ use Symfony\Component\HttpFoundation\File\File;
  */
 class FileField extends FieldGroup
 {
+    /**
+     * 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}
      */
@@ -45,9 +66,29 @@ class FileField extends FieldGroup
     protected function preprocessData(array $data)
     {
         if ($data['file']) {
-            $data['file']->move($this->getTmpPath($data['token']));
-            $data['original_name'] = $data['file']->getOriginalName();
-            $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->getTmpPath($data['token']));
+                    $data['original_name'] = $data['file']->getOriginalName();
+                    $data['file'] = '';
+                    break;
+            }
         }
 
         return $data;
@@ -120,4 +161,36 @@ class FileField extends FieldGroup
     {
         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;
+    }
 }

+ 18 - 0
src/Symfony/Component/Form/Resources/config/validation.xml

@@ -74,4 +74,22 @@
       </constraint>
     </getter>
   </class>
+
+  <class name="Symfony\Component\Form\FileField">
+    <getter property="iniSizeExceeded">
+      <constraint name="AssertFalse">
+        <option name="message">The file is too large. Please upload a smaller file</option>
+      </constraint>
+    </getter>
+    <getter property="formSizeExceeded">
+      <constraint name="AssertFalse">
+        <option name="message">The file is too large. Please upload a smaller file</option>
+      </constraint>
+    </getter>
+    <getter property="uploadComplete">
+      <constraint name="AssertTrue">
+        <option name="message">The file was only partially uploaded. Please try again</option>
+      </constraint>
+    </getter>
+  </class>
 </constraint-mapping>

+ 120 - 27
tests/Symfony/Tests/Component/Form/FileFieldTest.php

@@ -2,7 +2,6 @@
 
 namespace Symfony\Tests\Component\Form;
 
-use Symfony\Component\HttpFoundation\File\File;
 use Symfony\Component\Form\FileField;
 
 class FileFieldTest extends \PHPUnit_Framework_TestCase
@@ -11,6 +10,8 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
 
     protected static $tmpDir;
 
+    protected $field;
+
     public static function setUpBeforeClass()
     {
         self::$tmpDir = sys_get_temp_dir();
@@ -19,6 +20,14 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
         @session_start();
     }
 
+    protected function setUp()
+    {
+        $this->field = new FileField('file', array(
+            'secret' => '$secret$',
+            'tmp_dir' => self::$tmpDir,
+        ));
+    }
+
     protected function tearDown()
     {
         foreach (self::$tmpFiles as $key => $file) {
@@ -35,11 +44,6 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testBindUploadsNewFiles()
     {
-        $field = new FileField('file', array(
-            'secret' => '$secret$',
-            'tmp_dir' => self::$tmpDir,
-        ));
-
         $tmpPath = realpath(self::$tmpDir) . '/' . md5(session_id() . '$secret$' . '12345');
         $that = $this;
 
@@ -54,7 +58,7 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
              ->method('getOriginalName')
              ->will($this->returnValue('original_name.jpg'));
 
-        $field->bind(array(
+        $this->field->bind(array(
             'file' => $file,
             'token' => '12345',
             'original_name' => '',
@@ -65,21 +69,19 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
             'file' => '',
             'token' => '12345',
             'original_name' => 'original_name.jpg',
-        ), $field->getDisplayedData());
-        $this->assertEquals($tmpPath, $field->getData());
+        ), $this->field->getDisplayedData());
+        $this->assertEquals($tmpPath, $this->field->getData());
+        $this->assertFalse($this->field->isIniSizeExceeded());
+        $this->assertFalse($this->field->isFormSizeExceeded());
+        $this->assertTrue($this->field->isUploadComplete());
     }
 
     public function testBindKeepsUploadedFilesOnErrors()
     {
-        $field = new FileField('file', array(
-            'secret' => '$secret$',
-            'tmp_dir' => self::$tmpDir,
-        ));
-
         $tmpPath = self::$tmpDir . '/' . md5(session_id() . '$secret$' . '12345');
         $this->createTmpFile($tmpPath);
 
-        $field->bind(array(
+        $this->field->bind(array(
             'file' => '',
             'token' => '12345',
             'original_name' => 'original_name.jpg',
@@ -90,25 +92,20 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
             'file' => '',
             'token' => '12345',
             'original_name' => 'original_name.jpg',
-        ), $field->getDisplayedData());
-        $this->assertEquals(realpath($tmpPath), realpath($field->getData()));
+        ), $this->field->getDisplayedData());
+        $this->assertEquals(realpath($tmpPath), realpath($this->field->getData()));
     }
 
     public function testBindKeepsOldFileIfNotOverwritten()
     {
-        $field = new FileField('file', array(
-            'secret' => '$secret$',
-            'tmp_dir' => self::$tmpDir,
-        ));
-
         $oldPath = tempnam(sys_get_temp_dir(), 'FileFieldTest');
         $this->createTmpFile($oldPath);
 
-        $field->setData($oldPath);
+        $this->field->setData($oldPath);
 
-        $this->assertEquals($oldPath, $field->getData());
+        $this->assertEquals($oldPath, $this->field->getData());
 
-        $field->bind(array(
+        $this->field->bind(array(
             'file' => '',
             'token' => '12345',
             'original_name' => '',
@@ -119,7 +116,103 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
             'file' => '',
             'token' => '12345',
             'original_name' => '',
-        ), $field->getDisplayedData());
-        $this->assertEquals($oldPath, $field->getData());
+        ), $this->field->getDisplayedData());
+        $this->assertEquals($oldPath, $this->field->getData());
+    }
+
+    public function testBindHandlesUploadErrIniSize()
+    {
+        $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->bind(array(
+            'file' => $file,
+            'token' => '12345',
+            'original_name' => ''
+        ));
+
+        $this->assertTrue($this->field->isIniSizeExceeded());
+    }
+
+    public function testBindHandlesUploadErrFormSize()
+    {
+        $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->bind(array(
+            'file' => $file,
+            'token' => '12345',
+            'original_name' => ''
+        ));
+
+        $this->assertTrue($this->field->isFormSizeExceeded());
+    }
+
+    public function testBindHandlesUploadErrPartial()
+    {
+        $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->bind(array(
+            'file' => $file,
+            'token' => '12345',
+            'original_name' => ''
+        ));
+
+        $this->assertFalse($this->field->isUploadComplete());
+    }
+
+    public function testBindThrowsExceptionOnUploadErrNoTmpDir()
+    {
+        $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->bind(array(
+            'file' => $file,
+            'token' => '12345',
+            'original_name' => ''
+        ));
+    }
+
+    public function testBindThrowsExceptionOnUploadErrCantWrite()
+    {
+        $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->bind(array(
+            'file' => $file,
+            'token' => '12345',
+            'original_name' => ''
+        ));
+    }
+
+    public function testBindThrowsExceptionOnUploadErrExtension()
+    {
+        $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->bind(array(
+            'file' => $file,
+            'token' => '12345',
+            'original_name' => ''
+        ));
     }
 }