Skip to content

Commit 2ee3239

Browse files
committed
feature #11027 [Validator] Support "maxSize" given in KiB (Jérémy Derussé)
This PR was squashed before being merged into the 2.6-dev branch (closes #11027). Discussion ---------- [Validator] Support "maxSize" given in KiB | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #10962 | License | MIT | Doc PR | symfony/symfony-docs#3895 To display the constraint validation message with an expected suffix, this PR add a new option in File constraint. Commits ------- 48ed754 [Validator] Support "maxSize" given in KiB
2 parents a455848 + 22e6b68 commit 2ee3239

File tree

4 files changed

+213
-23
lines changed

4 files changed

+213
-23
lines changed

Constraints/File.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Validator\Constraints;
1313

1414
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1516

1617
/**
1718
* @Annotation
@@ -24,6 +25,7 @@
2425
class File extends Constraint
2526
{
2627
public $maxSize = null;
28+
public $binaryFormat = null;
2729
public $mimeTypes = array();
2830
public $notFoundMessage = 'The file could not be found.';
2931
public $notReadableMessage = 'The file is not readable.';
@@ -39,4 +41,30 @@ class File extends Constraint
3941
public $uploadCantWriteErrorMessage = 'Cannot write temporary file to disk.';
4042
public $uploadExtensionErrorMessage = 'A PHP extension caused the upload to fail.';
4143
public $uploadErrorMessage = 'The file could not be uploaded.';
44+
45+
public function __construct($options = null)
46+
{
47+
parent::__construct($options);
48+
49+
if ($this->maxSize) {
50+
if (ctype_digit((string) $this->maxSize)) {
51+
$this->maxSize = (int) $this->maxSize;
52+
$this->binaryFormat = $this->binaryFormat === null ? false : $this->binaryFormat;
53+
} elseif (preg_match('/^\d++k$/i', $this->maxSize)) {
54+
$this->maxSize = $this->maxSize * 1000;
55+
$this->binaryFormat = $this->binaryFormat === null ? false : $this->binaryFormat;
56+
} elseif (preg_match('/^\d++M$/i', $this->maxSize)) {
57+
$this->maxSize = $this->maxSize * 1000000;
58+
$this->binaryFormat = $this->binaryFormat === null ? false : $this->binaryFormat;
59+
} elseif (preg_match('/^\d++ki$/i', $this->maxSize)) {
60+
$this->maxSize = $this->maxSize << 10;
61+
$this->binaryFormat = $this->binaryFormat === null ? true : $this->binaryFormat;
62+
} elseif (preg_match('/^\d++Mi$/i', $this->maxSize)) {
63+
$this->maxSize = $this->maxSize << 20;
64+
$this->binaryFormat = $this->binaryFormat === null ? true : $this->binaryFormat;
65+
} else {
66+
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $this->maxSize));
67+
}
68+
}
69+
}
4270
}

Constraints/FileValidator.php

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Symfony\Component\HttpFoundation\File\UploadedFile;
1616
use Symfony\Component\Validator\Constraint;
1717
use Symfony\Component\Validator\ConstraintValidator;
18-
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1918
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
2019

2120
/**
@@ -26,13 +25,16 @@
2625
class FileValidator extends ConstraintValidator
2726
{
2827
const KB_BYTES = 1000;
29-
3028
const MB_BYTES = 1000000;
29+
const KIB_BYTES = 1024;
30+
const MIB_BYTES = 1048576;
3131

3232
private static $suffices = array(
3333
1 => 'bytes',
3434
self::KB_BYTES => 'kB',
3535
self::MB_BYTES => 'MB',
36+
self::KIB_BYTES => 'KiB',
37+
self::MIB_BYTES => 'MiB',
3638
);
3739

3840
/**
@@ -52,16 +54,7 @@ public function validate($value, Constraint $constraint)
5254
switch ($value->getError()) {
5355
case UPLOAD_ERR_INI_SIZE:
5456
if ($constraint->maxSize) {
55-
if (ctype_digit((string) $constraint->maxSize)) {
56-
$limitInBytes = (int) $constraint->maxSize;
57-
} elseif (preg_match('/^\d++k$/', $constraint->maxSize)) {
58-
$limitInBytes = $constraint->maxSize * self::KB_BYTES;
59-
} elseif (preg_match('/^\d++M$/', $constraint->maxSize)) {
60-
$limitInBytes = $constraint->maxSize * self::MB_BYTES;
61-
} else {
62-
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
63-
}
64-
$limitInBytes = min(UploadedFile::getMaxFilesize(), $limitInBytes);
57+
$limitInBytes = min(UploadedFile::getMaxFilesize(), (int) $constraint->maxSize);
6558
} else {
6659
$limitInBytes = UploadedFile::getMaxFilesize();
6760
}
@@ -127,24 +120,23 @@ public function validate($value, Constraint $constraint)
127120
} elseif ($constraint->maxSize) {
128121
$limitInBytes = (int) $constraint->maxSize;
129122

130-
if (preg_match('/^\d++k$/', $constraint->maxSize)) {
131-
$limitInBytes *= self::KB_BYTES;
132-
} elseif (preg_match('/^\d++M$/', $constraint->maxSize)) {
133-
$limitInBytes *= self::MB_BYTES;
134-
} elseif (!ctype_digit((string) $constraint->maxSize)) {
135-
throw new ConstraintDefinitionException(sprintf('"%s" is not a valid maximum size', $constraint->maxSize));
136-
}
137-
138123
if ($sizeInBytes > $limitInBytes) {
139124
// Convert the limit to the smallest possible number
140125
// (i.e. try "MB", then "kB", then "bytes")
141-
$coef = self::MB_BYTES;
126+
if ($constraint->binaryFormat) {
127+
$coef = self::MIB_BYTES;
128+
$coefFactor = self::KIB_BYTES;
129+
} else {
130+
$coef = self::MB_BYTES;
131+
$coefFactor = self::KB_BYTES;
132+
}
133+
142134
$limitAsString = (string) ($limitInBytes / $coef);
143135

144136
// Restrict the limit to 2 decimals (without rounding! we
145137
// need the precise value)
146138
while (self::moreDecimalsThan($limitAsString, 2)) {
147-
$coef /= 1000;
139+
$coef /= $coefFactor;
148140
$limitAsString = (string) ($limitInBytes / $coef);
149141
}
150142

@@ -154,7 +146,7 @@ public function validate($value, Constraint $constraint)
154146
// If the size and limit produce the same string output
155147
// (due to rounding), reduce the coefficient
156148
while ($sizeAsString === $limitAsString) {
157-
$coef /= 1000;
149+
$coef /= $coefFactor;
158150
$limitAsString = (string) ($limitInBytes / $coef);
159151
$sizeAsString = (string) round($sizeInBytes / $coef, 2);
160152
}

Tests/Constraints/FileTest.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use Symfony\Component\Validator\Constraints\File;
15+
16+
class FileTest extends \PHPUnit_Framework_TestCase
17+
{
18+
19+
/**
20+
* @param mixed $maxSize
21+
* @param int bytes
22+
* @param bool $bytes
23+
* @dataProvider provideValidSizes
24+
*/
25+
public function testMaxSize($maxSize, $bytes, $binaryFormat)
26+
{
27+
$file = new File(array('maxSize' => $maxSize));
28+
29+
$this->assertSame($bytes, $file->maxSize);
30+
$this->assertSame($binaryFormat, $file->binaryFormat);
31+
}
32+
33+
/**
34+
* @param mixed $maxSize
35+
* @param int $bytes
36+
* @dataProvider provideInValidSizes
37+
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
38+
*/
39+
public function testInvalideMaxSize($maxSize)
40+
{
41+
$file = new File(array('maxSize' => $maxSize));
42+
}
43+
44+
/**
45+
* @return array
46+
*/
47+
public function provideValidSizes()
48+
{
49+
return array(
50+
array('500', 500, false),
51+
array(12300, 12300, false),
52+
array('1ki', 1024, true),
53+
array('1KI', 1024, true),
54+
array('2k', 2000, false),
55+
array('2K', 2000, false),
56+
array('1mi', 1048576, true),
57+
array('1MI', 1048576, true),
58+
array('3m', 3000000, false),
59+
array('3M', 3000000, false),
60+
);
61+
}
62+
63+
/**
64+
* @return array
65+
*/
66+
public function provideInvalidSizes()
67+
{
68+
return array(
69+
array('+100'),
70+
array('foo'),
71+
array('1Ko'),
72+
array('1kio'),
73+
array('1G'),
74+
array('1Gi'),
75+
);
76+
}
77+
78+
/**
79+
* @param mixed $maxSize
80+
* @param bool $guessedFormat
81+
* @param bool $binaryFormat
82+
* @dataProvider provideFormats
83+
*/
84+
public function testBinaryFormat($maxSize, $guessedFormat, $binaryFormat)
85+
{
86+
$file = new File(array('maxSize' => $maxSize, 'binaryFormat' => $guessedFormat));
87+
88+
$this->assertSame($binaryFormat, $file->binaryFormat);
89+
}
90+
91+
/**
92+
* @return array
93+
*/
94+
public function provideFormats()
95+
{
96+
return array(
97+
array(100, null, false),
98+
array(100, true, true),
99+
array(100, false, false),
100+
array('100K', null, false),
101+
array('100K', true, true),
102+
array('100K', false, false),
103+
array('100Ki', null, true),
104+
array('100Ki', true, true),
105+
array('100Ki', false, false),
106+
);
107+
}
108+
}

