Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0a29c3a

Browse files
Renerenedelima
authored andcommitted
[HttpKernel] Add MapUploadedFile attribute
1 parent 887c960 commit 0a29c3a

File tree

7 files changed

+277
-10
lines changed

7 files changed

+277
-10
lines changed

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ApiAttributesTest.php

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
1313

14+
use Symfony\Component\HttpFoundation\File\UploadedFile;
1415
use Symfony\Component\HttpFoundation\JsonResponse;
1516
use Symfony\Component\HttpFoundation\Request;
1617
use Symfony\Component\HttpFoundation\Response;
1718
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
1819
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
20+
use Symfony\Component\HttpKernel\Attribute\MapUploadedFile;
1921
use Symfony\Component\Validator\Constraints as Assert;
2022

2123
class ApiAttributesTest extends AbstractWebTestCase
@@ -349,6 +351,171 @@ public static function mapRequestPayloadProvider(): iterable
349351
'expectedStatusCode' => 422,
350352
];
351353
}
354+
355+
public function testMapUploadedFileDefaults()
356+
{
357+
$client = self::createClient(['test_case' => 'ApiAttributesTest']);
358+
359+
$client->request(
360+
'POST',
361+
'/map-uploaded-file-defaults',
362+
[],
363+
['file' => new UploadedFile(__DIR__.'/Fixtures/file-small.txt', 'file-small.txt', 'text/plain')],
364+
['HTTP_CONTENT_TYPE' => 'multipart/form-data'],
365+
);
366+
$response = $client->getResponse();
367+
368+
self::assertResponseIsSuccessful();
369+
self::assertStringEqualsFile(__DIR__.'/Fixtures/file-small.txt', $response->getContent());
370+
}
371+
372+
public function testMapUploadedFileCustomName()
373+
{
374+
$client = self::createClient(['test_case' => 'ApiAttributesTest']);
375+
376+
$client->request(
377+
'POST',
378+
'/map-uploaded-file-custom-name',
379+
[],
380+
[
381+
'foo' => new UploadedFile(__DIR__.'/Fixtures/file-small.txt', 'file-small.txt', 'text/plain'),
382+
'bar' => new UploadedFile(__DIR__.'/Fixtures/file-big.txt', 'file-big.txt', 'text/plain'),
383+
],
384+
['HTTP_CONTENT_TYPE' => 'multipart/form-data'],
385+
);
386+
$response = $client->getResponse();
387+
388+
self::assertResponseIsSuccessful();
389+
self::assertStringEqualsFile(__DIR__.'/Fixtures/file-small.txt', $response->getContent());
390+
}
391+
392+
public function testMapUploadedFileNullable()
393+
{
394+
$client = self::createClient(['test_case' => 'ApiAttributesTest']);
395+
$client->request(
396+
'POST',
397+
'/map-uploaded-file-nullable',
398+
[],
399+
[],
400+
['HTTP_CONTENT_TYPE' => 'multipart/form-data'],
401+
);
402+
$response = $client->getResponse();
403+
404+
self::assertResponseIsSuccessful();
405+
self::assertEmpty($response->getContent());
406+
}
407+
408+
public function testMapUploadedFileWithConstraints()
409+
{
410+
$client = self::createClient(['test_case' => 'ApiAttributesTest']);
411+
412+
$client->request(
413+
'POST',
414+
'/map-uploaded-file-with-constraints',
415+
[],
416+
['file' => new UploadedFile(__DIR__.'/Fixtures/file-small.txt', 'file-small.txt', 'text/plain')],
417+
['HTTP_CONTENT_TYPE' => 'multipart/form-data'],
418+
);
419+
$response = $client->getResponse();
420+
421+
self::assertResponseIsSuccessful();
422+
self::assertStringEqualsFile(__DIR__.'/Fixtures/file-small.txt', $response->getContent());
423+
424+
$filePath = __DIR__.'/Fixtures/file-big.txt';
425+
$client->request(
426+
'POST',
427+
'/map-uploaded-file-with-constraints',
428+
[],
429+
['file' => new UploadedFile($filePath, 'file-big.txt', 'text/plain')],
430+
[
431+
'HTTP_ACCEPT' => 'application/json',
432+
'HTTP_CONTENT_TYPE' => 'multipart/form-data',
433+
],
434+
);
435+
$response = $client->getResponse();
436+
437+
$content = <<<JSON
438+
{
439+
"type": "https://symfony.com/errors/validation",
440+
"title": "Validation Failed",
441+
"status": 422,
442+
"detail": "The file is too large (71 bytes). Allowed maximum size is 50 bytes.",
443+
"violations": [
444+
{
445+
"propertyPath": "",
446+
"title": "The file is too large (71 bytes). Allowed maximum size is 50 bytes.",
447+
"template": "The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.",
448+
"parameters": {
449+
"{{ file }}": "\"$filePath\"",
450+
"{{ size }}": "71",
451+
"{{ limit }}": "50",
452+
"{{ suffix }}": "bytes",
453+
"{{ name }}": "\"file-big.txt\""
454+
},
455+
"type": "urn:uuid:df8637af-d466-48c6-a59d-e7126250a654"
456+
}
457+
]
458+
}
459+
JSON;
460+
461+
self::assertResponseIsUnprocessable();
462+
self::assertJsonStringEqualsJsonString($content, $response->getContent());
463+
}
464+
465+
public function testMapUploadedFileWithMultipleFilesArray()
466+
{
467+
$client = self::createClient(['test_case' => 'ApiAttributesTest']);
468+
469+
$client->request(
470+
'POST',
471+
'/map-uploaded-file-with-multiple-array',
472+
[],
473+
[
474+
'files' => [
475+
new UploadedFile(__DIR__.'/Fixtures/file-small.txt', 'file-small.txt', 'text/plain'),
476+
new UploadedFile(__DIR__.'/Fixtures/file-big.txt', 'file-big.txt', 'text/plain'),
477+
],
478+
],
479+
['HTTP_CONTENT_TYPE' => 'multipart/form-data'],
480+
);
481+
$response = $client->getResponse();
482+
483+
self::assertResponseIsSuccessful();
484+
self::assertJsonStringEqualsJsonString(
485+
json_encode([2, 'file-small.txt', 'file-big.txt'], \JSON_THROW_ON_ERROR),
486+
$response->getContent()
487+
);
488+
}
489+
490+
public function testMapUploadedFileWithMultipleFilesVariadic()
491+
{
492+
$client = self::createClient(['test_case' => 'ApiAttributesTest']);
493+
494+
$client->request(
495+
'POST',
496+
'/map-uploaded-file-with-multiple-variadic',
497+
[],
498+
[
499+
'foo' => [
500+
new UploadedFile(__DIR__.'/Fixtures/file-small.txt', 'first.txt', 'text/plain'),
501+
new UploadedFile(__DIR__.'/Fixtures/file-small.txt', 'second.txt', 'text/plain'),
502+
new UploadedFile(__DIR__.'/Fixtures/file-small.txt', 'third.txt', 'text/plain'),
503+
],
504+
'bar' => [
505+
new UploadedFile(__DIR__.'/Fixtures/file-big.txt', 'big.txt', 'text/plain'),
506+
new UploadedFile(__DIR__.'/Fixtures/file-big.txt', 'huge.txt', 'text/plain'),
507+
],
508+
],
509+
['HTTP_CONTENT_TYPE' => 'multipart/form-data'],
510+
);
511+
$response = $client->getResponse();
512+
513+
self::assertResponseIsSuccessful();
514+
self::assertJsonStringEqualsJsonString(
515+
json_encode([3, 'first.txt', 'second.txt', 'third.txt'], \JSON_THROW_ON_ERROR),
516+
$response->getContent()
517+
);
518+
}
352519
}
353520

