merged branch vicb/file (PR #1317)

Commits
-------

9d6357c [HttpFoundation] Document the changes to the File classes
136b80a [HttFoundation] Add File::getExtension() as \SplFileInfo::getExtension() was introduced in PHP 5.3.6
38b3b74 [HttpKernel] Fix and test previous commit
ac0c00c [HttpFoundation] Make File extends \SplFileInfo

Discussion
----------

[HttpFoundation] Make File extends \SplFileInfo

This is a rebased version of [PR 674](https://github.com/symfony/symfony/pull/674).

  * File: The API has changed (now extends \SplFileInfo),
  * File: move() creates the target directory when it does not exist
  * UploadedFile: introduction of getClientXXX() methods (for Size, OriginalName, MimeType)

If this PR does not get merged UploadedFile should at least be fixed: [Client.php](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Client.php#L124) relies on a last parameter which is no more defined and which is used to bypass [move_uploaded_file()](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/File/UploadedFile.php#L155) in test mode.

If this could be merged, I'll detail the changes in UPDATE.md

---------------------------------------------------------------------------

by fabpot at 2011/06/14 08:20:59 -0700

I'll merge it. Can you update the UPDATE file?

---------------------------------------------------------------------------

by vicb at 2011/06/14 09:24:01 -0700

done
This commit is contained in:
Fabien Potencier 2011-06-15 08:57:16 +02:00
commit c5223bbcd1
11 changed files with 350 additions and 307 deletions

View File

@ -14,6 +14,36 @@ beta4 to beta5
* Exception classes have been moved to their own namespace * Exception classes have been moved to their own namespace
* `Yaml::load()` has been renamed to `Yaml::parse()` * `Yaml::load()` has been renamed to `Yaml::parse()`
* The File classes from `HttpFoundation` have been refactored:
* `Symfony\Component\HttpFoundation\File\File` has a new API:
* It now extends `\splFileInfo`:
* former `getName()` equivalent is `getBasename()`,
* former `getDirectory()` equivalent is `getPath()`,
* former `getPath()` equivalent is `getRealPath()`.
* the `move()` method now creates the target directory when it does not exist,
* `getExtension()` and `guessExtension()` do not return the extension
with a leading `.` anymore
* `Symfony\Component\HttpFoundation\File\UploadedFile` has a new API:
* The constructor has a new Boolean parameter that must be set to true
in test mode only in order to be able to move the file. This parameter
is not intended to be set to true from outside of the core files.
* `getMimeType()` now always returns the mime type of the underlying file.
Use `getClientMimeType()` to get the mime type from the request.
* `getSize()` now always returns the size of the underlying file.
Use `getClientSize()` to get the file size from the request.
* Use `getClientOriginalName()` to retrieve the original name from the
request.
* The `extensions` setting for Twig has been removed. There is now only one * The `extensions` setting for Twig has been removed. There is now only one
way to register Twig extensions, via the `twig.extension` tag. way to register Twig extensions, via the `twig.extension` tag.
@ -33,9 +63,6 @@ beta4 to beta5
* The file input is now rendered as any other input field. * The file input is now rendered as any other input field.
* The `Symfony\Component\HttpFoundation\File\File::getExtension()` and
`guessExtension()` methods do not return the extension with a `.` anymore.
* The `em` option of the Doctrine `EntityType` class now takes the entity * The `em` option of the Doctrine `EntityType` class now takes the entity
manager name instead of the EntityManager instance. If you don't pass this manager name instead of the EntityManager instance. If you don't pass this
option, the default Entity Manager will be used as before. option, the default Entity Manager will be used as before.

View File

@ -20,7 +20,7 @@ use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
* *
* @author Bernhard Schussek <bernhard.schussek@symfony.com> * @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/ */
class File class File extends \SplFileInfo
{ {
/** /**
* A map of mime types and their default extensions. * A map of mime types and their default extensions.
@ -440,13 +440,6 @@ class File
'x-world/x-vrml' => 'wrl', 'x-world/x-vrml' => 'wrl',
); );
/**
* The absolute path to the file without dots.
*
* @var string
*/
protected $path;
/** /**
* Constructs a new file from the given path. * Constructs a new file from the given path.
* *
@ -460,41 +453,7 @@ class File
throw new FileNotFoundException($path); throw new FileNotFoundException($path);
} }
$this->path = realpath($path); parent::__construct($path);
}
/**
* Alias for getPath().
*
* @return string
*/
public function __toString()
{
return (string) $this->path;
}
/**
* Returns the file name.
*
* @return string
*/
public function getName()
{
return basename($this->path);
}
/**
* Returns the file extension.
*
* @return string
*/
public function getExtension()
{
if ($ext = pathinfo($this->getName(), PATHINFO_EXTENSION)) {
return $ext;
}
return '';
} }
/** /**
@ -508,31 +467,7 @@ class File
{ {
$type = $this->getMimeType(); $type = $this->getMimeType();
if (isset(self::$defaultExtensions[$type])) { return isset(static::$defaultExtensions[$type]) ? static::$defaultExtensions[$type] : null;
return self::$defaultExtensions[$type];
}
return null;
}
/**
* Returns the directory of the file.
*
* @return string
*/
public function getDirectory()
{
return dirname($this->path);
}
/**
* Returns the absolute file path, without dots.
*
* @return string
*/
public function getPath()
{
return $this->path;
} }
/** /**
@ -542,28 +477,25 @@ class File
* and the system binary "file" (in this order), depending on which of those * and the system binary "file" (in this order), depending on which of those
* is available on the current operating system. * is available on the current operating system.
* *
* @return string The guessed mime type (i.e. "application/pdf") * @return string|null The guessed mime type (i.e. "application/pdf")
*/ */
public function getMimeType() public function getMimeType()
{ {
$guesser = MimeTypeGuesser::getInstance(); $guesser = MimeTypeGuesser::getInstance();
return $guesser->guess($this->getPath()); return $guesser->guess($this->getPathname());
} }
/** /**
* Returns the size of this file. * Returns the extension of the file.
* *
* @return integer The file size in bytes * \SplFileInfo::getExtension() is not available before PHP 5.3.6
*
* @return string The extension
*/ */
public function getSize() public function getExtension()
{ {
if (false === $size = @filesize($this->getPath())) { return pathinfo($this->getBasename(), PATHINFO_EXTENSION);
$error = error_get_last();
throw new FileException(sprintf('Could not read file size of %s (%s)', $this->getPath(), strip_tags($error['message'])));
}
return $size;
} }
/** /**
@ -571,16 +503,30 @@ class File
* *
* @param string $directory The destination folder * @param string $directory The destination folder
* @param string $name The new file name * @param string $name The new file name
*
* @return File A File object representing the new file
*
* @throws FileException if the target file could not be created
*/ */
public function move($directory, $name = null) public function move($directory, $name = null)
{ {
$newPath = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getName() : $name); if (!is_dir($directory)) {
if (false === @mkdir($directory, 0777, true)) {
if (!@rename($this->getPath(), $newPath)) { throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
$error = error_get_last(); }
throw new FileException(sprintf('Could not move file %s to %s (%s)', $this->getPath(), $newPath, strip_tags($error['message']))); } elseif (!is_writable($directory)) {
throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
} }
$this->path = realpath($newPath); $target = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : basename($name));
if (!@rename($this->getPathname(), $target)) {
$error = error_get_last();
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
}
chmod($target, 0666);
return new File($target);
} }
} }

