Skip to content

[HttpFoundation] Add nullable getters for InputBag/ParameterBag #53668

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/Symfony/Component/HttpFoundation/InputBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,26 @@ public function getString(string $key, string $default = ''): string
return (string) $this->get($key, $default);
}

public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
/**
* Returns the parameter value converted to string or null.
*/
public function getStringOrNull(string $key, ?string $default = null): ?string
{
// Shortcuts the parent method because the validation on scalar is already done in get().
$value = $this->get($key, $default);
if (null === $value) {
return null;
}

return (string) $value;
}

public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = [], bool $nullable = false): mixed
{
$value = $this->has($key) ? $this->all()[$key] : $default;
if ($nullable && null === $value) {
return null;
}

// Always turn $options into an array - this allows filter_var option shortcuts.
if (!\is_array($options) && $options) {
Expand Down
38 changes: 37 additions & 1 deletion src/Symfony/Component/HttpFoundation/ParameterBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ public function getString(string $key, string $default = ''): string
return (string) $value;
}

/**
* Returns the parameter as string or null.
*/
public function getStringOrNull(string $key, ?string $default = null): ?string
{
$value = $this->get($key, $default);
if (null === $value) {
return null;
}

if (!\is_scalar($value) && !$value instanceof \Stringable) {
throw new UnexpectedValueException(sprintf('Parameter value "%s" cannot be converted to "string".', $key));
}

return (string) $value;
}

/**
* Returns the parameter value converted to integer.
*/
Expand All @@ -143,6 +160,14 @@ public function getInt(string $key, int $default = 0): int
return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR]);
}

/**
* Returns the parameter value converted to integer or null.
*/
public function getIntOrNull(string $key, ?int $default = null): ?int
{
return $this->filter($key, $default, \FILTER_VALIDATE_INT, ['flags' => \FILTER_REQUIRE_SCALAR], true);
}

/**
* Returns the parameter value converted to boolean.
*/
Expand All @@ -151,6 +176,14 @@ public function getBoolean(string $key, bool $default = false): bool
return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR]);
}

/**
* Returns the parameter value converted to boolean or null.
*/
public function getBooleanOrNull(string $key, ?bool $default = null): ?bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use Boolean (long) but Int (short), what about using either getIntegerOrNull or getBoolOrNull?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree this is weird, but the decision should be taken first on getBoolean and getInt.
I'll be happy to update my PR as soon as a decision is taken first on which method between getBoolean and getInt should be deprecated (in favor of getBool or getInteger)

{
return $this->filter($key, $default, \FILTER_VALIDATE_BOOL, ['flags' => \FILTER_REQUIRE_SCALAR], true);
}

/**
* Returns the parameter value converted to an enum.
*
Expand Down Expand Up @@ -184,9 +217,12 @@ public function getEnum(string $key, string $class, ?\BackedEnum $default = null
*
* @see https://php.net/filter-var
*/
public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = [], bool $nullable = false): mixed
{
$value = $this->get($key, $default);
if ($nullable && null === $value) {
return null;
}

// Always turn $options into an array - this allows filter_var option shortcuts.
if (!\is_array($options) && $options) {
Expand Down
20 changes: 20 additions & 0 deletions src/Symfony/Component/HttpFoundation/Tests/InputBagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,26 @@ public function __toString(): string
$this->assertSame('strval', $bag->getString('stringable'), '->getString() gets a value of a stringable paramater as string');
}

public function testGetStringOrNull()
{
$bag = new InputBag(['nullable' => null, 'integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
public function __toString(): string
{
return 'strval';
}
}]);

$this->assertSame('123', $bag->getStringOrNull('integer'), '->getString() gets a value of parameter as string');
$this->assertSame('abc', $bag->getStringOrNull('string'), '->getString() gets a value of parameter as string');
$this->assertNull($bag->getStringOrNull('unknown'), '->getString() returns null if a parameter is not defined');
$this->assertSame('foo', $bag->getStringOrNull('unknown', 'foo'), '->getString() returns the default if a parameter is not defined');
$this->assertSame('1', $bag->getStringOrNull('bool_true'), '->getString() returns "1" if a parameter is true');
$this->assertSame('', $bag->getStringOrNull('bool_false', 'foo'), '->getString() returns an empty empty string if a parameter is false');
$this->assertSame('strval', $bag->getStringOrNull('stringable'), '->getString() gets a value of a stringable paramater as string');
$this->assertNull($bag->getStringOrNull('nullable'), '->getStringOrNull() gets null if a parameter is null');
$this->assertNull($bag->getStringOrNull('nullable', ''), '->getStringOrNull() gets null if a parameter is null');
}

