8000 feature #38410 [Validator] Migrate File and Image constraints to attr… · symfony/symfony@2f925df · GitHub
[go: up one dir, main page]

Skip to content

Commit 2f925df

Browse files
committed
feature #38410 [Validator] Migrate File and Image constraints to attributes (derrabus)
This PR was merged into the 5.2-dev branch. Discussion ---------- [Validator] Migrate File and Image constraints to attributes | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | #38096 | License | MIT | Doc PR | TODO with symfony/symfony-docs#14305 I have migrated a lot of the constraints already and am preparing a big PR with them at the moment. I decided to pull this part out because it might raise some discussion. This PR enables the `File` and `Image` constraints to be used as attributes. Especially the constructor signature of the `Image` constraint has grown pretty large this way. This by itself should be a big problem, if we don't expect the constructor to be called with ordered parameters by userland code. But it shows that the constraints have grown a bit too large. We might want to consider to split it. Commits ------- d8c1869 [Validator] Migrate File and Image constraints to attributes.
2 parents a324b22 + d8c1869 commit 2f925df

File tree

6 files changed

+539
-84
lines changed

6 files changed

+539
-84
lines changed

src/Symfony/Component/Validator/Constraints/File.php

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*
2323
* @author Bernhard Schussek <bschussek@gmail.com>
2424
*/
25+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
2526
class File extends Constraint
2627
{
2728
// Check the Image constraint for clashes if adding new constants here
@@ -61,10 +62,57 @@ class File extends Constraint
6162

6263
/**
6364
* {@inheritdoc}
65+
*
66+
* @param int|string|null $maxSize
67+
* @param string[]|string|null $mimeTypes
6468
*/
65-
public function __construct($options = null)
66-
{
67-
parent::__construct($options);
69+
public function __construct(
70+
array $options = null,
71+
$maxSize = null,
72+
bool $binaryFormat = null,
73+
$mimeTypes = null,
74+
string $notFoundMessage = null,
75+
string $notReadableMessage = null,
76+
string $maxSizeMessage = null,
77+
string $mimeTypesMessage = null,
78+
string $disallowEmptyMessage = null,
79+
80+
string $uploadIniSizeErrorMessage = null,
81+
string $uploadFormSizeErrorMessage = null,
82+
string $uploadPartialErrorMessage = null,
83+
string $uploadNoFileErrorMessage = null,
84+
string $uploadNoTmpDirErrorMessage = null,
85+
string $uploadCantWriteErrorMessage = null,
86+
string $uploadExtensionErrorMessage = null,
87+
string $uploadErrorMessage = null,
88+
array $groups = null,
89+
$payload = null
90+
) {
91+
if (null !== $maxSize && !\is_int($maxSize) && !\is_string($maxSize)) {
92+
throw new \TypeError(sprintf('"%s": Expected argument $maxSize to be either null, an integer or a string, got "%s".', __METHOD__, get_debug_type($maxSize)));
93+
}
94+
if (null !== $mimeTypes && !\is_array($mimeTypes) && !\is_string($mimeTypes)) {
95+
throw new \TypeError(sprintf('"%s": Expected argument $mimeTypes to be either null, an array or a string, got "%s".', __METHOD__, get_debug_type($mimeTypes)));
96+
}
97+
98+
parent::__construct($options, $groups, $payload);
99+
100+
$this->maxSize = $maxSize ?? $this->maxSize;
101+
$this->binaryFormat = $binaryFormat ?? $this->binaryFormat;
102+
$this->mimeTypes = $mimeTypes ?? $this->mimeTypes;
103+
$this->notFoundMessage = $notFoundMessage ?? $this->notFoundMessage;
104+
$this->notReadableMessage = $notReadableMessage ?? $this->notReadableMessage;
105+
$this->maxSizeMessage = $maxSizeMessage ?? $this->maxSizeMessage;
106+
$this->mimeTypesMessage = $mimeTypesMessage ?? $this->mimeTypesMessage;
107+
$this->disallowEmptyMessage = $disallowEmptyMessage ?? $this->disallowEmptyMessage;
108+
$this->uploadIniSizeErrorMessage = $uploadIniSizeErrorMessage ?? $this->uploadIniSizeErrorMessage;
109+
$this->uploadFormSizeErrorMessage = $uploadFormSizeErrorMessage ?? $this->uploadFormSizeErrorMessage;
110+
$this->uploadPartialErrorMessage = $uploadPartialErrorMessage ?? $this->uploadPartialErrorMessage;
111+
$this->uploadNoFileErrorMessage = $uploadNoFileErrorMessage ?? $this->uploadNoFileErrorMessage;
112+
$this->uploadNoTmpDirErrorMessage = $uploadNoTmpDirErrorMessage ?? $this->uploadNoTmpDirErrorMessage;
113+
$this->uploadCantWriteErrorMessage = $uploadCantWriteErrorMessage ?? $this->uploadCantWriteErrorMessage;
114+
$this->uploadExtensionErrorMessage = $uploadExtensionErrorMessage ?? $this->uploadExtensionErrorMessage;
115+
$this->uploadErrorMessage = $uploadErrorMessage ?? $this->uploadErrorMessage;
68116

69117
if (null !== $this->maxSize) {
70118
$this->normalizeBinaryFormat($this->maxSize);
@@ -100,6 +148,9 @@ public function __isset(string $option)
100148
return parent::__isset($option);
101149
}
102150

151+
/**
152+
* @param int|string $maxSize
153+
*/
103154
private function normalizeBinaryFormat($maxSize)
104155
{
105156
$factors = [

src/Symfony/Component/Validator/Constraints/Image.php

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* @author Benjamin Dulau <benjamin.dulau@gmail.com>
1919
* @author Bernhard Schussek <bschussek@gmail.com>
2020
*/
21+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
2122
class Image extends File
2223
{
2324
const SIZE_NOT_DETECTED_ERROR = '6d55c3f4-e58e-4fe3-91ee-74b492199956';
@@ -86,4 +87,107 @@ class Image extends File
8687
public $allowLandscapeMessage = 'The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.';
8788
public $allowPortraitMessage = 'The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.';
8889
public $corruptedMessage = 'The image file is corrupted.';
90+
91+
/**
92+
* {@inheritdoc}
93+
*
94+
* @param int|float $maxRatio
95+
* @param int|float $minRatio
96+
* @param int|float $minPixels
97+
* @param int|float $maxPixels
98+
*/
99+
public function __construct(
100+
array $options = null,
101+
$maxSize = null,
102+
bool $binaryFormat = null,
103+
array $mimeTypes = null,
104+
int $minWidth = null,
105+
int $maxWidth = null,
106+
int $maxHeight = null,
107+
int $minHeight = null,
108+
$maxRatio = null,
109+
$minRatio = null,
110+
$minPixels = null,
111+
$maxPixels = null,
112+
bool $allowSquare = null,
113+
bool $allowLandscape = null,
114+
bool $allowPortrait = null,
115+
bool $detectCorrupted = null,
116+
string $notFoundMessage = null,
117+
string $notReadableMessage = null,
118+
string $maxSizeMessage = null,
119+
string $mimeTypesMessage = null,
120+
string $disallowEmptyMessage = null,
121+
string $uploadIniSizeErrorMessage = null,
122+
string $uploadFormSizeErrorMessage = null,
123+
string $uploadPartialErrorMessage = null,
124+
string $uploadNoFileErrorMessage = null,
125+
string $uploadNoTmpDirErrorMessage = null,
126+
string $uploadCantWriteErrorMessage = null,
127+
string $uploadExtensionErrorMessage = null,
128+
string $uploadErrorMessage = null,
129+
string $sizeNotDetectedMessage = null,
130+
string $maxWidthMessage = null,
131+
string $minWidthMessage = null,
132+
string $maxHeightMessage = null,
133+
string $minHeightMessage = null,
134+
string $minPixelsMessage = null,
135+
string $maxPixelsMessage = null,
136+
string $maxRatioMessage = null,
137+
string $minRatioMessage = null,
138+
string $allowSquareMessage = null,
139+
string $allowLandscapeMessage = null,
140+
string $allowPortraitMessage = null,
141+
string $corruptedMessage = null,
142+
array $groups = null,
143+
$payload = null
144+
) {
145+
parent::__construct(
146+
$options,
147+
$maxSize,
148+
$binaryFormat,
149+
$mimeTypes,
150+
$notFoundMessage,
151+
$notReadableMessage,
152+
$maxSizeMessage,
153+
$mimeTypesMessage,
154+
$disallowEmptyMessage,
155+
$uploadIniSizeErrorMessage,
156+
$uploadFormSizeErrorMessage,
157+
$uploadPartialErrorMessage,
158+
$uploadNoFileErrorMessage,
159+
$uploadNoTmpDirErrorMessage,
160+
$uploadCantWriteErrorMessage,
161+
$uploadExtensionErrorMessage,
162+
$uploadErrorMessage,
163+
$groups,
164+
$payload
165+
);
166+
167+
$this->minWidth = $minWidth ?? $this->minWidth;
168+
$this->maxWidth = $maxWidth ?? $this->maxWidth;
169+
$this->maxHeight = $maxHeight ?? $this->maxHeight;
170+
$this->minHeight = $minHeight ?? $this->minHeight;
171+
$this->maxRatio = $maxRatio ?? $this->maxRatio;
172+
$this->minRatio = $minRatio ?? $this->minRatio;
173+
$this->minPixels = $minPixels ?? $this->minPixels;
174+
$this->maxPixels = $maxPixels ?? $this->maxPixels;
175+
$this->allowSquare = $allowSquare ?? $this->allowSquare;
176+
$this->allowLandscape = $allowLandscape ?? $this->allowLandscape;
177+
$this->allowPortrait = $allowPortrait ?? $this->allowPortrait;
178+
$this->detectCorrupted = $detectCorrupted ?? $this->detectCorrupted;
179+
$this->sizeNotDetectedMessage = $sizeNotDetectedMessage ?? $this->sizeNotDetectedMessage;
180+
$this->maxWidthMessage = $maxWidthMessage ?? $this->maxWidthMessage;
181+
$this->minWidthMessage = $minWidthMessage ?? $this->minWidthMessage;
182+
$this->maxHeightMessage = $maxHeightMessage ?? $this->maxHeightMessage;
183+
$this->minHeightMessage = $minHeightMessage ?? $this->minHeightMessage;
184+
$this->minPixelsMessage = $minPixelsMessage ?? $this->minPixelsMessage;
185+
$this->maxPixelsMessage = $maxPixelsMessage ?? $this->maxPixelsMessage;
186+
$this->maxRatioMessage = $maxRatioMessage ?? $this->maxRatioMessage;
187+
$this->minRatioMessage = $minRatioMessage ?? $this->minRatioMessage;
188+
$this->allowSquareMessage = $allowSquareMessage ?? $this->allowSquareMessage;
189+
$this->allowLandscapeMessage = $allowLandscapeMessage ?? $this->allowLandscapeMessage;
190+
$this->allowPortraitMessage = $allowPortraitMessage ?? $this->allowPortraitMessage;
191+
$this->corruptedMessage = $corruptedMessage ?? $this->corruptedMessage;
192+
}
89193
}

src/Symfony/Component/Validator/Tests/Constraints/FileTest.php

Lines changed: 36 additions & 0 deletions
+
self::assertSame('some attached data', $cConstraint->payload);
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Validator\Constraints\File;
1616
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
use Symfony\Component\Validator\Mapping\ClassMetadata;
18+
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
1719

1820
class FileTest extends TestCase
1921
{
@@ -138,4 +140,38 @@ public function provideFormats()
138140
['100Ki', false, false],
139141
];
140142
}
143+
144+
/**
145+
* @requires PHP 8
146+
*/
147+
public function testAttributes()
148+
{
149+
$metadata = new ClassMetadata(FileDummy::class);
150+
self::assertTrue((new AnnotationLoader())->loadClassMetadata($metadata));
151+
152+
list($aConstraint) = $metadata->properties['a']->getConstraints();
153+
self::assertNull($aConstraint->maxSize);
154+
155+
list($bConstraint) = $metadata->properties['b']->getConstraints();
156+
self::assertSame(100, $bConstraint->maxSize);
157+
self::assertSame('myMessage', $bConstraint->notFoundMessage);
158+
self::assertSame(['Default', 'FileDummy'], $bConstraint->groups);
159+
160+
list($cConstraint) = $metadata->properties['c']->getConstraints();
161+
self::assertSame(100000, $cConstraint->maxSize);
162+
self::assertSame(['my_group'], $cConstraint->groups);
163
164+
}
165+
}
166+
167+
class FileDummy
168+
{
169+
#[File]
170+
private $a;
171+
172+
#[File(maxSize: 100, notFoundMessage: 'myMessage')]
173+
private $b;
174+
175+
#[File(maxSize: '100K', groups: ['my_group'], payload: 'some attached data')]
176+
private $c;
141177
}

src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,29 @@ public function testBinaryFormat($bytesWritten, $limit, $binaryFormat, $sizeAsSt
281281
->assertRaised();
282282
}
283283

284+
/**
285+
* @requires PHP 8
286+
*/
287+
public function testBinaryFormatNamed()
288+
{
289+
fseek($this->file, 10, \SEEK_SET);
290+
fwrite($this->file, '0');
291+
fclose($this->file);
292+
293+
$constraint = eval('return new \Symfony\Component\Validator\Constraints\File(maxSize: 10, binaryFormat: true, maxSizeMessage: "myMessage");');
294+
295+
$this->validator->validate($this->getFile($this->path), $constraint);
296+
297+
$this->buildViolation('myMessage')
298+
->setParameter('{{ limit }}', '10')
299+
->setParameter('{{ size }}', '11')
300+
->setParameter('{{ suffix }}', 'bytes')
301+
->setParameter('{{ file }}', '"'.$this->path.'"')
302+
->setParameter('{{ name }}', '"'.basename($this->path).'"')
303+
->setCode(File::TOO_LARGE_ERROR)
304+
->assertRaised();
305+
}
306+
284307
public function testValidMimeType()
285308
{
286309
$file = $this
@@ -329,7 +352,10 @@ public function testValidWildcardMimeType()
329352
$this->assertNoViolation();
330353
}
331354

332-
public function testInvalidMimeType()
355+
/**
356+
* @dataProvider provideMimeTypeConstraints
357+
*/
358+
public function testInvalidMimeType(File $constraint)
333359
{
334360
$file = $this
335361
->getMockBuilder('Symfony\Component\HttpFoundation\File\File')
@@ -344,11 +370,6 @@ public function testInvalidMimeType()
344370
->method('getMimeType')
345371
->willReturn('application/pdf');
346372

347-
$constraint = new File([
348-
'mimeTypes' => ['image/png', 'image/jpg'],
349-
'mimeTypesMessage' => 'myMessage',
350-
]);
351-
352373
$this->validator->validate($file, $constraint);
353374

354375
$this->buildViolation('myMessage')
@@ -360,6 +381,20 @@ public function testInvalidMimeType()
360381
->assertRaised();
361382
}
362383

384+
public function provideMimeTypeConstraints(): iterable
385+
{
386+
yield 'Doctrine style' => [new File([
387+
'mimeTypes' => ['image/png', 'image/jpg'],
388+
'mimeTypesMessage' => 'myMessage',
389+
])];
390+
391+
if (\PHP_VERSION_ID >= 80000) {
392+
yield 'named arguments' => [
393+
eval('return new \Symfony\Component\Validator\Constraints\File(mimeTypes: ["image/png", "image/jpg"], mimeTypesMessage: "myMessage");'),
394+
];
395+
}
396+
}
397+
363398
public function testInvalidWildcardMimeType()
364399
{
365400
$file = $this
@@ -391,14 +426,13 @@ public function testInvalidWildcardMimeType()
391426
->assertRaised();
392427
}
393428

394-
public function testDisallowEmpty()
429+
/**
430+
* @dataProvider provideDisallowEmptyConstraints
431+
*/
432+
public function testDisallowEmpty(File $constraint)
395433
{
396434
ftruncate($this->file, 0);
397435

398-
$constraint = new File([
399-
'disallowEmptyMessage' => 'myMessage',
400-
]);
401-
402436
$this->validator->validate($this->getFile($this->path), $constraint);
403437

404438
$this->buildViolation('myMessage')
@@ -408,6 +442,19 @@ public function testDisallowEmpty()
408442
->assertRaised();
409443
}
410444

445+
public function provideDisallowEmptyConstraints(): iterable
446+
{
447+
yield 'Doctrine style' => [new File([
448+
'disallowEmptyMessage' => 'myMessage',
449+
])];
450+
451+
if (\PHP_VERSION_ID >= 80000) {
452+
yield 'named arguments' => [
453+
eval('return new \Symfony\Component\Validator\Constraints\File(disallowEmptyMessage: "myMessage");'),
454+
];
455+
}
456+
}
457+
411458
/**
412459
* @dataProvider uploadedFileErrorProvider
413460
*/

0 commit comments

Comments
 (0)
0