View File

@ -23,33 +23,42 @@ use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
*/ */
class UploadedFile extends File class UploadedFile extends File
{ {
/**
* Whether the test mode is activated.
*
* Local files are used in test mode hence the code should not enforce HTTP uploads.
*
* @var Boolean
*/
private $test = false;
/** /**
* The original name of the uploaded file. * The original name of the uploaded file.
* *
* @var string * @var string
*/ */
protected $originalName; private $originalName;
/** /**
* The mime type provided by the uploader. * The mime type provided by the uploader.
* *
* @var string * @var string
*/ */
protected $mimeType; private $mimeType;
/** /**
* The file size provided by the uploader. * The file size provided by the uploader.
* *
* @var integer * @var string
*/ */
protected $size; private $size;
/** /**
* The UPLOAD_ERR_XXX constant provided by the uploader. * The UPLOAD_ERR_XXX constant provided by the uploader.
* *
* @var integer * @var integer
*/ */
protected $error; private $error;
/** /**
* Accepts the information of the uploaded file as provided by the PHP global $_FILES. * Accepts the information of the uploaded file as provided by the PHP global $_FILES.
@ -59,69 +68,65 @@ class UploadedFile extends File
* @param string $mimeType The type of the file as provided by PHP * @param string $mimeType The type of the file as provided by PHP
* @param integer $size The file size * @param integer $size The file size
* @param integer $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants) * @param integer $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants)
* @param Boolean $test Whether the test mode is active
* *
* @throws FileException If file_uploads is disabled * @throws FileException If file_uploads is disabled
* @throws FileNotFoundException If the file does not exist * @throws FileNotFoundException If the file does not exist
*/ */
public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null) public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
{ {
if (!ini_get('file_uploads')) { if (!ini_get('file_uploads')) {
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'))); 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)) {
throw new FileNotFoundException($path);
}
$this->path = realpath($path);
$this->originalName = basename($originalName); $this->originalName = basename($originalName);
$this->mimeType = $mimeType ?: 'application/octet-stream'; $this->mimeType = $mimeType ?: 'application/octet-stream';
$this->size = $size; $this->size = $size;
$this->error = $error ?: UPLOAD_ERR_OK; $this->error = $error ?: UPLOAD_ERR_OK;
$this->test = (Boolean) $test;
parent::__construct($path);
} }
/** /**
* {@inheritDoc} * Returns the original file name.
*/
public function getMimeType()
{
return parent::getMimeType() ?: $this->mimeType;
}
/**
* {@inheritDoc}
*/
public function getSize()
{
return null === $this->size ? parent::getSize() : $this->size;
}
/**
* {@inheritDoc}
*/
public function getExtension()
{
if ($ext = pathinfo($this->getOriginalName(), PATHINFO_EXTENSION)) {
return $ext;
}
return '';
}
/**
* Gets the original uploaded name.
* *
* Warning: This name is not safe as it can have been manipulated by the end-user. * It is extracted from the request from which the file has been uploaded.
* Moreover, it can contain characters that are not allowed in file names. * Then is should not be considered as a safe value.
* Never use it in a path.
* *
* @return string * @return string|null The original name
*/ */
public function getOriginalName() public function getClientOriginalName()
{ {
return $this->originalName; return $this->originalName;
} }
/**
* Returns the file mime type.
*
* It is extracted from the request from which the file has been uploaded.
* Then is should not be considered as a safe value.
*
* @return string|null The mime type
*/
public function getClientMimeType()
{
return $this->mimeType;
}
/**
* Returns the file size.
*
* It is extracted from the request from which the file has been uploaded.
* Then is should not be considered as a safe value.
*
* @return integer|null The file size
*/
public function getClientSize()
{
return $this->size;
}
/** /**
* Returns the upload error. * Returns the upload error.
* *
@ -146,17 +151,21 @@ class UploadedFile extends File
} }
/** /**
* {@inheritDoc} * Moves the file to a new location.
*
* @param string $directory The destination folder
* @param string $name The new file name
*
* @return File A File object representing the new file
*
* @throws FileException if the file has not been uploaded via Http
*/ */
public function move($directory, $name = null) public function move($directory, $name = null)
{ {
$newPath = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getName() : $name); if (!$this->test && !is_uploaded_file($this->getPathname())) {
throw new FileException(sprintf('The file "%s" has not been uploaded via Http', $this->getPathname()));
if (!@move_uploaded_file($this->getPath(), $newPath)) {
$error = error_get_last();
throw new FileException(sprintf('Could not move file %s to %s (%s)', $this->getPath(), $newPath, strip_tags($error['message'])));
} }
$this->path = realpath($newPath); return parent::move($directory, $name);
} }
} }

