Browse Source

[Form][HttpFoundation] Improved File and UploadedFile class

Bernhard Schussek 14 years ago
parent
commit
8513082007

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

@@ -84,7 +84,8 @@ class FileField extends FieldGroup
                     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['file']->move($this->getTmpDir());
+                    $data['file']->rename($this->getTmpName($data['token']));
                     $data['original_name'] = $data['file']->getOriginalName();
                     $data['file'] = '';
                     break;
@@ -123,13 +124,23 @@ class FileField extends FieldGroup
     }
 
     /**
-     * Returns the absolute temporary file path for the given token
+     * Returns the absolute temporary path to the uploaded file
      *
      * @param string $token
      */
     protected function getTmpPath($token)
     {
-        return realpath($this->getOption('tmp_dir')) . '/' . $this->getTmpName($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'));
     }
 
     /**

+ 77 - 3
src/Symfony/Component/HttpFoundation/File/File.php

@@ -438,12 +438,42 @@ class File
         'x-world/x-vrml' => 'wrl',
     );
 
+    /**
+     * Stores the absolute path to the document root directory
+     * @var string
+     */
+    static protected $documentRoot;
+
     /**
      * The absolute path to the file without dots
      * @var string
      */
     protected $path;
 
+    /**
+     * Sets the path t the document root directory
+     *
+     * @param string $documentRoot
+     */
+    static public function setDocumentRoot($documentRoot)
+    {
+        if (!is_dir($documentRoot)) {
+            throw new \LogicException($documentRoot . ' is no directory');
+        }
+
+        self::$documentRoot = realpath($documentRoot);
+    }
+
+    /**
+     * Returns the path to the document root directory
+     *
+     * @return string
+     */
+    static public function getDocumentRoot()
+    {
+        return self::$documentRoot;
+    }
+
     /**
      * Constructs a new file from the given path.
      *
@@ -533,6 +563,26 @@ class File
         return $this->path;
     }
 
+    /**
+     * Returns the path relative to the document root
+     *
+     * You can set the document root using the static method setDocumentRoot().
+     * If the file is outside of the document root, this method returns an
+     * empty string.
+     *
+     * @return string  The relative file path
+     */
+    public function getWebPath()
+    {
+        $root = self::$documentRoot;
+
+        if (strpos($this->path, $root) === false) {
+            return '';
+        }
+
+        return str_replace(array($root, DIRECTORY_SEPARATOR), array('', '/'), $this->path);
+    }
+
     /**
      * Returns the mime type of the file.
      *
@@ -564,16 +614,40 @@ class File
     }
 
     /**
-     * Moves the file to a new location.
+     * Moves the file to a new directory and gives it a new filename
      *
-     * @param string $newPath
+     * @param  string $directory   The new directory
+     * @param  string $filename    The new file name
+     * @throws FileException       When the file could not be moved
      */
-    public function move($newPath)
+    protected function doMove($directory, $filename)
     {
+        $newPath = $directory . DIRECTORY_SEPARATOR . $filename;
+
         if (!rename($this->getPath(), $newPath)) {
             throw new FileException(sprintf('Could not move file %s to %s', $this->getPath(), $newPath));
         }
 
         $this->path = realpath($newPath);
     }
+
+    /**
+     * Moves the file to a new location.
+     *
+     * @param string $directory
+     */
+    public function move($directory)
+    {
+        $this->doMove($directory, $this->getName());
+    }
+
+    /**
+     * Renames the file
+     *
+     * @param string $name  The new file name
+     */
+    public function rename($name)
+    {
+        $this->doMove($this->getDirectory(), $name);
+    }
 }

+ 43 - 13
src/Symfony/Component/HttpFoundation/File/UploadedFile.php