public function testGetStringExceptionWithArray()
{
$bag = new InputBag(['key' => ['abc']]);
Expand Down
65 changes: 65 additions & 0 deletions src/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,28 @@ public function testGetIntExceptionWithInvalid()
$bag->getInt('word');
}

public function testGetIntOrNull()
{
$bag = new ParameterBag(['digits' => '123', 'nullable' => null]);

$this->assertSame(123, $bag->getIntOrNull('digits'), '->getIntOrNull() gets a value of parameter as integer');
$this->assertNull($bag->getIntOrNull('unknown'), '->getIntOrNull() returns default if a parameter is not defined');
$this->assertSame(0, $bag->getIntOrNull('unknown', 0), '->getIntOrNull() returns default if a parameter is not defined');
$this->assertSame(10, $bag->getIntOrNull('unknown', 10), '->getIntOrNull() returns the default if a parameter is not defined');
$this->assertNull($bag->getIntOrNull('nullable'), '->getIntOrNull() returns null if a parameter is null');
$this->assertNull($bag->getIntOrNull('nullable', 0), '->getIntOrNull() returns null if a parameter is null');
}

public function testGetIntOrNullExceptionWithInvalid()
{
$bag = new ParameterBag(['word' => 'foo_BAR_012']);

$this->expectException(\UnexpectedValueException::class);
$this->expectExceptionMessage('Parameter value "word" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.');

$bag->getIntOrNull('word');
}

public function testGetString()
{
$bag = new ParameterBag(['integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
Expand Down Expand Up @@ -242,6 +264,26 @@ public function testGetStringExceptionWithObject()
$bag->getString('object');
}

public function testGetStringOrNull()
{
$bag = new ParameterBag(['nullable' => null, 'integer' => 123, 'bool_true' => true, 'bool_false' => false, 'string' => 'abc', 'stringable' => new class() implements \Stringable {
public function __toString(): string
{
return 'strval';
}
}]);

$this->assertSame('123', $bag->getStringOrNull('integer'), '->getStringOrNull() gets a value of parameter as string');
$this->assertSame('abc', $bag->getStringOrNull('string'), '->getStringOrNull() gets a value of parameter as string');
$this->assertNull($bag->getStringOrNull('unknown'), '->getStringOrNull() returns null if a parameter is not defined');
$this->assertSame('foo', $bag->getStringOrNull('unknown', 'foo'), '->getStringOrNull() returns the default if a parameter is not defined');
$this->assertSame('1', $bag->getStringOrNull('bool_true'), '->getStringOrNull() returns "1" if a parameter is true');
$this->assertSame('', $bag->getStringOrNull('bool_false', 'foo'), '->getStringOrNull() returns an empty empty string if a parameter is false');
$this->assertSame('strval', $bag->getStringOrNull('stringable'), '->getStringOrNull() gets a value of a stringable paramater as string');
$this->assertNull($bag->getStringOrNull('nullable'), '->getStringOrNull() gets null if a parameter is null');
$this->assertNull($bag->getStringOrNull('nullable', ''), '->getStringOrNull() gets null if a parameter is null');
}

public function testFilter()
{
$bag = new ParameterBag([
Expand Down Expand Up @@ -337,6 +379,29 @@ public function testGetBooleanExceptionWithInvalid()
$bag->getBoolean('invalid');
}

public function testGetBooleanOrNull()
{
$parameters = ['string_true' => 'true', 'string_false' => 'false', 'string' => 'abc', 'nullable' => null];
$bag = new ParameterBag($parameters);

$this->assertTrue($bag->getBooleanOrNull('string_true'), '->getBooleanOrNull() gets the string true as boolean true');
$this->assertFalse($bag->getBooleanOrNull('string_false'), '->getBooleanOrNull() gets the string false as boolean false');
$this->assertNull($bag->getBooleanOrNull('unknown'), '->getBooleanOrNull() returns null if a parameter is not defined');
$this->assertTrue($bag->getBooleanOrNull('unknown', true), '->getBooleanOrNull() returns default if a parameter is not defined');
$this->assertNull($bag->getBooleanOrNull('nullable'), '->getBooleanOrNull() returns null if a parameter is null');
$this->assertNull($bag->getBooleanOrNull('nullable', true), '->getBooleanOrNull() returns null if a parameter is null');
}

public function testGetBooleanOrNullExceptionWithInvalid()
{
$bag = new ParameterBag(['invalid' => 'foo']);

$this->expectException(\UnexpectedValueException::class);
$this->expectExceptionMessage('Parameter value "invalid" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.');

$bag->getBooleanOrNull('invalid');
}

public function testGetEnum()
{
$bag = new ParameterBag(['valid-value' => 1]);
Expand Down