View File

@ -120,8 +120,15 @@ EOF;
if (is_array($value)) { if (is_array($value)) {
$filtered[$key] = $this->filterFiles($value); $filtered[$key] = $this->filterFiles($value);
} elseif ($value instanceof UploadedFile) { } elseif ($value instanceof UploadedFile) {
// create an already-moved uploaded file // Create a test mode UploadedFile
$filtered[$key] = new UploadedFile($value->getPath(), $value->getName(), $value->getMimeType(), $value->getSize(), $value->getError(), true); $filtered[$key] = new UploadedFile(
$value->getPathname(),
$value->getClientOriginalName(),
$value->getClientMimeType(),
$value->getClientSize(),
$value->getError(),
true
);
} else { } else {
$filtered[$key] = $value; $filtered[$key] = $value;
} }

View File

@ -29,11 +29,7 @@ class FileValidator extends ConstraintValidator
throw new UnexpectedTypeException($value, 'string'); throw new UnexpectedTypeException($value, 'string');
} }
if ($value instanceof FileObject && null === $value->getPath()) { $path = $value instanceof FileObject ? $value->getPathname() : (string) $value;
return true;
}
$path = $value instanceof FileObject ? $value->getPath() : (string) $value;
if (!file_exists($path)) { if (!file_exists($path)) {
$this->setMessage($constraint->notFoundMessage, array('{{ file }}' => $path)); $this->setMessage($constraint->notFoundMessage, array('{{ file }}' => $path));
@ -66,9 +62,9 @@ class FileValidator extends ConstraintValidator
if ($size > $limit) { if ($size > $limit) {
$this->setMessage($constraint->maxSizeMessage, array( $this->setMessage($constraint->maxSizeMessage, array(
'{{ size }}' => $size . $suffix, '{{ size }}' => $size . $suffix,
'{{ limit }}' => $limit . $suffix, '{{ limit }}' => $limit . $suffix,
'{{ file }}' => $path, '{{ file }}' => $path,
)); ));
return false; return false;
@ -82,9 +78,9 @@ class FileValidator extends ConstraintValidator
if (!in_array($value->getMimeType(), (array) $constraint->mimeTypes)) { if (!in_array($value->getMimeType(), (array) $constraint->mimeTypes)) {
$this->setMessage($constraint->mimeTypesMessage, array( $this->setMessage($constraint->mimeTypesMessage, array(
'{{ type }}' => '"'.$value->getMimeType().'"', '{{ type }}' => '"'.$value->getMimeType().'"',
'{{ types }}' => '"'.implode('", "', (array) $constraint->mimeTypes).'"', '{{ types }}' => '"'.implode('", "', (array) $constraint->mimeTypes).'"',
'{{ file }}' => $path, '{{ file }}' => $path,
)); ));
return false; return false;

View File

@ -30,18 +30,26 @@ class FileTypeTest extends TypeTestCase
private function createUploadedFileMock($name, $originalName, $valid) private function createUploadedFileMock($name, $originalName, $valid)
{ {
$file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') $file = $this
->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile')
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock()
$file->expects($this->any()) ;
->method('getName') $file
->will($this->returnValue($name)); ->expects($this->any())
$file->expects($this->any()) ->method('getBasename')
->method('getOriginalName') ->will($this->returnValue($name))
->will($this->returnValue($originalName)); ;
$file->expects($this->any()) $file
->expects($this->any())
->method('getClientOriginalName')
->will($this->returnValue($originalName))
;
$file
->expects($this->any())
->method('isValid') ->method('isValid')
->will($this->returnValue($valid)); ->will($this->returnValue($valid))
;
return $file; return $file;
} }

View File

@ -19,49 +19,15 @@ class FileTest extends \PHPUnit_Framework_TestCase
{ {
protected $file; protected $file;
protected function setUp()
{
$this->file = new File(__DIR__.'/Fixtures/test.gif');
}
public function testGetPathReturnsAbsolutePath()
{
$this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.gif', $this->file->getPath());
}
public function test__toString()
{
$this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.gif', (string) $this->file);
}
public function testGetNameReturnsNameWithExtension()
{
$this->assertEquals('test.gif', $this->file->getName());
}
public function testGetExtensionReturnsEmptyString()
{
$file = new File(__DIR__.'/Fixtures/test');
$this->assertEquals('', $file->getExtension());
}
public function testGetExtensionReturnsExtensionWithDot()
{
$this->assertEquals('gif', $this->file->getExtension());
}
public function testGetDirectoryReturnsDirectoryName()
{
$this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures', $this->file->getDirectory());
}
public function testGetMimeTypeUsesMimeTypeGuessers() public function testGetMimeTypeUsesMimeTypeGuessers()
{ {
$guesser = $this->createMockGuesser($this->file->getPath(), 'image/gif'); $file = new File(__DIR__.'/Fixtures/test.gif');
$guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
MimeTypeGuesser::getInstance()->register($guesser); MimeTypeGuesser::getInstance()->register($guesser);
$this->assertEquals('image/gif', $this->file->getMimeType()); $this->assertEquals('image/gif', $file->getMimeType());
} }
public function testGuessExtensionWithoutGuesser() public function testGuessExtensionWithoutGuesser()
@ -74,7 +40,7 @@ class FileTest extends \PHPUnit_Framework_TestCase
public function testGuessExtensionIsBasedOnMimeType() public function testGuessExtensionIsBasedOnMimeType()
{ {
$file = new File(__DIR__.'/Fixtures/test'); $file = new File(__DIR__.'/Fixtures/test');
$guesser = $this->createMockGuesser($file->getPath(), 'image/gif'); $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif');
MimeTypeGuesser::getInstance()->register($guesser); MimeTypeGuesser::getInstance()->register($guesser);
@ -88,100 +54,83 @@ class FileTest extends \PHPUnit_Framework_TestCase
new File(__DIR__.'/Fixtures/not_here'); new File(__DIR__.'/Fixtures/not_here');
} }
public function testSizeReturnsFileSize()
{
$this->assertEquals(filesize($this->file->getPath()), $this->file->getSize());
}
public function testSizeFailing()
{
$dir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'directory';
$path = $dir.DIRECTORY_SEPARATOR.'test.copy.gif';
@unlink($path);
copy(__DIR__.'/Fixtures/test.gif', $path);
$file = new File($path);
@unlink($path);
try {
$file->getSize();
$this->fail('File::getSize should throw an exception.');
} catch (FileException $e) {
}
}
public function testMove() public function testMove()
{ {
$path = __DIR__.'/Fixtures/test.copy.gif'; $path = __DIR__.'/Fixtures/test.copy.gif';
$targetDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'directory'; $targetDir = __DIR__.'/Fixtures/directory';
$targetPath = $targetDir.DIRECTORY_SEPARATOR.'test.copy.gif'; $targetPath = $targetDir.'/test.copy.gif';
@unlink($path); @unlink($path);
@unlink($targetPath); @unlink($targetPath);
copy(__DIR__.'/Fixtures/test.gif', $path); copy(__DIR__.'/Fixtures/test.gif', $path);
$file = new File($path); $file = new File($path);
$file->move($targetDir); $movedFile = $file->move($targetDir);
$this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile);
$this->assertTrue(file_exists($targetPath)); $this->assertTrue(file_exists($targetPath));
$this->assertFalse(file_exists($path)); $this->assertFalse(file_exists($path));
$this->assertEquals($targetPath, $file->getPath()); $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
@unlink($path);
@unlink($targetPath); @unlink($targetPath);
} }
public function testMoveWithNewName() public function testMoveWithNewName()
{ {
$path = __DIR__.'/Fixtures/test.copy.gif'; $path = __DIR__.'/Fixtures/test.copy.gif';
$targetDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'directory'; $targetDir = __DIR__.'/Fixtures/directory';
$targetPath = $targetDir.DIRECTORY_SEPARATOR.'test.newname.gif'; $targetPath = $targetDir.'/test.newname.gif';
@unlink($path); @unlink($path);
@unlink($targetPath); @unlink($targetPath);
copy(__DIR__.'/Fixtures/test.gif', $path); copy(__DIR__.'/Fixtures/test.gif', $path);
$file = new File($path); $file = new File($path);
$file->move($targetDir, 'test.newname.gif'); $movedFile = $file->move($targetDir, 'test.newname.gif');
$this->assertTrue(file_exists($targetPath)); $this->assertTrue(file_exists($targetPath));
$this->assertFalse(file_exists($path)); $this->assertFalse(file_exists($path));
$this->assertEquals($targetPath, $file->getPath()); $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
@unlink($path);
@unlink($targetPath); @unlink($targetPath);
} }
public function testMoveFailing() public function testMoveToAnUnexistentDirectory()
{ {
$path = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.copy.gif'; $sourcePath = __DIR__.'/Fixtures/test.copy.gif';
$targetPath = '/thisfolderwontexist'; $targetDir = __DIR__.'/Fixtures/directory/sub';
@unlink($path); $targetPath = $targetDir.'/test.copy.gif';
@unlink($sourcePath);
@unlink($targetPath); @unlink($targetPath);
copy(__DIR__.'/Fixtures/test.gif', $path); @rmdir($targetDir);
copy(__DIR__.'/Fixtures/test.gif', $sourcePath);
$file = new File($path); $file = new File($sourcePath);
$movedFile = $file->move($targetDir);
try { $this->assertFileExists($targetPath);
$file->move($targetPath); $this->assertFileNotExists($sourcePath);
$this->fail('File::move should throw an exception.'); $this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
} catch (FileException $e) {
}
$this->assertFileExists($path); @unlink($sourcePath);
$this->assertFileNotExists($path.$targetPath.'test.gif');
$this->assertEquals($path, $file->getPath());
@unlink($path);
@unlink($targetPath); @unlink($targetPath);
@rmdir($targetDir);
}
public function testGetExtension()
{
$file = new File(__DIR__.'/Fixtures/test.gif');
$this->assertEquals('gif', $file->getExtension());
} }
protected function createMockGuesser($path, $mimeType) protected function createMockGuesser($path, $mimeType)
{ {
$guesser = $this->getMock('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface'); $guesser = $this->getMock('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface');
$guesser->expects($this->once()) $guesser
->method('guess') ->expects($this->once())
->with($this->equalTo($path)) ->method('guess')
->will($this->returnValue($mimeType)); ->with($this->equalTo($path))
->will($this->returnValue($mimeType))
;
return $guesser; return $guesser;
} }
} }

View File

@ -32,11 +32,10 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
UPLOAD_ERR_OK UPLOAD_ERR_OK
); );
$this->assertAttributeEquals('application/octet-stream', 'mimeType', $file); $this->assertEquals('application/octet-stream', $file->getClientMimeType());
if (extension_loaded('fileinfo')) { if (extension_loaded('fileinfo')) {
$this->assertEquals('image/gif', $file->getMimeType()); $this->assertEquals('image/gif', $file->getMimeType());
} else {
$this->assertEquals('application/octet-stream', $file->getMimeType());
} }
} }
@ -50,8 +49,7 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
UPLOAD_ERR_OK UPLOAD_ERR_OK
); );
$this->assertAttributeEquals('application/octet-stream', 'mimeType', $file); $this->assertEquals('application/octet-stream', $file->getClientMimeType());
$this->assertEquals('application/octet-stream', $file->getMimeType());
} }
public function testErrorIsOkByDefault() public function testErrorIsOkByDefault()
@ -67,7 +65,7 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(UPLOAD_ERR_OK, $file->getError()); $this->assertEquals(UPLOAD_ERR_OK, $file->getError());
} }
public function testGetOriginalName() public function testGetClientOriginalName()
{ {
$file = new UploadedFile( $file = new UploadedFile(
__DIR__.'/Fixtures/test.gif', __DIR__.'/Fixtures/test.gif',
@ -77,10 +75,54 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
null null
); );
$this->assertEquals('original.gif', $file->getOriginalName()); $this->assertEquals('original.gif', $file->getClientOriginalName());
} }
public function testGetOriginalNameSanitizeFilename() /**
* @expectedException Symfony\Component\HttpFoundation\File\Exception\FileException
*/
public function testMoveLocalFileIsNotAllowed()
{
$file = new UploadedFile(
__DIR__.'/Fixtures/test.gif',
'original.gif',
'image/gif',
filesize(__DIR__.'/Fixtures/test.gif'),
UPLOAD_ERR_OK
);
$movedFile = $file->move(__DIR__.'/Fixtures/directory');
}
public function testMoveLocalFileIsAllowedInTestMode()
{
$path = __DIR__.'/Fixtures/test.copy.gif';
$targetDir = __DIR__.'/Fixtures/directory';
$targetPath = $targetDir.'/test.copy.gif';
@unlink($path);
@unlink($targetPath);
copy(__DIR__.'/Fixtures/test.gif', $path);
$file = new UploadedFile(
$path,
'original.gif',
'image/gif',
filesize($path),
UPLOAD_ERR_OK,
true
);
$movedFile = $file->move(__DIR__.'/Fixtures/directory');
$this->assertTrue(file_exists($targetPath));
$this->assertFalse(file_exists($path));
$this->assertEquals(realpath($targetPath), $movedFile->getRealPath());
@unlink($targetPath);
}
public function testGetClientOriginalNameSanitizeFilename()
{ {
$file = new UploadedFile( $file = new UploadedFile(
__DIR__.'/Fixtures/test.gif', __DIR__.'/Fixtures/test.gif',
@ -90,6 +132,6 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase
null null
); );
$this->assertEquals('original.gif', $file->getOriginalName()); $this->assertEquals('original.gif', $file->getClientOriginalName());
} }
} }

