rfc:safe_cast

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

rfc:safe_cast [2014/11/12 20:15]
ajf Crated? Created.
rfc:safe_cast [2017/09/22 13:28]
Line 1: Line 1:
-====== PHP RFC: Safe Casting Functions ====== 
-  * Version: 0.1.4 
-  * Date: 2014-10-20, Last Updated 2014-11-12 
-  * Author: Andrea Faulds, ajf@ajf.me 
-  * Status: Under Discussion 
-  * First Published at: http://wiki.php.net/rfc/safe_cast 
  
-===== Introduction ===== 
- 
-Currently, PHP only provides one means of type conversion: explicit casts. These casts never fail or emit errors, making them dangerous to use, as when passed garbage input, they will simply return garbage instead of indicating that something went wrong. This makes it difficult to write robust applications which handle user data. They also prevent any suggestion of strict type hinting for scalar types, because if that were to be added, users would simply use dangerous explicit casts to get around errors and the result would be code that is buggier than it would have been without type hinting at all. 
- 
-===== Proposal ===== 
- 
-A set of three "safe casting" functions for converting to types is is added to ''ext/standard'': ''to_int()'', ''to_float()'' and ''to_string()''. These functions validate their input to ensure data is not lost with the cast (thus the cast can be considered safe), instead of casting blindly. If the input fails to validate, they return NULL. (Returning ''NULL'' is controversial, however, so while this is my preferred option and is the one implemented, it will be put to a vote.) 
- 
-''to_int()'' accepts only ints, non-NaN integral floats within the range of an integer (''PHP_INT_MIN'' to ''PHP_INT_MAX''), and strings containing decimal integer sequences within the range of an integer. Leading and trailing whitespace is not permitted, nor are leading zeros or a positive sign. 
- 
-''to_float()'' accepts only ints, floats, and strings representing floats. Leading and trailing whitespace is not permitted, nor are leading zeros or a positive sign. 
- 
-''to_string()'' accepts only strings, objects which cast to strings, ints and floats. 
- 
-==== Rationale ==== 
- 
-The concept was developed in my thoughts, and in discussions with Anthony Ferrara (both in-person at PHPNW14 and online) and others in [[http://chat.stackoverflow.com/rooms/11/php|StackOverflow's PHP chatroom]]. Here, I list some of my or our rationale for particular decisions. 
- 
-  * The functions keep to the principle of zero data-loss, i.e. where ''X'' is the type of ''$A'' and ''Y'' is the type being converted to, ''to_Y($A) !== NULL'' [[https://en.wikipedia.org/wiki/If_and_only_if|IFF]] ''(X)(Y)$A === $A''. However, there are some limited exceptions: 
-      * Floats are allowed to lose accuracy from their decimal representations. This is because zero data-loss is impossible for floats, as PHP does not output them in full precision. Furthermore, some loss of accuracy is expected when dealing with floats. 
-      * Objects with ''__toString'' are accepted for ''to_string'', despite a cast back from a string to the original type not necessarily being allowed 
-      * ''to_int'' and ''to_float'' only accept integers, floats and strings 
-      * ''to_string'' only accepts integers, floats, strings and objects 
-  * An error return value was chosen instead of an exception: 
-      * To make chaining easier, and because checking for NULL is easier than checking if an exception was thrown (''if ($value === NULL) { ... } else { ... }'' vs ''try { ... } catch (Exception $e) { $errored = true; ... } if (!$errored) { ... }'') 
-      * Exceptions are currently not used in core functions 
-      * The cast failing is not an exceptional case, as it is a validation function, so it should use a return value, not an exception 
-      * Catching exceptions is slower than checking error values 
- 
-==== Examples Table ==== 
- 
-A sample table of whether values pass or fail generated by [[https://gist.github.com/TazeTSchnitzel/19c91f800e47d53cc28c|this script]], on a 64-bit machine: 
- 
-^ value                                   ^ to_int                                  ^ to_float                                ^ to_string                                
-^ string(6) "foobar"                      | fail                                    | fail                                    | pass                                    |  
-^ string(0) ""                            | fail                                    | fail                                    | pass                                    |  
-^ string(1) "0"                           | pass                                    | pass                                    | pass                                    |  
-^ int(0)                                  | pass                                    | pass                                    | pass                                    |  
-^ float(0)                                | pass                                    | pass                                    | pass                                    |  
-^ string(2) "10"                          | pass                                    | pass                                    | pass                                    |  
-^ string(3) "010"                         | fail                                    | fail                                    | pass                                    |  
-^ string(3) "+10"                         | fail                                    | fail                                    | pass                                    |  
-^ string(3) "-10"                         | pass                                    | pass                                    | pass                                    |  
-^ int(10)                                 | pass                                    | pass                                    | pass                                    |  
-^ float(10)                               | pass                                    | pass                                    | pass                                    |  
-^ string(19) "9223372036854775807"        | pass                                    | pass                                    | pass                                    |  
-^ int(9223372036854775807)                | pass                                    | pass                                    | pass                                    |  
-^ string(20) "-9223372036854775808"       | pass                                    | pass                                    | pass                                    |  
-^ int(-9223372036854775808)               | pass                                    | pass                                    | pass                                    |  
-^ string(4) "10.0"                        | fail                                    | pass                                    | pass                                    |  
-^ string(5) "75e-5"                       | fail                                    | pass                                    | pass                                    |  
-^ string(5) "31e+7"                       | fail                                    | pass                                    | pass                                    |  
-^ NULL                                    | fail                                    | fail                                    | fail                                    |  
-^ bool(true)                              | fail                                    | fail                                    | fail                                    |  
-^ bool(false)                             | fail                                    | fail                                    | fail                                    |  
-^ object(stdClass)#1 (0) {}               | fail                                    | fail                                    | fail                                    |  
-^ resource(5) of type (stream)            | fail                                    | fail                                    | fail                                    |  
-^ array(0) {}                             | fail                                    | fail                                    | fail                                    |  
-^ float(1.5)                              | fail                                    | pass                                    | pass                                    |  
-^ string(3) "1.5"                         | fail                                    | pass                                    | pass                                    |  
-^ string(5) "10abc"                       | fail                                    | fail                                    | pass                                    |  
-^ string(5) "abc10"                       | fail                                    | fail                                    | pass                                    |  
-^ string(4) "100 "                        | fail                                    | fail                                    | pass                                    |  
-^ string(4) " 100"                        | fail                                    | fail                                    | pass                                    |  
-^ string(5) " 100 "                       | fail                                    | fail                                    | pass                                    |  
-^ string(4) "0x10"                        | fail                                    | fail                                    | pass                                    |  
-^ float(INF)                              | fail                                    | pass                                    | pass                                    |  
-^ float(-INF)                             | fail                                    | pass                                    | pass                                    |  
-^ float(NAN)                              | fail                                    | pass                                    | pass                                    |  
-^ float(1.844674407371E+19)               | fail                                    | pass                                    | pass                                    |  
-^ float(-1.844674407371E+19)              | fail                                    | pass                                    | pass                                    |  
-^ string(18) "1.844674407371E+19"         | fail                                    | pass                                    | pass                                    |  
-^ string(19) "-1.844674407371E+19"        | fail                                    | pass                                    | pass                                    |  
-^ object(Stringable)#2 (0) {}             | fail                                    | fail                                    | pass                                    |  
-^ object(NotStringable)#3 (0) {}          | fail                                    | fail                                    | fail                                    |  
-^ object(stdClass)#4 (0) {}               | fail                                    | fail                                    | fail                                    |  
- 
-===== Proposed PHP Version(s) ===== 
- 
-This is proposed for the next major version of PHP, currently PHP 7. 
- 
-===== Open Issues ===== 
- 
-While I'd prefer to return NULL on error, it would also be possible to return FALSE. As this seems to be relatively controversial, it will be put to a vote. 
- 
-===== Unaffected PHP Functionality ===== 
- 
-This does not touch the explicit cast operators (''(int)'', ''(float)'', ''(string)'' etc.) nor the explicit cast functions (''intval()'', ''floatval'', ''strval()'' etc.). 
- 
-===== Future Scope ===== 
- 
-This might be extended to other types. However, support for the other scalar types has deliberately not been included. For booleans, there is no clear single format to accept, and it is very simple to do so manually. NULL is a type with only one possible value, so there is no point in casting. Resources are special and don't really count as scalars. 
- 
-===== Proposed Voting Choices ===== 
- 
-As this is not a language change and only introduces new functions, only a 50%+1 majority will be required. The vote will be a straight Yes/No vote on accepting the RFC and merging the patch into master. 
- 
-Because the behaviour on the error case is controversial, a second two-way vote will be held at the same time, with the options being return ''NULL'' and return ''FALSE''. 
- 
-===== Patches and Tests ===== 
- 
-I have made a patch and pull request on GitHub against the master branch: https://github.com/php/php-src/pull/874 
- 
-Theodore Brown has created a userland polyfill with the same behaviour: https://github.com/theodorejb/PolyCast 
- 
-===== Implementation ===== 
-After the project is implemented, this section should contain  
-  - the version(s) it was merged to 
-  - a link to the git commit(s) 
-  - a link to the PHP manual entry for the feature 
- 
-===== References ===== 
- 
-Safer or stricter casts have been requested before: 
- 
-  * http://marc.info/?l=php-internals&m=141029082416896&w=2 
-  * http://marc.info/?l=php-internals&m=138868787412173&w=2 
- 
-===== Rejected Features ===== 
-Keep this updated with features that were discussed on the mail lists. 
- 
-===== Changelog ===== 
- 
-  * v0.1.4 - Reject leading '+' and '0' for int/float, ''to_Y($A) !== NULL IFF (X)(Y)$A === $A'' principle in rationale 
-  * v0.1.3 - Return NULL, don't include exceptions in vote 
-  * v0.1.2 - Leading and trailing whitespace is not permitted 
-  * v0.1.1 - Rationale 
-  * v0.1 - Created 
rfc/safe_cast.txt · Last modified: 2017/09/22 13:28 (external edit)