Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 172f0a7

Browse filesBrowse files
GromNaNnicolas-grekas
authored andcommitted
[HttpFoundation] Add ParameterBag::getString() and deprecate accepting invalid values
1 parent 6a7cd16 commit 172f0a7
Copy full SHA for 172f0a7

File tree

6 files changed

+311
-26
lines changed
Filter options

6 files changed

+311
-26
lines changed

‎UPGRADE-6.3.md

Copy file name to clipboardExpand all lines: UPGRADE-6.3.md
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ HttpFoundation
6363

6464
* `Response::sendHeaders()` now takes an optional `$statusCode` parameter
6565

66+
HttpFoundation
67+
--------------
68+
69+
* Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()`
70+
* Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set
71+
6672
HttpKernel
6773
----------
6874

‎src/Symfony/Component/HttpFoundation/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ CHANGELOG
44
6.3
55
---
66

7+
* Calling `ParameterBag::getDigit()`, `getAlnum()`, `getAlpha()` on an `array` throws a `UnexpectedValueException` instead of a `TypeError`
8+
* Add `ParameterBag::getString()` to convert a parameter into string and throw an exception if the value is invalid
79
* Add `ParameterBag::getEnum()`
810
* Create migration for session table when pdo handler is used
911
* Add support for Relay PHP extension for Redis
1012
* The `Response::sendHeaders()` method now takes an optional HTTP status code as parameter, allowing to send informational responses such as Early Hints responses (103 status code)
13+
* Deprecate conversion of invalid values in `ParameterBag::getInt()` and `ParameterBag::getBoolean()`,
14+
* Deprecate ignoring invalid values when using `ParameterBag::filter()`, unless flag `FILTER_NULL_ON_FAILURE` is set
1115

1216
6.2
1317
---

‎src/Symfony/Component/HttpFoundation/InputBag.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/InputBag.php
+26-1Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ public function getEnum(string $key, string $class, \BackedEnum $default = null)
9292
}
9393
}
9494

95+
/**
96+
* Returns the parameter value converted to string.
97+
*/
98+
public function getString(string $key, string $default = ''): string
99+
{
100+
// Shortcuts the parent method because the validation on scalar is already done in get().
101+
return (string) $this->get($key, $default);
102+
}
103+
95104
public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
96105
{
97106
$value = $this->has($key) ? $this->all()[$key] : $default;
@@ -109,6 +118,22 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
109118
throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
110119
}
111120

112-
return filter_var($value, $filter, $options);
121+
$options['flags'] ??= 0;
122+
$nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE;
123+
$options['flags'] |= \FILTER_NULL_ON_FAILURE;
124+
125+
$value = filter_var($value, $filter, $options);
126+
127+
if (null !== $value || $nullOnFailure) {
128+
return $value;
129+
}
130+
131+
$method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1];
132+
$method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter';
133+
$hint = 'filter' === $method ? 'pass' : 'use method "filter()" with';
134+
135+
trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw a "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, BadRequestException::class);
136+
137+
return false;
113138
}
114139
}

‎src/Symfony/Component/HttpFoundation/ParameterBag.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/ParameterBag.php
+42-8Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,40 +114,53 @@ public function remove(string $key)
114114
*/
115115
public function getAlpha(string $key, string $default = ''): string
116116
{
117-
return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default));
117+
return preg_replace('/[^[:alpha:]]/', '', $this->getString($key, $default));
118118
}
119119

120120
/**
121121
* Returns the alphabetic characters and digits of the parameter value.
122122
*/
123123
public function getAlnum(string $key, string $default = ''): string
124124
{
125-
return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default));
125+
return preg_replace('/[^[:alnum:]]/', '', $this->getString($key, $default));
126126
}
127127

128128
/**
129129
* Returns the digits of the parameter value.
130130
*/
131131
public function getDigits(string $key, string $default = ''): string
132132
{
133-
// we need to remove - and + because they're allowed in the filter
134-
return str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT));
133+
return preg_replace('/[^[:digit:]]/', '', $this->getString($key, $default));
134+
}
135+
136+
/**
137+
* Returns the parameter as string.
138+
*/
139+
public function getString(string $key, string $default = ''): string
140+
{
141+
$value = $this->get($key, $default);
142+
if (!\is_scalar($value) && !$value instanceof \Stringable) {
143+
throw new \UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key));
144+
}
145+
146+
return (string) $value;
135147
}
136148

137149
/**
138150
* Returns the parameter value converted to integer.
139151
*/
140152
public function getInt(string $key, int $default = 0): int
141153
{
142-
return (int) $this->get($key, $default);
154+
// In 7.0 remove the fallback to 0, in case of failure an exception will be thrown
155+
return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR]) ?: 0;
143156
}
144157

145158
/**
146159
* Returns the parameter value converted to boolean.
147160
*/
148161
public function getBoolean(string $key, bool $default = false): bool
149162
{
150-
return $this->filter($key, $default, \FILTER_VALIDATE_BOOL);
163+
return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR]);
151164
}
152165

153166
/**
@@ -178,7 +191,8 @@ public function getEnum(string $key, string $class, \BackedEnum $default = null)
178191
/**
179192
* Filter key.
180193
*
181-
* @param int $filter FILTER_* constant
194+
* @param int $filter FILTER_* constant
195+
* @param int|array{flags?: int, options?: array} $options Flags from FILTER_* constants
182196
*
183197
* @see https://php.net/filter-var
184198
*/
@@ -196,11 +210,31 @@ public function filter(string $key, mixed $default = null, int $filter = \FILTER
196210
$options['flags'] = \FILTER_REQUIRE_ARRAY;
197211
}
198212

213+
if (\is_object($value) && !$value instanceof \Stringable) {
214+
throw new \UnexpectedValueException(sprintf('Parameter value "%s" cannot be filtered.', $key));
215+
}
216+
199217
if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
200218
throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
201219
}
202220

203-
return filter_var($value, $filter, $options);
221+
$options['flags'] ??= 0;
222+
$nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE;
223+
$options['flags'] |= \FILTER_NULL_ON_FAILURE;
224+
225+
$value = filter_var($value, $filter, $options);
226+
227+
if (null !== $value || $nullOnFailure) {
228+
return $value;
229+
}
230+
231+
$method = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)[1];
232+
$method = ($method['object'] ?? null) === $this ? $method['function'] : 'filter';
233+
$hint = 'filter' === $method ? 'pass' : 'use method "filter()" with';
234+
235+
trigger_deprecation('symfony/http-foundation', '6.3', 'Ignoring invalid values when using "%s::%s(\'%s\')" is deprecated and will throw an "%s" in 7.0; '.$hint.' flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.', $this::class, $method, $key, \UnexpectedValueException::class);
236+
237+
return false;
204238
}
205239

206240
/**

‎src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php
+85Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
namespace Symfony\Component\HttpFoundation\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
1516
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
1617
use Symfony\Component\HttpFoundation\InputBag;
1718
use Symfony\Component\HttpFoundation\Tests\Fixtures\FooEnum;
1819

1920
class InputBagTest extends TestCase
2021
{
22+
use ExpectDeprecationTrait;
23+
2124
public function testGet()
2225
{
2326
$bag = new InputBag(['foo' => 'bar', 'null' => null, 'int' => 1, 'float' => 1.0, 'bool' => false, 'stringable' => new class() implements \Stringable {
@@ -36,6 +39,58 @@ public function __toString(): string
3639
$this->assertFalse($bag->get('bool'), '->get() gets the value of a bool parameter');
3740
}
3841

42+
/**
43+
* @group legacy
44+
*/
45+
public function testGetIntError()
46+
{
47+
$this->expectDeprecation('Since symfony/http-foundation 6.3: Ignoring invalid values when using "Symfony\Component\HttpFoundation\InputBag::getInt(\'foo\')" is deprecated and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.');
48+
49+
$bag = new InputBag(['foo' => 'bar']);
50+
$result = $bag->getInt('foo');
51+
$this->assertSame(0, $result);
52+
}
53+
54+
/**
55+
* @group legacy
56+
*/
57+
public function testGetBooleanError()
58+
{
59+
$this->expectDeprecation('Since symfony/http-foundation 6.3: Ignoring invalid values when using "Symfony\Component\HttpFoundation\InputBag::getBoolean(\'foo\')" is deprecated and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" in 7.0; use method "filter()" with flag "FILTER_NULL_ON_FAILURE" to keep ignoring them.');
60+
61+
$bag = new InputBag(['foo' => 'bar']);
62+
$result = $bag->getBoolean('foo');
63+
$this->assertFalse($result);
64+
}
65+
66+
public function testGetString()
67+
{
68+
$bag = new InputBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
69+
public function __toString(): string
70+
{
71+
return 'strval';
72+
}
73+
}]);
74+
75+
$this->assertSame('123', $bag->getString('integer'), '->getString() gets a value of parameter as string');
76+
$this->assertSame('abc', $bag->getString('string'), '->getString() gets a value of parameter as string');
77+
$this->assertSame('', $bag->getString('unknown'), '->getString() returns zero if a parameter is not defined');
78+
$this->assertSame('foo', $bag->getString('unknown', 'foo'), '->getString() returns the default if a parameter is not defined');
79+
$this->assertSame('1', $bag->getString('bool_true'), '->getString() returns "1" if a parameter is true');
80+
$this->assertSame('', $bag->getString('bool_false', 'foo'), '->getString() returns an empty empty string if a parameter is false');
81+
$this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable paramater as string');
82+
}
83+
84+
public function testGetStringExceptionWithArray()
85+
{
86+
$bag = new InputBag(['key' => ['abc']]);
87+
88+
$this->expectException(BadRequestException::class);
89+
$this->expectExceptionMessage('Input value "key" contains a non-scalar value.');
90+
91+
$bag->getString('key');
92+
}
93+
3994
public function testGetDoesNotUseDeepByDefault()
4095
{
4196
$bag = new InputBag(['foo' => ['bar' => 'moo']]);
@@ -126,4 +181,34 @@ public function testGetEnumThrowsExceptionWithInvalidValue()
126181

127182
$this->assertNull($bag->getEnum('invalid-value', FooEnum::class));
128183
}
184+
185+
public function testGetAlnumExceptionWithArray()
186+
{
187+
$bag = new InputBag(['word' => ['foo_BAR_012']]);
188+
189+
$this->expectException(BadRequestException::class);
190+
$this->expectExceptionMessage('Input value "word" contains a non-scalar value.');
191+
192+
$bag->getAlnum('word');
193+
}
194+
195+
public function testGetAlphaExceptionWithArray()
196+
{
197+
$bag = new InputBag(['word' => ['foo_BAR_012']]);
198+
199+
$this->expectException(BadRequestException::class);
200+
$this->expectExceptionMessage('Input value "word" contains a non-scalar value.');
201+
202+
$bag->getAlpha('word');
203+
}
204+
205+
public function testGetDigitsExceptionWithArray()
206+
{
207+
$bag = new InputBag(['word' => ['foo_BAR_012']]);
208+
209+
$this->expectException(BadRequestException::class);
210+
$this->expectExceptionMessage('Input value "word" contains a non-scalar value.');
211+
212+
$bag->getDigits('word');
213+
}
129214
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.