[Validator] Improved to-string conversion of the file size/size limit
This commit is contained in:
parent
bbe1045989
commit
e4c6da548b
@ -25,6 +25,16 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
*/
|
||||
class FileValidator extends ConstraintValidator
|
||||
{
|
||||
const KB_BYTES = 1000;
|
||||
|
||||
const MB_BYTES = 1000000;
|
||||
|
||||
private static $suffices = array(
|
||||
1 => 'bytes',
|
||||
self::KB_BYTES => 'kB',
|
||||
self::MB_BYTES => 'MB',
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -43,21 +53,21 @@ class FileValidator extends ConstraintValidator
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
if ($constraint->maxSize) {
|
||||
if (ctype_digit((string) $constraint->maxSize)) {
|
||||
$maxSize = (int) $constraint->maxSize;
|
||||
$limitInBytes = (int) $constraint->maxSize;
|
||||
} elseif (preg_match('/^\d++k$/', $constraint->maxSize)) {
|
||||
$maxSize = $constraint->maxSize * 1000;
|
||||
$limitInBytes = $constraint->maxSize * self::KB_BYTES;
|
||||
} elseif (preg_match('/^\d++M$/', $constraint->maxSize)) {
|
||||
$maxSize = $constraint->maxSize * 1000 * 1000;
|
||||
$limitInBytes = $constraint->maxSize * self::MB_BYTES;
|
||||
} else {
|
||||
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
|
||||
}
|
||||
$maxSize = min(UploadedFile::getMaxFilesize(), $maxSize);
|
||||
$limitInBytes = min(UploadedFile::getMaxFilesize(), $limitInBytes);
|
||||
} else {
|
||||
$maxSize = UploadedFile::getMaxFilesize();
|
||||
$limitInBytes = UploadedFile::getMaxFilesize();
|
||||
}
|
||||
|
||||
$this->context->addViolation($constraint->uploadIniSizeErrorMessage, array(
|
||||
'{{ limit }}' => $maxSize,
|
||||
'{{ limit }}' => $limitInBytes,
|
||||
'{{ suffix }}' => 'bytes',
|
||||
));
|
||||
|
||||
@ -112,27 +122,45 @@ class FileValidator extends ConstraintValidator
|
||||
}
|
||||
|
||||
if ($constraint->maxSize) {
|
||||
if (ctype_digit((string) $constraint->maxSize)) {
|
||||
$size = filesize($path);
|
||||
$limit = (int) $constraint->maxSize;
|
||||
$suffix = 'bytes';
|
||||
} elseif (preg_match('/^\d++k$/', $constraint->maxSize)) {
|
||||
$size = round(filesize($path) / 1000, 2);
|
||||
$limit = (int) $constraint->maxSize;
|
||||
$suffix = 'KB';
|
||||
$sizeInBytes = filesize($path);
|
||||
$limitInBytes = (int) $constraint->maxSize;
|
||||
|
||||
if (preg_match('/^\d++k$/', $constraint->maxSize)) {
|
||||
$limitInBytes *= self::KB_BYTES;
|
||||
} elseif (preg_match('/^\d++M$/', $constraint->maxSize)) {
|
||||
$size = round(filesize($path) / (1000 * 1000), 2);
|
||||
$limit = (int) $constraint->maxSize;
|
||||
$suffix = 'MB';
|
||||
} else {
|
||||
$limitInBytes *= self::MB_BYTES;
|
||||
} elseif (!ctype_digit((string) $constraint->maxSize)) {
|
||||
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
|
||||
}
|
||||
|
||||
if ($size > $limit) {
|
||||
if ($sizeInBytes > $limitInBytes) {
|
||||
// Convert the limit to the smallest possible number
|
||||
// (i.e. try "MB", then "kB", then "bytes")
|
||||
$coef = self::MB_BYTES;
|
||||
$limitAsString = (string) ($limitInBytes / $coef);
|
||||
|
||||
// Restrict the limit to 2 decimals (without rounding! we
|
||||
// need the precise value)
|
||||
while (self::moreDecimalsThan($limitAsString, 2)) {
|
||||
$coef /= 1000;
|
||||
$limitAsString = (string) ($limitInBytes / $coef);
|
||||
}
|
||||
|
||||
// Convert size to the same measure, but round to 2 decimals
|
||||
$sizeAsString = (string) round($sizeInBytes / $coef, 2);
|
||||
|
||||
// If the size and limit produce the same string output
|
||||
// (due to rounding), reduce the coefficient
|
||||
while ($sizeAsString === $limitAsString) {
|
||||
$coef /= 1000;
|
||||
$limitAsString = (string) ($limitInBytes / $coef);
|
||||
$sizeAsString = (string) round($sizeInBytes / $coef, 2);
|
||||
}
|
||||
|
||||
$this->context->addViolation($constraint->maxSizeMessage, array(
|
||||
'{{ size }}' => $size,
|
||||
'{{ limit }}' => $limit,
|
||||
'{{ suffix }}' => $suffix,
|
||||
'{{ size }}' => $sizeAsString,
|
||||
'{{ limit }}' => $limitAsString,
|
||||
'{{ suffix }}' => static::$suffices[$coef],
|
||||
'{{ file }}' => $path,
|
||||
));
|
||||
|
||||
@ -172,4 +200,9 @@ class FileValidator extends ConstraintValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function moreDecimalsThan($double, $numberOfDecimals)
|
||||
{
|
||||
return strlen((string) $double) > strlen(round($double, $numberOfDecimals));
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,13 @@ abstract class FileValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
fclose($this->file);
|
||||
if (is_resource($this->file)) {
|
||||
fclose($this->file);
|
||||
}
|
||||
|
||||
if (file_exists($this->path)) {
|
||||
unlink($this->path);
|
||||
}
|
||||
|
||||
$this->context = null;
|
||||
$this->validator = null;
|
||||
@ -82,93 +88,101 @@ abstract class FileValidatorTest extends \PHPUnit_Framework_TestCase
|
||||
$this->validator->validate($file, new File());
|
||||
}
|
||||
|
||||
public function testTooLargeBytes()
|
||||
public function provideMaxSizeExceededTests()
|
||||
{
|
||||
fwrite($this->file, str_repeat('0', 11));
|
||||
return array(
|
||||
array(11, 10, '11', '10', 'bytes'),
|
||||
|
||||
array(ceil(1.005*1000), ceil(1.005*1000) - 1, '1005', '1004', 'bytes'),
|
||||
array(ceil(1.005*1000*1000), ceil(1.005*1000*1000) - 1, '1005000', '1004999', 'bytes'),
|
||||
|
||||
// round(size) == 1.01kB, limit == 1kB
|
||||
array(ceil(1.005*1000), 1000, '1.01', '1', 'kB'),
|
||||
array(ceil(1.005*1000), '1k', '1.01', '1', 'kB'),
|
||||
|
||||
// round(size) == 1kB, limit == 1kB -> use bytes
|
||||
array(ceil(1.004*1000), 1000, '1004', '1000', 'bytes'),
|
||||
array(ceil(1.004*1000), '1k', '1004', '1000', 'bytes'),
|
||||
|
||||
array(1000 + 1, 1000, '1001', '1000', 'bytes'),
|
||||
array(1000 + 1, '1k', '1001', '1000', 'bytes'),
|
||||
|
||||
// round(size) == 1.01MB, limit == 1MB
|
||||
array(ceil(1.005*1000*1000), 1000*1000, '1.01', '1', 'MB'),
|
||||
array(ceil(1.005*1000*1000), '1000k', '1.01', '1', 'MB'),
|
||||
array(ceil(1.005*1000*1000), '1M', '1.01', '1', 'MB'),
|
||||
|
||||
// round(size) == 1MB, limit == 1MB -> use kB
|
||||
array(ceil(1.004*1000*1000), 1000*1000, '1004', '1000', 'kB'),
|
||||
array(ceil(1.004*1000*1000), '1000k', '1004', '1000', 'kB'),
|
||||
array(ceil(1.004*1000*1000), '1M', '1004', '1000', 'kB'),
|
||||
|
||||
array(1000*1000 + 1, 1000*1000, '1000001', '1000000', 'bytes'),
|
||||
array(1000*1000 + 1, '1000k', '1000001', '1000000', 'bytes'),
|
||||
array(1000*1000 + 1, '1M', '1000001', '1000000', 'bytes'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideMaxSizeExceededTests
|
||||
*/
|
||||
public function testMaxSizeExceeded($bytesWritten, $limit, $sizeAsString, $limitAsString, $suffix)
|
||||
{
|
||||
fseek($this->file, $bytesWritten-1, SEEK_SET);
|
||||
fwrite($this->file, '0');
|
||||
fclose($this->file);
|
||||
|
||||
$constraint = new File(array(
|
||||
'maxSize' => 10,
|
||||
'maxSize' => $limit,
|
||||
'maxSizeMessage' => 'myMessage',
|
||||
));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('myMessage', array(
|
||||
'{{ limit }}' => '10',
|
||||
'{{ size }}' => '11',
|
||||
'{{ suffix }}' => 'bytes',
|
||||
'{{ limit }}' => $limitAsString,
|
||||
'{{ size }}' => $sizeAsString,
|
||||
'{{ suffix }}' => $suffix,
|
||||
'{{ file }}' => $this->path,
|
||||
));
|
||||
|
||||
$this->validator->validate($this->getFile($this->path), $constraint);
|
||||
}
|
||||
|
||||
public function testTooLargeKiloBytes()
|
||||
public function provideMaxSizeNotExceededTests()
|
||||
{
|
||||
fwrite($this->file, str_repeat('0', 1400));
|
||||
return array(
|
||||
array(10, 10),
|
||||
array(9, 10),
|
||||
|
||||
array(1000, '1k'),
|
||||
array(1000 - 1, '1k'),
|
||||
|
||||
array(1000*1000, '1M'),
|
||||
array(1000*1000 - 1, '1M'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideMaxSizeNotExceededTests
|
||||
*/
|
||||
public function testMaxSizeNotExceeded($bytesWritten, $limit)
|
||||
{
|
||||
fseek($this->file, $bytesWritten-1, SEEK_SET);
|
||||
fwrite($this->file, '0');
|
||||
fclose($this->file);
|
||||
|
||||
$constraint = new File(array(
|
||||
'maxSize' => '1k',
|
||||
'maxSize' => $limit,
|
||||
'maxSizeMessage' => 'myMessage',
|
||||
));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('myMessage', array(
|
||||
'{{ limit }}' => '1',
|
||||
'{{ size }}' => '1.37',
|
||||
'{{ suffix }}' => 'KiB',
|
||||
'{{ file }}' => $this->path,
|
||||
));
|
||||
$this->context->expects($this->never())
|
||||
->method('addViolation');
|
||||
|
||||
$this->validator->validate($this->getFile($this->path), $constraint);
|
||||
}
|
||||
|
||||
public function testTooLargeMegaBytes()
|
||||
{
|
||||
fwrite($this->file, str_repeat('0', 1400000));
|
||||
|
||||
$constraint = new File(array(
|
||||
'maxSize' => '1M',
|
||||
'maxSizeMessage' => 'myMessage',
|
||||
));
|
||||
|
||||
$this->context->expects($this->once())
|
||||
->method('addViolation')
|
||||
->with('myMessage', array(
|
||||
'{{ limit }}' => '1',
|
||||
'{{ size }}' => '1.34',
|
||||
'{{ suffix }}' => 'MiB',
|
||||
'{{ file }}' => $this->path,
|
||||
));
|
||||
|
||||
$this->validator->validate($this->getFile($this->path), $constraint);
|
||||
}
|
||||
|
||||
public function testMaxSizeKiloBytes()
|
||||
{
|
||||
fwrite($this->file, str_repeat('0', 1010));
|
||||
|
||||
$constraint = new File(array(
|
||||
'maxSize' => '1k',
|
||||
));
|
||||
|
||||
$this->context->expects($this->never())->method('addViolation');
|
||||
$this->validator->validate($this->getFile($this->path), $constraint);
|
||||
}
|
||||
|
||||
public function testMaxSizeMegaBytes()
|
||||
{
|
||||
fwrite($this->file, str_repeat('0', (1024 * 1022)));
|
||||
|
||||
$constraint = new File(array(
|
||||
'maxSize' => '1M',
|
||||
));
|
||||
|
||||
$this->context->expects($this->never())->method('addViolation');
|
||||
$this->validator->validate($this->getFile($this->path), $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
*/
|
||||
|
Reference in New Issue
Block a user