Tests/Constraints/FileValidatorTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,27 +100,36 @@ public function provideMaxSizeExceededTests()
100100
// round(size) == 1.01kB, limit == 1kB
101101
array(ceil(1.005*1000), 1000, '1.01', '1', 'kB'),
102102
array(ceil(1.005*1000), '1k', '1.01', '1', 'kB'),
103+
array(ceil(1.005*1024), '1Ki', '1.01', '1', 'KiB'),
103104

104105
// round(size) == 1kB, limit == 1kB -> use bytes
105106
array(ceil(1.004*1000), 1000, '1004', '1000', 'bytes'),
106107
array(ceil(1.004*1000), '1k', '1004', '1000', 'bytes'),
108+
array(ceil(1.004*1024), '1Ki', '1029', '1024', 'bytes'),
107109

108110
array(1000 + 1, 1000, '1001', '1000', 'bytes'),
109111
array(1000 + 1, '1k', '1001', '1000', 'bytes'),
112+
array(1024 + 1, '1Ki', '1025', '1024', 'bytes'),
110113

111114
// round(size) == 1.01MB, limit == 1MB
112115
array(ceil(1.005*1000*1000), 1000*1000, '1.01', '1', 'MB'),
113116
array(ceil(1.005*1000*1000), '1000k', '1.01', '1', 'MB'),
114117
array(ceil(1.005*1000*1000), '1M', '1.01', '1', 'MB'),
118+
array(ceil(1.005*1024*1024), '1024Ki', '1.01', '1', 'MiB'),
119+
array(ceil(1.005*1024*1024), '1Mi', '1.01', '1', 'MiB'),
115120

