[HttpFoundation] Add `ParameterBag::getString` and add 3rd argument to make `getInt` more strict
| Q | A |
|---|---|
| Branch? | 6.3 |
| Bug fix? | no |
| New feature? | yes |
| Deprecations? | yes |
| Tickets | Fix #44787 #48219 |
| License | MIT |
| Doc PR | symfony/symfony-docs#... |
There were a lot of discussions on #44787 regarding the implementation. The main debate is to determine the right behavior when the value is invalid: try to convert the value, throw an exception, return the default value or the falsy?
I added plenty of test cases for the methods getAlpha, getAlnum, getDigits, getInt, ~getInteger~, getString so that we can discuss the expected result for each input value.
My goals:
- Provide safe methods to get values in the expected type. Example: The parameters being generally of type
string, thegetStringmethod is useful to be sure we don't get an array. - Reduce deprecations on methods that exist since Symfony 2.0, in one of the most popular Symfony component.
How are these getter methods used?
- Most of the time, parameter values are
string(from routing, query string, request payload).getis used but does not validate the return type. -
getIntis used for pagination (UX Autocomplete) or getting an index (EasyAdmin) - I think there was an unidentified breaking change when we introduced return types #42507. Methods
getAlpha,getAlnumandgetDigitscould return an array, but their return type have been modified tostring, resulting in aTypeError. example of use
I don't really like the extra argument to handle the BC layer because it means we're going to have to deprecate it in 7.1 + remove it in 8.0. Looks like a costly change. But I don't have a better idea other than just changing the behavior on 6.3 without this layer...
I'll take care of this process 😃.
But I am also tempted to change the behavior directly in 6.3. The modification to keep the current behavior is to replace $parameters->getInt('page') by (int) $parameters->get('page'). This change in user code is compatible with all previous version of Symfony and could be applied as a bugfix in all existing codebase that want to fallback to 0 in case of invalid value.
But I am also tempted to change the behavior directly in 6.3.
I vote for this also. getInt() is for ints and the BC layer looks quite costly to me. I'd prefer getting rid of it.
Dunno it it's a good idea but we could plan the BC break for 7.0 and announce the change with the following:
try {
trigger_deprecation(...);
trigger_error('Blahblah');
} catch (\ErrorException $e) {
throw new BadRequestException(...);
}
By first a deprecation we announce this using the existing channel. The trigger_error would then turn into an exception in dev, and a regular non-blocking in prod. We would preserve the current behavior in 6.3+ and turn that into a real exception in 7.0.
WDYT?
That's very clever. I'll try to implement this solution.
After reflection by testing the implementation, as a user of this functions I prefer adding a 3rd argument to getInt instead of wrapping the method call with try-catch in order to convert the error (which we are not used to deal with) into the expected exception.
Why would you wrap the call?
I'm not sure to understand your proposition. Here is code samples from user/developer view.
Currently (<=6.2):
$inputs->getInt('invalid'); // returns 0
With deprecation (6.3)
$inputs->getInt('invalid'); // returns 0
// and trigger deprecation + error
Handling deprecation with try-catch (6.3) (my understanding of @nicolas-grekas proposition)
try {
$inputs->getInt('invalid');
} catch (\ErrorException $e) {
throw new BadRequestException($e->getMessage());
}
// throw an exception, the developer have to know which one.
// the deprecation notice is still triggered.
Handling deprecation with 3rd arg (6.3) (my first proposition)
$inputs->getInt('invalid', 0, true);
// throw an exception
Target in 7.0
$inputs->getInt('invalid', 0);
// throw an exception
I meant to put the try/catch inside getInt, not outside!
Hum, something like this?
public function getInt(string $key, int $default = 0): int
{
try {
return $this->filter(/* ... */);
} catch (\UnexpectedValueException|BadRequestException $e) {
trigger_deprecation('symfony/http-foundation', '6.3', '...');
trigger_error($e->getMessage());
return 0;
}
}
Updated. I'm not sure about the trigger_error: this introduces a very different behavior on dev vs prod. And I don't know how to test it since expectError is deprecated.
or better: we (plan to) throw unless FILTER_NULL_ON_FAILURE is explicitly set, and we drop the error_on_failure option
This is a brilliant idea, let me explain it for the readers.
Historically, filter_var returns false in case of invalid data. But this is a design problem because you can't tell a false from FILTER_VALIDATE_BOOLEAN from a false from an invalid value. So FILTER_NULL_ON_FAILURE was introduced in PHP 5.2 to allow differentiating errors. So we should always use this flag.
We will use the absence of this flag to define our default behavior in Symfony 7.0: throw an exception in case of invalid data.
Thank you @GromNaN.