Boolean Loose Casting Function Tests

PHP Version

value = $value; } public function __toString() { return (string) $this->value; } } class CountableThing implements Countable { private $values = array(); public function __construct($values) { $this->values = $values; } public function count() { return count($this->values); } } class StringyAndCountableThing implements Countable { private $values = array(); private $value; public function __construct($stringy_value, $countable_values) { $this->value = $stringy_value; $this->values = $countable_values; } public function count() { return count($this->values); } public function __toString() { return (string) $this->value; } } /** * runs a test function on all test data, outputting tables of results */ function run_test($test) { global $test_data; echo '
'; echo '

'.$test.'

'; foreach($test_data as $set_name => $set) { echo '

'.$set_name.'

'; echo ''; foreach($set as $index => $value) { echo ''; echo ''; echo ''; $result = $test($value); $class = ($result ? 'truthy' : 'falsy'); if(is_null($result)) $class .= ' null'; echo ''; echo ''; } echo '
'; echo gettype($value); echo ''; // be clever with test values if(is_string($index)) { // use the index as a placeholder representation echo $index; } else if(is_resource($value)) { echo get_resource_type($value); } else { var_export($value); } echo ''; var_export($result); echo '
'; } echo '
'; }; /** * data to run through for all tests * string keys in subarrays can be used to represent values in test output */ $test_data = array( 'ought to be true' => array( true, 1, 'true', 'yes', 'YES', 'on', '1', 'y', 'this should probably be TRUE', 'รก', 100, 0.1, -1, INF, -INF, // for performance testing more than anything else // depending on the length used, may hit PHP's memory limit // http://www.php.net/manual/en/ini.core.php#ini.memory-limit // '(A VERY LONG STRING)' => str_repeat('.', 100000000), array(1), array('a key' => true), array(null => true), simplexml_load_string('i have content'), new StringyThing(true), new StringyThing('true'), new StringyThing('a string'), new CountableThing(array(1, 2, 3)), new CountableThing('count() returns 1 for this'), new StringyAndCountableThing(true, array(true)), ), 'ought to be false' => array( false, 0, 0.0, (float) '-0', '0', '', 'no', 'off', 'OFF', 'false', array(), // this is explicitly mentioned to be false in the manual: // http://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting simplexml_load_string(''), new StringyThing(false), new StringyThing('no'), new StringyThing(''), new CountableThing(array()), new StringyAndCountableThing(false, array()), ), 'iffy' => array( null, NAN, // the list of things like this could go on forever (not even taking translations into account) 'null', 'n', 'unset', 'nothing', 'nil', 'nope', // equivalent to an empty string in ini directives 'none', 'NONE', // this is not falsy in PHP (even though '0' is) '0.0', '0x0', ' ', 'PHP_EOL' => PHP_EOL, '"\t"' => "\t", // mixed signals, should countability or string value take precedence (or should these be null)? new StringyAndCountableThing(false, array(true)), new StringyAndCountableThing('yes', array()), // in PHP4 this would have been consider falsy new stdClass(), // could check for a ->scalar property to handle this case (object) false, array(false), array(null), array('a key' => false), array(null => false), // give these readable keys since var_export is not helpful for anonymous functions 'function() {}' => function() {}, 'function() { return false; }' => function() { return false; }, 'function() { return true; }' => function() { return true; }, "function() { return 'no'; }" => function() { return 'no'; }, "function() { return 'on'; }" => function() { return 'on'; }, "function() { return 'something completely different'; }" => function() { return 'something completely different'; }, // resources xml_parser_create(), fopen(__FILE__, 'r'), ), ); /** * functions to run tests with */ // PHP's built in behavior for things like if($value) function typecast($value) { return (boolean) $value; } function filter_var_bool($value) { return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); } // see http://us2.php.net/manual/en/filter.filters.validate.php#108218 // and https://bugs.php.net/bug.php?id=49510 function filter_var_bool_with_null_fallback($value) { $filtered = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if(is_null($filtered)) return (boolean) $value; else return $filtered; } // from http://www.php.net/manual/en/function.is-bool.php#93165 function kim_s_solution($var){ if(is_bool($var)){ return $var; } else if($var === NULL || $var === 'NULL' || $var === 'null'){ return false; } else if(is_string($var)){ $var = trim($var); if($var=='false'){ return false; } else if($var=='true'){ return true; } else if($var=='no'){ return false; } else if($var=='yes'){ return true; } else if($var=='off'){ return false; } else if($var=='on'){ return true; } else if($var==''){ return false; } else if(ctype_digit($var)){ if((int) $var) return true; else return false; } else { return true; } /* NOTE: i changed these lines from the original code. string casting results in an error for some objects, and handling floats correctly is a good idea anyway. } else if(ctype_digit((string) $var)){ if((int) $var) */ } else if(is_numeric($var)){ if((float) $var) /* end of changed lines */ return true; else return false; } else if(is_array($var)){ if(count($var)) return true; else return false; } else if(is_object($var)){ return true;// No reason to (bool) an object, we assume OK for crazy logic } else { return true;// Whatever came though must be something, OK for crazy logic } } function custom_solution($value) { // FIXME? if an object is both countable and string-castable, this will favor the count over the string value. // which should take precedence (if any)? maybe it should return null if there are mixed signals? or true? if(is_array($value) || $value instanceof Countable) { return (boolean) count($value); } else if(is_string($value) || is_object($value) && method_exists($value, '__toString')) { $value = (string) $value; // see http://www.php.net/manual/en/filter.filters.validate.php#108218 // see https://bugs.php.net/bug.php?id=49510 $filtered = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if(!is_null($filtered)) { return $filtered; } else { // "none" gets special treatment to be consistent with ini file behavior. // see documentation in php.ini for more information, in part it says: // "An empty string can be denoted by simply not writing anything after // the equal sign, or by using the None keyword". if(strtolower($value) === 'none') { $value = ''; } return (boolean) $value; } } else { return (boolean) $value; } } /** * test the various solutions */ run_test('typecast'); run_test('filter_var_bool'); run_test('filter_var_bool_with_null_fallback'); run_test('kim_s_solution'); run_test('custom_solution'); ?>