116121
// round(size) == 1MB, limit == 1MB -> use kB
117122
array(ceil(1.004*1000*1000), 1000*1000, '1004', '1000', 'kB'),
118123
array(ceil(1.004*1000*1000), '1000k', '1004', '1000', 'kB'),
119124
array(ceil(1.004*1000*1000), '1M', '1004', '1000', 'kB'),
125+
array(ceil(1.004*1024*1024), '1024Ki', '1028.1', '1024', 'KiB'),
126+
array(ceil(1.004*1024*1024), '1Mi', '1028.1', '1024', 'KiB'),
120127

121128
array(1000*1000 + 1, 1000*1000, '1000001', '1000000', 'bytes'),
122129
array(1000*1000 + 1, '1000k', '1000001', '1000000', 'bytes'),
123130
array(1000*1000 + 1, '1M', '1000001', '1000000', 'bytes'),
131+
array(1024*1024 + 1, '1024Ki', '1048577', '1048576', 'bytes'),
132+
array(1024*1024 + 1, '1Mi', '1048577', '1048576', 'bytes'),
124133
);
125134
}
126135

@@ -158,9 +167,13 @@ public function provideMaxSizeNotExceededTests()
158167

159168
array(1000, '1k'),
160169
array(1000 - 1, '1k'),
170+
array(1024, '1Ki'),
171+
array(1024 - 1, '1Ki'),
161172

162173
array(1000*1000, '1M'),
163174
array(1000*1000 - 1, '1M'),
175+
array(1024*1024, '1Mi'),
176+
array(1024*1024 - 1, '1Mi'),
164177
);
165178
}
166179

@@ -196,6 +209,55 @@ public function testInvalidMaxSize()
196209
$this->validator->validate($this->path, $constraint);
197210
}
198211

212+
public function provideBinaryFormatTests()
213+
{
214+
return array(
215+
array(11, 10, null, '11', '10', 'bytes'),
216+
array(11, 10, true, '11', '10', 'bytes'),
217+
array(11, 10, false, '11', '10', 'bytes'),
218+
219+
// round(size) == 1.01kB, limit == 1kB
220+
array(ceil(1000*1.01), 1000, null, '1.01', '1', 'kB'),
221+
array(ceil(1000*1.01), '1k', null, '1.01', '1', 'kB'),
222+
array(ceil(1024*1.01), '1Ki', null, '1.01', '1', 'KiB'),
223+
224+
array(ceil(1024*1.01), 1024, true, '1.01', '1', 'KiB'),
225+
array(ceil(1024*1.01*1000), '1024k', true, '1010', '1000', 'KiB'),
226+
array(ceil(1024*1.01), '1Ki', true, '1.01', '1', 'KiB'),
227+
228+
array(ceil(1000*1.01), 1000, false, '1.01', '1', 'kB'),
229+
array(ceil(1000*1.01), '1k', false, '1.01', '1', 'kB'),
230+
array(ceil(1024*1.01*10), '10Ki', false, '10.34', '10.24', 'kB'),
231+
);
232+
}
233+
234+
/**
235+
* @dataProvider provideBinaryFormatTests
236+
*/
237+
public function testBinaryFormat($bytesWritten, $limit, $binaryFormat, $sizeAsString, $limitAsString, $suffix)
238+
{
239+
fseek($this->file, $bytesWritten-1, SEEK_SET);
240+
fwrite($this->file, '0');
241+
fclose($this->file);
242+
243+
$constraint = new File(array(
244+
'maxSize' => $limit,
245+
'binaryFormat' => $binaryFormat,
246+
'maxSizeMessage' => 'myMessage',
247+
));
248+
249+
$this->context->expects($this->once())
250+
->method('addViolation')
251+
->with('myMessage', array(
252+
'{{ limit }}' => $limitAsString,
253+
'{{ size }}' => $sizeAsString,
254+
'{{ suffix }}' => $suffix,
255+
'{{ file }}' => $this->path,
256+
));
257+
258+
$this->validator->validate($this->getFile($this->path), $constraint);
259+
}
260+
199261
public function testValidMimeType()
200262
{
201263
$file = $this

0 commit comments

Comments
 (0)