354521
class WithMapQueryStringController
@@ -388,6 +555,39 @@ public function __invoke(#[MapRequestPayload] ?RequestBody $body, Request $reque
388555
}
389556
}
390557

558+
class WithMapUploadedFileController
559+
{
560+
public function defaults(#[MapUploadedFile] UploadedFile $file): Response
561+
{
562+
return new Response($file->getContent());
563+
}
564+
565+
public function customName(#[MapUploadedFile(name: 'foo')] UploadedFile $bar): Response
566+
{
567+
return new Response($bar->getContent());
568+
}
569+
570+
public function nullable(#[MapUploadedFile] ?UploadedFile $file): Response
571+
{
572+
return new Response($file?->getContent());
573+
}
574+
575+
public function withConstraints(#[MapUploadedFile(constraints: new Assert\File(maxSize: 50))] ?UploadedFile $file): Response
576+
{
577+
return new Response($file->getContent());
578+
}
579+
580+
public function withMultipleFilesArray(#[MapUploadedFile(constraints: new Assert\All([new Assert\File(maxSize: 100)]))] ?array $files): JsonResponse
581+
{
582+
return new JsonResponse([\count($files), ...array_map(static fn ($current) => $current->getClientOriginalName(), $files)]);
583+
}
584+
585+
public function withMultipleFilesVariadic(#[MapUploadedFile(constraints: new Assert\All([new Assert\File(maxSize: 100)]))] UploadedFile ...$foo): JsonResponse
586+
{
587+
return new JsonResponse([\count($foo), ...array_map(static fn ($current) => $current->getClientOriginalName(), $foo)]);
588+
}
589+
}
590+
391591
class QueryString
392592
{
393593
public function __construct(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I'm not big, but I'm big enough to carry more than 50 bytes inside me.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I'm a file with less than 50 bytes.

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/ApiAttributesTest/routing.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,27 @@ map_query_string:
55
map_request_body:
66
path: /map-request-body.{_format}
77
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapRequestPayloadController
8+
9+
map_uploaded_file_defaults:
10+
path: /map-uploaded-file-defaults
11+
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapUploadedFileController::defaults
12+
13+
map_uploaded_file_custom_name:
14+
path: /map-uploaded-file-custom-name
15+
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapUploadedFileController::customName
16+
17+
map_uploaded_file_nullable:
18+
path: /map-uploaded-file-nullable
19+
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapUploadedFileController::nullable
20+
21+
map_uploaded_file_constraints:
22+
path: /map-uploaded-file-with-constraints
23+
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapUploadedFileController::withConstraints
24+
25+
map_uploaded_file_multiple_array:
26+
path: /map-uploaded-file-with-multiple-array
27+
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapUploadedFileController::withMultipleFilesArray
28+
29+
map_uploaded_file_multiple_variadic:
30+
path: /map-uploaded-file-with-multiple-variadic
31+
controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\WithMapUploadedFileController::withMultipleFilesVariadic
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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\HttpKernel\Attribute;
13+
14+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
15+
use Symfony\Component\Validator\Constraint;
16+
17+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
18+
class MapUploadedFile extends ValueResolver
19+
{
20+
public function __construct(
21+
public string|null $name = null,
22+
/** @var Constraint|array<Constraint>|null */
23+
public Constraint|array|null $constraints = null,
24+
string $resolver = RequestPayloadValueResolver::class,
25+
) {
26+
parent::__construct($resolver);
27+
}
28+
}

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ CHANGELOG
1515
* Add `#[MapQueryString]` to map and validate request query string from `Request::$query->all()` to typed objects
1616
* Add `#[MapQueryParameter]` to map and validate individual query parameters to controller arguments
1717
* Collect data from every event dispatcher
18+
* Add `#[MapUploadedFile]` attribute to fetch, validate, and inject uploaded files into controller arguments
1819

1920
6.2
2021
---

0 commit comments

Comments
 (0)