@@ -20,10 +20,34 @@ use Symfony\Component\HttpFoundation\File\Exception\FileException;
  */
 class UploadedFile extends File
 {
+    /**
+     * The original name of the uploaded file
+     * @var string
+     */
     protected $originalName;
+
+    /**
+     * The mime type provided by the uploader
+     * @var string
+     */
     protected $mimeType;
+
+    /**
+     * The file size provided by the uploader
+     * @var integer
+     */
     protected $size;
+
+    /**
+     * The UPLOAD_ERR_XXX constant provided by the uploader
+     * @var integer
+     */
     protected $error;
+
+    /**
+     * Whether the uploaded file has already been moved
+     * @var boolean
+     */
     protected $moved = false;
 
     /**
@@ -35,7 +59,7 @@ class UploadedFile extends File
      * @param string  $type     The type of the file as provided by PHP
      * @param integer $size     The file size
      * @param string  $error    The error constant of the upload. Should be
-     *                          one of PHP's UPLOAD_XXX constants.
+     *                          one of PHP's UPLOAD_ERR_XXX constants.
      */
     public function __construct($path, $originalName, $mimeType, $size, $error)
     {
@@ -62,13 +86,7 @@ class UploadedFile extends File
     }
 
     /**
-     * Returns the mime type of the file.
-     *
-     * The mime type is guessed using the functions finfo(), mime_content_type()
-     * and the system binary "file" (in this order), depending on which of those
-     * is available on the current operating system.
-     *
-     * @returns string  The guessed mime type, e.g. "application/pdf"
+     * @inheritDoc
      */
     public function getMimeType()
     {
@@ -105,13 +123,13 @@ class UploadedFile extends File
     }
 
     /**
-     * Moves the file to a new location.
-     *
-     * @param string $newPath
+     * @inheritDoc
      */
-    public function move($newPath)
+    protected function doMove($directory, $filename)
     {
         if (!$this->moved) {
+            $newPath = $directory . DIRECTORY_SEPARATOR . $filename;
+
             if (!move_uploaded_file($this->getPath(), $newPath)) {
                 throw new FileException(sprintf('Could not move file %s to %s', $this->getPath(), $newPath));
             }
@@ -119,7 +137,19 @@ class UploadedFile extends File
             $this->moved = true;
             $this->path = realpath($newPath);
         } else {
-            parent::move($newPath);
+            parent::doMove($directory, $filename);
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function move($directory)
+    {
+        if (!$this->moved) {
+            $this->doMove($directory, $this->originalName);
+        } else {
+            parent::move($directory);
         }
     }
 }

+ 10 - 4
tests/Symfony/Tests/Component/Form/FileFieldTest.php

@@ -3,6 +3,7 @@
 namespace Symfony\Tests\Component\Form;
 
 use Symfony\Component\Form\FileField;
+use Symfony\Component\HttpFoundation\File\File;
 
 class FileFieldTest extends \PHPUnit_Framework_TestCase
 {
@@ -44,15 +45,20 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase
 
     public function testBindUploadsNewFiles()
     {
-        $tmpPath = realpath(self::$tmpDir) . '/' . md5(session_id() . '$secret$' . '12345');
+        $tmpDir = realpath(self::$tmpDir);
+        $tmpName = md5(session_id() . '$secret$' . '12345');
+        $tmpPath = $tmpDir . DIRECTORY_SEPARATOR . $tmpName;
         $that = $this;
 
         $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false);
         $file->expects($this->once())
              ->method('move')
-             ->with($this->equalTo($tmpPath))
-             ->will($this->returnCallback(function ($path) use ($that) {
-                $that->createTmpFile($path);
+             ->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')

+ 36 - 2
tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php

@@ -20,6 +20,20 @@ class FileTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.gif', $this->file->getPath());
     }
 
+    public function testGetWebPathReturnsPathRelativeToDocumentRoot()
+    {
+        File::setDocumentRoot(__DIR__);
+
+        $this->assertEquals('/Fixtures/test.gif', $this->file->getWebPath());
+    }
+
+    public function testGetWebPathReturnsEmptyPathIfOutsideDocumentRoot()
+    {
+        File::setDocumentRoot(__DIR__.'/Fixtures/directory');
+
+        $this->assertEquals('', $this->file->getWebPath());
+    }
+
     public function testGetNameReturnsNameWithExtension()
     {
         $this->assertEquals('test.gif', $this->file->getName());
@@ -62,13 +76,33 @@ class FileTest extends \PHPUnit_Framework_TestCase
     public function testMove()
     {
         $path = __DIR__.'/Fixtures/test.copy.gif';
-        $targetPath = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.target.gif';
+        $targetDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'directory';
+        $targetPath = $targetDir.DIRECTORY_SEPARATOR.'test.copy.gif';
+        @unlink($path);
+        @unlink($targetPath);
+        copy(__DIR__.'/Fixtures/test.gif', $path);
+
+        $file = new File($path);
+        $file->move($targetDir);
+
+        $this->assertTrue(file_exists($targetPath));
+        $this->assertFalse(file_exists($path));
+        $this->assertEquals($targetPath, $file->getPath());
+
+        @unlink($path);
+        @unlink($targetPath);
+    }
+
+    public function testRename()
+    {
+        $path = __DIR__.'/Fixtures/test.copy.gif';
+        $targetPath = __DIR__.'/Fixtures/test.target.gif';
         @unlink($path);
         @unlink($targetPath);
         copy(__DIR__.'/Fixtures/test.gif', $path);
 
         $file = new File($path);
-        $file->move($targetPath);
+        $file->rename('test.target.gif');
 
         $this->assertTrue(file_exists($targetPath));
         $this->assertFalse(file_exists($path));