Skip to content

Commit fbde828

Browse files
committed
Adding a new Attribute MapRequestHeader class and resolver
1 parent 8d5be07 commit fbde828

File tree

5 files changed

+279
-0
lines changed

5 files changed

+279
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
2121
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\QueryParameterValueResolver;
2222
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
23+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestHeaderValueResolver;
2324
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
2425
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver;
2526
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver;
@@ -100,6 +101,9 @@
100101
->set('argument_resolver.query_parameter_value_resolver', QueryParameterValueResolver::class)
101102
->tag('controller.targeted_value_resolver', ['name' => QueryParameterValueResolver::class])
102103

104+
->set('argument_resolver.header_value_resolver', RequestHeaderValueResolver::class)
105+
->tag('controller.targeted_value_resolver', ['name' => RequestHeaderValueResolver::class])
106+
103107
->set('response_listener', ResponseListener::class)
104108
->args([
105109
param('kernel.charset'),
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\HttpFoundation\Response;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestHeaderValueResolver;
16+
17+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
18+
class MapRequestHeader extends ValueResolver
19+
{
20+
public function __construct(
21+
public readonly ?string $name = null,
22+
public readonly int $validationFailedStatusCode = Response::HTTP_BAD_REQUEST,
23+
string $resolver = RequestHeaderValueResolver::class,
24+
) {
25+
parent::__construct($resolver);
26+
}
27+
}

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ CHANGELOG
1919
* Add argument `$buildDir` to `WarmableInterface`
2020
* Add argument `$filter` to `Profiler::find()` and `FileProfilerStorage::find()`
2121
* Add `ControllerResolver::allowControllers()` to define which callables are legit controllers when the `_check_controller_is_allowed` request attribute is set
22+
* Add `#[MapRequestHeader]` to map header from `Request::$headers`
2223

2324
6.3
2425
---
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Controller\ArgumentResolver;
13+
14+
use Symfony\Component\HttpFoundation\AcceptHeader;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Attribute\MapRequestHeader;
17+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
18+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
19+
use Symfony\Component\HttpKernel\Exception\HttpException;
20+
21+
class RequestHeaderValueResolver implements ValueResolverInterface
22+
{
23+
public function resolve(Request $request, ArgumentMetadata $argument): iterable
24+
{
25+
if (!$attribute = $argument->getAttributesOfType(MapRequestHeader::class)[0] ?? null) {
26+
return [];
27+
}
28+
29+
$type = $argument->getType();
30+
31+
if (!\in_array($type, ['string', 'array', AcceptHeader::class])) {
32+
throw new \LogicException(\sprintf('Could not resolve the argument typed "%s". Valid values types are "array", "string" or "%s".', $type, AcceptHeader::class));
33+
}
34+
35+
$name = $attribute->name ?? $argument->getName();
36+
37+
$value = match ($type) {
38+
'string' => $request->headers->get($name),
39+
'array' => match (strtolower($name)) {
40+
'accept' => $request->getAcceptableContentTypes(),
41+
'accept-charset' => $request->getCharsets(),
42+
'accept-language' => $request->getLanguages(),
43+
'accept-encoding' => $request->getEncodings(),
44+
default => $request->headers->has($name) ? [$request->headers->get($name)] : null,
45+
},
46+
default => AcceptHeader::fromString($request->headers->get($name)),
47+
};
48+
49+
if (null === $value && $argument->hasDefaultValue()) {
50+
$value = $argument->getDefaultValue();
51+
}
52+
53+
if (null === $value && !$argument->isNullable()) {
54+
throw new HttpException($attribute->validationFailedStatusCode, \sprintf('Missing header "%s".', $name));
55+
}
56+
57+
return [$value];
58+
}
59+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Tests\Controller\ArgumentResolver;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\AcceptHeader;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpKernel\Attribute\MapRequestHeader;
18+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestHeaderValueResolver;
19+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
20+
use Symfony\Component\HttpKernel\Exception\HttpException;
21+
22+
class RequestHeaderValueResolverTest extends TestCase
23+
{
24+
public static function provideHeaderValuesWithStringType(): iterable
25+
{
26+
yield 'with accept' => ['accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'];
27+
yield 'with accept-language' => ['accept-language', 'en-us,en;q=0.5'];
28+
yield 'with host' => ['host', 'localhost'];
29+
yield 'with user-agent' => ['user-agent', 'Symfony'];
30+
}
31+
32+
public static function provideHeaderValuesWithArrayType(): iterable
33+
{
34+
yield 'with accept' => [
35+
'accept',
36+
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
37+
[
38+
[
39+
'text/html',
40+
'application/xhtml+xml',
41+
'application/xml',
42+
'*/*',
43+
],
44+
],
45+
];
46+
yield 'with accept-language' => [
47+
'accept-language',
48+
'en-us,en;q=0.5',
49+
[
50+
[
51+
'en_US',
52+
'en',
53+
],
54+
],
55+
];
56+
yield 'with host' => [
57+
'host',
58+
'localhost',
59+
[
60+
['localhost'],
61+
],
62+
];
63+
yield 'with user-agent' => [
64+
'user-agent',
65+
'Symfony',
66+
[
67+
['Symfony'],
68+
],
69+
];
70+
}
71+
72+
public static function provideHeaderValuesWithAcceptHeaderType(): iterable
73+
{
74+
yield 'with accept' => [
75+
'accept',
76+
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
77+
[AcceptHeader::fromString('text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')],
78+
];
79+
yield 'with accept-language' => [
80+
'accept-language',
81+
'en-us,en;q=0.5',
82+
[AcceptHeader::fromString('en-us,en;q=0.5')],
83+
];
84+
yield 'with host' => [
85+
'host',
86+
'localhost',
87+
[AcceptHeader::fromString('localhost')],
88+
];
89+
yield 'with user-agent' => [
90+
'user-agent',
91+
'Symfony',
92+
[AcceptHeader::fromString('Symfony')],
93+
];
94+
}
95+
96+
public static function provideNoValueNullable(): iterable
97+
{
98+
yield 'with array type' => ['array', [null]];
99+
yield 'with string type' => ['string', [null]];
100+
}
101+
102+
/**
103+
* @dataProvider provideHeaderValuesWithStringType
104+
*/
105+
public function testWithStringType(string $parameter, string $value)
106+
{
107+
$resolver = new RequestHeaderValueResolver();
108+
109+
$metadata = new ArgumentMetadata('variableName', 'string', false, false, null, false, [
110+
MapRequestHeader::class => new MapRequestHeader($parameter),
111+
]);
112+
113+
$request = Request::create('/');
114+
$request->headers->set($parameter, $value);
115+
116+
$arguments = $resolver->resolve($request, $metadata);
117+
118+
self::assertEquals([$value], $arguments);
119+
}
120+
121+
/**
122+
* @dataProvider provideHeaderValuesWithArrayType
123+
*/
124+
public function testWithArrayType(string $parameter, string $value, array $expected)
125+
{
126+
$resolver = new RequestHeaderValueResolver();
127+
128+
$metadata = new ArgumentMetadata('variableName', 'array', false, false, null, false, [
129+
MapRequestHeader::class => new MapRequestHeader($parameter),
130+
]);
131+
132+
$request = Request::create('/');
133+
$request->headers->set($parameter, $value);
134+
135+
$arguments = $resolver->resolve($request, $metadata);
136+
137+
self::assertEquals($expected, $arguments);
138+
}
139+
140+
/**
141+
* @dataProvider provideHeaderValuesWithAcceptHeaderType
142+
*/
143+
public function testWithAcceptHeaderType(string $parameter, string $value, array $expected)
144+
{
145+
$resolver = new RequestHeaderValueResolver();
146+
147+
$metadata = new ArgumentMetadata('variableName', AcceptHeader::class, false, false, null, false, [
148+
MapRequestHeader::class => new MapRequestHeader($parameter),
149+
]);
150+
151+
$request = Request::create('/');
152+
$request->headers->set($parameter, $value);
153+
154+
$arguments = $resolver->resolve($request, $metadata);
155+
156+
self::assertEquals($expected, $arguments);
157+
}
158+
159+
/**
160+
* @dataProvider provideNoValueNullable
161+
*/
162+
public function testWithNoValueNullable(string $type, array $expected)
163+
{
164+
$resolver = new RequestHeaderValueResolver();
165+
166+
$metadata = new ArgumentMetadata('variableName', $type, false, false, null, true, [
167+
MapRequestHeader::class => new MapRequestHeader(),
168+
]);
169+
170+
$arguments = $resolver->resolve(Request::create('/'), $metadata);
171+
172+
self::assertEquals($expected, $arguments);
173+
}
174+
175+
public function testWithNoStringValueNotNullable()
176+
{
177+
$resolver = new RequestHeaderValueResolver();
178+
179+
self::expectException(HttpException::class);
180+
self::expectExceptionMessage('Missing header "variableName".');
181+
182+
$metadata = new ArgumentMetadata('variableName', 'string', false, false, null, false, [
183+
MapRequestHeader::class => new MapRequestHeader(),
184+
]);
185+
186+
$resolver->resolve(Request::create('/'), $metadata);
187+
}
188+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy