rfc:named_params
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:named_params [2013/09/06 16:34] – created nikic | rfc:named_params [2022/05/26 15:30] – Add alternatives section iquito | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Named Parameters | + | ====== PHP RFC: Stricter implicit boolean coercions |
- | * Version: | + | * Version: |
- | * Date: 2013-09-06 | + | * Date: 2022-05-16 |
- | * Author: | + | * Author: |
* Status: Under Discussion | * Status: Under Discussion | ||
- | * Proposed for: PHP 5.6 | + | * First Published at: http:// |
- | ===== State of this RFC ===== | + | ===== Introduction |
- | This is a preliminary RFC for named parameters. It's purpose is to find out if we want to support them in the next PHP version and if so, how the implementation should work. The syntax and behavior described here are just basic ideas that still need to be fleshed out. | + | When not using strict_types |
- | The implementation that accompanies | + | Some examples where this might lead to unexpected outcomes: |
- | The implementation is based off and includes the [[rfc: | + | < |
- | + | function toBool(bool $a) | |
- | ===== What are named arguments? ===== | + | { |
+ | | ||
+ | } | ||
- | Named arguments are a way to pass arguments to a function, which makes use of the parameter names rather than the position of the parameters: | + | toBool(' |
+ | toBool(-0); // bool(false) | ||
+ | toBool(' | ||
+ | toBool(0.0); | ||
+ | toBool(' | ||
+ | toBool(0.1); | ||
+ | toBool(-37593); | ||
+ | toBool(' | ||
+ | toBool(' | ||
+ | </ | ||
- | <code php> | + | ===== Proposal ===== |
- | // Using positional arguments: | + | |
- | array_fill(0, | + | |
- | // Using named arguments: | + | |
- | array_fill(start_index | + | |
- | </ | + | |
- | The order in which the named arguments | + | In coercive typing mode, limit the allowed scalar values for typed boolean |
- | <code php> | + | * 0 (and -0) integer (= false) |
- | array_fill(value => 42, num => 100, start_index | + | * 0.0 (and -0.0) float (= false) |
- | </ | + | * " |
+ | * "" | ||
+ | * 1 integer (= true) | ||
+ | * 1.0 float (= true) | ||
+ | * " | ||
- | It is possible to combine named arguments with normal, positional arguments | + | Any other integers, floats |
- | <code php> | + | * For coercions from string |
- | htmlspecialchars($string, double_encode => false); | + | * For coercions from int the deprecation notice is: Implicit conversion from int %d to true, only 0 or 1 are allowed |
- | // Same as | + | * For coercions from float the deprecation notice is: Implicit conversion from float %f to true, only 0 or 1 are allowed |
- | htmlspecialchars($string, | + | |
- | </ | + | |
- | ===== What are the benefits of named arguments? ===== | + | These would be the notices generated for the examples in the introduction: |
- | One obvious benefit of named arguments can be seen in the last code sample | + | < |
+ | toBool('0'); | ||
+ | toBool(-0); | ||
+ | toBool('-0'); // Implicit conversion from string " | ||
+ | toBool(0.0); | ||
+ | toBool(' | ||
+ | toBool(0.1); | ||
+ | toBool(-37593); | ||
+ | toBool(' | ||
+ | toBool(' | ||
+ | </ | ||
- | This is also possible with the [[rfc: | + | In the long-term these deprecations should be raised to a warning or to a '' |
- | <code php> | + | ===== Rationale ===== |
- | htmlspecialchars($string, | + | |
- | // vs | + | |
- | htmlspecialchars($string, | + | |
- | </ | + | |
- | Seeing the first line you will not know what the '' | + | This RFC boils down to these questions: |
- | The benefit of making code self-documenting obviously even applies | + | * Are you losing information |
+ | * Would you want to know when a value like -375, " | ||
+ | * How likely is it that such a coercion is unintended? | ||
+ | * What about other boolean coercions in PHP? (this is handled in the next section) | ||
- | <code php> | + | The main motivation for this RFC is to reduce the possibility of errors when using the boolean type in a similar way that you cannot give a typed int a non-number string - if you provide |
- | $str-> | + | |
- | // vs | + | |
- | $str-> | + | |
- | </ | + | |
- | Currently you can already get something similar to named arguments by taking | + | * Avoid losing information when an unusual value is coerced to a boolean type |
+ | * Make the boolean type and the type juggling system safer and more consistent | ||
+ | * Setting up only 7 scalar values as unambiguous boolean values is easy to document and reason about | ||
- | < | + | When implementing this feature I found two bugs in php-src tests and the test runner that are most likely typical cases: |
- | htmlspecialchars($string, | + | |
- | </ | + | |
- | Using an '' | + | * In the PHP test runner the strings " |
+ | * In an IMAP test a boolean argument $simpleMessages always got the string " | ||
+ | |||
+ | Changing the type of an argument, return or property in a codebase happens often, and because the boolean type accepts everything with no complaint | ||
- | * The available options are not documented in the signature. You have to look into the code to find out. | + | While using strict_types is an option |
- | * Handling '' | + | |
- | * Something like '' | + | |
- | Lastly, named arguments allow a new sort of variadic function, one which can take not just an ordered list of values, but also a list of key-value pairs. Sample application is the '' | + | ===== Other boolean coercions in PHP ===== |
- | <code php> | + | Typed booleans |
- | // currently possible: | + | |
- | $db-> | + | |
- | ' | + | |
- | $firstName, $lastName, $minAge | + | |
- | ); | + | |
- | // named args additionally allow: | + | |
- | $db-> | + | |
- | | + | |
- | firstName => $firstName, lastName => $lastName, minAge => $minAge | + | |
- | ); | + | |
- | </code> | + | |
- | ===== Implementation ===== | + | However in these expressions you can use any values and are not restricted to scalar types like with typed booleans: |
- | ==== Internally ==== | + | < |
+ | if ($variable) { // identical to if ($variable | ||
+ | // the $variable in the if statement is coerced in the following way: | ||
+ | // - true for a string if it is not empty and not ' | ||
+ | // - true for an int if it is not zero | ||
+ | // - true for a float if it is not zero | ||
+ | // - true for an array if it is not empty | ||
+ | // - always true for a resource | ||
+ | // - always true for an object | ||
+ | // - always false for null | ||
+ | } | ||
- | Named args are internally passed the same way as other arguments | + | if ($array) { |
+ | // executed for a non-empty array | ||
+ | } | ||
- | ==== Errors ==== | + | toBool($array); |
+ | </ | ||
- | While it is possible | + | Typed booleans behave differently compared |
- | <code php> | + | <PHP> |
- | strpos(haystack => " | + | // often used to check if $string is not empty, and it is reasonably clear |
- | // Fatal error: Cannot pass positional arguments after named arguments | + | if ($string) { |
- | </ | + | // do something with $string here |
+ | } | ||
- | If a named argument | + | $obj-> |
+ | // is it a value from a form, API or DB that should be '', | ||
+ | // or is it a mistake because something | ||
+ | </ | ||
- | <code php> | + | When giving a typed boolean a scalar value you are reducing an int, float or string to a boolean, possibly losing information, |
- | strpos(hasytack => " | + | |
- | // Fatal error: Unknown named argument $hasytack | + | |
- | </code> | + | |
- | When named arguments are in used, it can happen that the same parameter is set twice. In this case the newer value will overwrite the older one and a warning | + | < |
+ | $obj-> | ||
+ | $obj-> | ||
+ | </ | ||
- | <code php> | + | ===== filter extension ===== |
- | function test($a, $b) { var_dump($a, | + | |
- | test(1, 1, a => 2); // 2, 1 | + | The filter extension has its own way to validate booleans |
- | // Warning: Overwriting already passed parameter 1 ($a) | + | |
- | test(a => 1, b => 1, a => 2); // 2, 1 | + | |
- | // Warning: Overwriting already passed parameter 1 ($a) | + | |
- | </ | + | |
- | ==== Collecting unknown named arguments ==== | + | * " |
+ | * if FILTER_NULL_ON_FAILURE is also used, only " | ||
+ | |||
+ | This behavior is incompatible with how PHP handles boolean coercions, making it impossible to resolve the behaviors without massive BC breaks. But it does add another argument in favor of this RFC - somebody switching from the filter extension to built-in boolean types has a big chance of accidentally introducing behavior changes in their application: | ||
- | Functions declared as variadic using '' | + | * PHP converts most values to true, while the filter extension converts these values to false (or null) - for example " |
+ | * The deprecation notice would make all these occurences visible | ||
+ | |||
+ | Usages of FILTER_VALIDATE_BOOLEAN are otherwise not affected by this RFC - that behavior remains unchanged. | ||
- | Example of the behavior: | + | ===== Considered alternatives ===== |
- | <code php> | + | It was briefly considered to allow more values for typed booleans instead of only 0, 1 and an empty string - for example the string " |
- | function test(...$args) { var_dump($args); | + | |
- | test(1, 2, 3, a => ' | + | Another possibility would have been to also change the behavior of boolean coercions, for example coerce the string |
- | // [1, 2, 3, "a" => " | + | |
- | </ | + | |
- | An example usage is the '' | + | ===== Implementation notes ===== |
- | This feature | + | As this is my first RFC and my first contribution to php-src, I mimicked the code from the " |
- | ==== Unpacking named arguments | + | ===== Backward Incompatible Changes ===== |
- | The '' | + | The following operations will now emit an '' |
- | <code php> | + | * Assignment |
- | $params = [' | + | * Argument for a parameter of type '' |
- | strpos(...$params); | + | * Returning such a value for userland functions declared with a return |
- | </ | + | |
- | + | ||
- | Any value with a string key is unpacked as a named parameter. Other key types (for arrays only integers) are treated as normal positional arguments. | + | |
- | + | ||
- | It's possible | + | |
- | + | ||
- | ==== func_* and call_user_func_array ==== | + | |
- | + | ||
- | If (due to the usage of named arguments) some arguments are missing ('' | + | |
- | + | ||
- | * '' | + | |
- | * '' | + | |
- | * '' | + | |
| | ||
- | All three functions are also oblivious | + | The actual conversion |
- | + | ||
- | The '' | + | |
- | Generally: | + | The following shows typical ways to avoid a deprecation notice: |
- | ===== Open questions | + | < |
+ | // Resolution 1: Check for an expected value or range | ||
+ | toBool($number > 0); | ||
+ | toBool($int | ||
+ | toBool($string | ||
+ | toBool(strlen($string) > 0); | ||
+ | |||
+ | // Resolution 2: Check for truthiness | ||
+ | toBool($scalar | ||
+ | |||
+ | // Resolution 3: Explicitly cast the argument | ||
+ | toBool((bool) $scalar); | ||
+ | </ | ||
- | ==== Syntax ==== | + | With the many deprecation notices that appeared in PHP 8.0 and 8.1 there is some wariness if more deprecation notices are worth it. These are the arguments why the RFC author thinks it will be worth it without too much pain: |
- | The current implementation (and proposal) support | + | * Each individual case is easy to fix, the easiest |
- | + | * Most of the coercions that will lead to a deprecation notice are likely to be unintended and the information given in the notice should make it reasonably clear to a developer whether it is a bug and how to fix it | |
- | <code php> | + | * bool arguments |
- | test(foo => " | + | * deprecation notices |
- | test(" | + | |
- | </ | + | |
- | + | ||
- | The second syntax | + | |
- | + | ||
- | <code php> | + | |
- | test(array => [1, 2, 3]); // syntax error | + | |
- | test(" | + | |
- | </ | + | |
- | + | ||
- | The choice of this syntax is mostly arbitrary, I didn't put much thought into it. Here are some alternative syntax proposals courtesy of Phil Sturgeon: | + | |
- | + | ||
- | <code php> | + | |
- | // currently implemented: | + | |
- | test(foo => " | + | |
- | test(" | + | |
- | + | ||
- | // suggestions (can use keywords): | + | |
- | test($foo => " | + | |
- | test(:foo => " | + | |
- | + | ||
- | // suggestions (cannot use keywords): | + | |
- | test(foo = " | + | |
- | test(foo: " | + | |
- | + | ||
- | // not possible because already valid code: | + | |
- | test($foo = " | + | |
- | </ | + | |
- | + | ||
- | Which one(s) of these we want to support is up to discussion. | + | |
- | + | ||
- | ==== Collection | + | |
- | + | ||
- | The current implementation / proposal suggests | + | |
- | + | ||
- | Pro current solution: | + | |
- | + | ||
- | * Seems very PHP-like to do it this way, because PHP allows mixing | + | |
| | ||
- | Con current solution: | + | ===== Proposed PHP Version |
- | + | ||
- | * Having a separate syntax for capturing unknown named args makes the intention clearer: You don't always want to support **both** positional and named variadics. Separate syntax allows you to enforce one type or the other. | + | |
- | + | ||
- | Opinions and arguments how to handle this are welcome. | + | |
- | + | ||
- | ==== Unpacking named args ==== | + | |
- | + | ||
- | The same question comes up for argument unpacking: Should the '' | + | |
- | + | ||
- | In any case, this descision should mirror the one for the previous question. | + | |
- | + | ||
- | ==== Parameters names are part of the contract ==== | + | |
- | + | ||
- | Currently parameter names are not part of the contract: In an interface implementation you can rename parameters as much as you like, it won't make a difference to the caller. Named arguments change this. If an inheriting class changes a parameter name calls using named args might fail, thus violating LSP: | + | |
- | + | ||
- | <code php> | + | |
- | interface A { | + | |
- | public function test($foo, $bar); | + | |
- | } | + | |
- | + | ||
- | class B implements A { | + | |
- | public function test($a, $b) {} | + | |
- | } | + | |
- | $obj = new B; | + | Next minor version: PHP 8.2. |
- | // Pass params according to A::test() contract | + | ===== Unaffected PHP Functionality ===== |
- | $obj-> | + | |
- | </ | + | |
- | If named parameters are introduced, signature validation should include parameter names. Throwing | + | * Manually casting to boolean will not raise a notice. |
+ | * Strict Type behaviour is unaffected. | ||
+ | * Implicit boolean expressions | ||
+ | * FILTER_VALIDATE_BOOLEAN in the filter extension is not affected. | ||
- | ===== Patch ===== | + | ===== Patches and Tests ===== |
- | You can find the diff for the work-in-programm patch here: https:// | + | Patch: https:// |
- | Credits: The patch includes some of the work that Stas' did for the skipparams RFC. | + | ===== References ===== |
- | Work that still needs to be done: | + | Initial mailing list discussion: < |
+ | RFC mailing list discussion: < | ||
- | * Implement the results of "Open questions" | ||
- | * Update all arginfos of internal functions to match the documentation (and improve names along the way). The current arginfo structs are hopelessly outdated. I hope that this work can be done mainly automatically. (Note: After named parameters are introduced the argument names are frozen and should not be changed.) | ||
- | * Make sure that internal functions properly handle skipped arguments. This should work in most cases automatically, |
rfc/named_params.txt · Last modified: 2022/05/26 15:31 by iquito