View File

@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\File\UploadedFile;
require_once __DIR__.'/TestHttpKernel.php'; require_once __DIR__.'/TestHttpKernel.php';
@ -76,4 +77,32 @@ class ClientTest extends \PHPUnit_Framework_TestCase
$domResponse = $m->invoke($client, $response); $domResponse = $m->invoke($client, $response);
$this->assertEquals('foo=bar; expires=Sun, 15-Feb-2009 20:00:00 GMT; domain=http://example.com; path=/foo; secure; httponly, foo1=bar1; expires=Sun, 15-Feb-2009 20:00:00 GMT; domain=http://example.com; path=/foo; secure; httponly', $domResponse->getHeader('Set-Cookie')); $this->assertEquals('foo=bar; expires=Sun, 15-Feb-2009 20:00:00 GMT; domain=http://example.com; path=/foo; secure; httponly, foo1=bar1; expires=Sun, 15-Feb-2009 20:00:00 GMT; domain=http://example.com; path=/foo; secure; httponly', $domResponse->getHeader('Set-Cookie'));
} }
public function testUploadedFile()
{
$source = tempnam(sys_get_temp_dir(), 'source');
$target = sys_get_temp_dir().'/sf.moved.file';
@unlink($target);
$kernel = new TestHttpKernel();
$client = new Client($kernel);
$client->request('POST', '/', array(), array(new UploadedFile($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK)));
$files = $kernel->request->files->all();
$this->assertEquals(1, count($files));
$file = $files[0];
$this->assertEquals('original', $file->getClientOriginalName());
$this->assertEquals('mime/original', $file->getClientMimeType());
$this->assertEquals('123', $file->getClientSize());
$this->assertTrue($file->isValid());
$file->move(dirname($target), basename($target));
$this->assertFileExists($target);
unlink($target);
}
} }

View File

@ -19,6 +19,8 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
class TestHttpKernel extends HttpKernel implements ControllerResolverInterface class TestHttpKernel extends HttpKernel implements ControllerResolverInterface
{ {
public $request;
public function __construct() public function __construct()
{ {
parent::__construct(new EventDispatcher(), $this); parent::__construct(new EventDispatcher(), $this);
@ -36,6 +38,7 @@ class TestHttpKernel extends HttpKernel implements ControllerResolverInterface
public function callController(Request $request) public function callController(Request $request)
{ {
$this->request = $request;
return new Response('Request: '.$request->getRequestUri()); return new Response('Request: '.$request->getRequestUri());
} }
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Tests\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\FileValidator; use Symfony\Component\Validator\Constraints\FileValidator;
use Symfony\Component\HttpFoundation\File\File as FileObject;
class FileValidatorTest extends \PHPUnit_Framework_TestCase class FileValidatorTest extends \PHPUnit_Framework_TestCase
{ {
@ -42,10 +43,11 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->validator->isValid('', new File())); $this->assertTrue($this->validator->isValid('', new File()));
} }
/**
* @expectedException Symfony\Component\Validator\Exception\UnexpectedTypeException
*/
public function testExpectsStringCompatibleTypeOrFile() public function testExpectsStringCompatibleTypeOrFile()
{ {
$this->setExpectedException('Symfony\Component\Validator\Exception\UnexpectedTypeException');
$this->validator->isValid(new \stdClass(), new File()); $this->validator->isValid(new \stdClass(), new File());
} }
@ -59,16 +61,16 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
fwrite($this->file, str_repeat('0', 11)); fwrite($this->file, str_repeat('0', 11));
$constraint = new File(array( $constraint = new File(array(
'maxSize' => 10, 'maxSize' => 10,
'maxSizeMessage' => 'myMessage', 'maxSizeMessage' => 'myMessage',
)); ));
$this->assertFalse($this->validator->isValid($this->path, $constraint)); $this->assertFileValid($this->path, $constraint, false);
$this->assertEquals($this->validator->getMessageTemplate(), 'myMessage'); $this->assertEquals($this->validator->getMessageTemplate(), 'myMessage');
$this->assertEquals($this->validator->getMessageParameters(), array( $this->assertEquals($this->validator->getMessageParameters(), array(
'{{ limit }}' => '10 bytes', '{{ limit }}' => '10 bytes',
'{{ size }}' => '11 bytes', '{{ size }}' => '11 bytes',
'{{ file }}' => $this->path, '{{ file }}' => $this->path,
)); ));
} }
@ -77,16 +79,16 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
fwrite($this->file, str_repeat('0', 1400)); fwrite($this->file, str_repeat('0', 1400));
$constraint = new File(array( $constraint = new File(array(
'maxSize' => '1k', 'maxSize' => '1k',
'maxSizeMessage' => 'myMessage', 'maxSizeMessage' => 'myMessage',
)); ));
$this->assertFalse($this->validator->isValid($this->path, $constraint)); $this->assertFileValid($this->path, $constraint, false);
$this->assertEquals($this->validator->getMessageTemplate(), 'myMessage'); $this->assertEquals($this->validator->getMessageTemplate(), 'myMessage');
$this->assertEquals($this->validator->getMessageParameters(), array( $this->assertEquals($this->validator->getMessageParameters(), array(
'{{ limit }}' => '1 kB', '{{ limit }}' => '1 kB',
'{{ size }}' => '1.4 kB', '{{ size }}' => '1.4 kB',
'{{ file }}' => $this->path, '{{ file }}' => $this->path,
)); ));
} }
@ -95,27 +97,28 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
fwrite($this->file, str_repeat('0', 1400000)); fwrite($this->file, str_repeat('0', 1400000));
$constraint = new File(array( $constraint = new File(array(
'maxSize' => '1M', 'maxSize' => '1M',
'maxSizeMessage' => 'myMessage', 'maxSizeMessage' => 'myMessage',
)); ));
$this->assertFalse($this->validator->isValid($this->path, $constraint)); $this->assertFileValid($this->path, $constraint, false);
$this->assertEquals($this->validator->getMessageTemplate(), 'myMessage'); $this->assertEquals($this->validator->getMessageTemplate(), 'myMessage');
$this->assertEquals($this->validator->getMessageParameters(), array( $this->assertEquals($this->validator->getMessageParameters(), array(
'{{ limit }}' => '1 MB', '{{ limit }}' => '1 MB',
'{{ size }}' => '1.4 MB', '{{ size }}' => '1.4 MB',
'{{ file }}' => $this->path, '{{ file }}' => $this->path,
)); ));
} }
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testInvalidMaxSize() public function testInvalidMaxSize()
{ {
$constraint = new File(array( $constraint = new File(array(
'maxSize' => '1abc', 'maxSize' => '1abc',
)); ));
$this->setExpectedException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
$this->validator->isValid($this->path, $constraint); $this->validator->isValid($this->path, $constraint);
} }
@ -125,7 +128,7 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
'notFoundMessage' => 'myMessage', 'notFoundMessage' => 'myMessage',
)); ));
$this->assertFalse($this->validator->isValid('foobar', $constraint)); $this->assertFileValid('foobar', $constraint, false);
$this->assertEquals($this->validator->getMessageTemplate(), 'myMessage'); $this->assertEquals($this->validator->getMessageTemplate(), 'myMessage');
$this->assertEquals($this->validator->getMessageParameters(), array( $this->assertEquals($this->validator->getMessageParameters(), array(
'{{ file }}' => 'foobar', '{{ file }}' => 'foobar',
@ -134,13 +137,21 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
public function testValidMimeType() public function testValidMimeType()
{ {
$file = $this->getMock('Symfony\Component\HttpFoundation\File\File', array(), array(), '', false); $file = $this
$file->expects($this->any()) ->getMockBuilder('Symfony\Component\HttpFoundation\File\File')
->method('getPath') ->disableOriginalConstructor()
->will($this->returnValue($this->path)); ->getMock()
$file->expects($this->any()) ;
->method('getMimeType') $file
->will($this->returnValue('image/jpg')); ->expects($this->once())
->method('getPathname')
->will($this->returnValue($this->path))
;
$file
->expects($this->once())
->method('getMimeType')
->will($this->returnValue('image/jpg'))
;
$constraint = new File(array( $constraint = new File(array(
'mimeTypes' => array('image/png', 'image/jpg'), 'mimeTypes' => array('image/png', 'image/jpg'),
@ -151,13 +162,21 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
public function testInvalidMimeType() public function testInvalidMimeType()
{ {
$file = $this->getMock('Symfony\Component\HttpFoundation\File\File', array(), array(), '', false); $file = $this
$file->expects($this->any()) ->getMockBuilder('Symfony\Component\HttpFoundation\File\File')
->method('getPath') ->disableOriginalConstructor()
->will($this->returnValue($this->path)); ->getMock()
$file->expects($this->any()) ;
->method('getMimeType') $file
->will($this->returnValue('application/pdf')); ->expects($this->once())
->method('getPathname')
->will($this->returnValue($this->path))
;
$file
->expects($this->exactly(2))
->method('getMimeType')
->will($this->returnValue('application/pdf'))
;
$constraint = new File(array( $constraint = new File(array(
'mimeTypes' => array('image/png', 'image/jpg'), 'mimeTypes' => array('image/png', 'image/jpg'),
@ -167,9 +186,17 @@ class FileValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($this->validator->isValid($file, $constraint)); $this->assertFalse($this->validator->isValid($file, $constraint));
$this->assertEquals($this->validator->getMessageTemplate(), 'myMessage'); $this->assertEquals($this->validator->getMessageTemplate(), 'myMessage');
$this->assertEquals($this->validator->getMessageParameters(), array( $this->assertEquals($this->validator->getMessageParameters(), array(
'{{ type }}' => '"application/pdf"', '{{ type }}' => '"application/pdf"',
'{{ types }}' => '"image/png", "image/jpg"', '{{ types }}' => '"image/png", "image/jpg"',
'{{ file }}' => $this->path, '{{ file }}' => $this->path,
)); ));
} }
}
protected function assertFileValid($filename, File $constraint, $valid = true)
{
$this->assertEquals($this->validator->isValid($filename, $constraint), $valid);
if (file_exists($filename)) {
$this->assertEquals($this->validator->isValid(new FileObject($filename), $constraint), $valid);
}
}
}