Related
I have an user input like this: $_POST['multi']['dim']['form'] = 1.213.
I want to validate this and at the moment I use this function:
public function checkFloat($number, $min = null, $max = null)
{
$num = filter_var($number, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND);
if($num === false || ($min !== null && $num < $min) || ($max !== null && $num > $max))
return false;
return $num;
}
But befor calling this function I have to check first that $_POST['multi']['dim']['form'] is set.
So every call looks like
if(isset($_POST['multi']['dim']['form']) && ($num = checkFloat($_POST['multi']['dim']['form']) !== false))
{
// do something here
}
If I do not check first, if the variable is set, PHP will throw a notice.
I noticed the PHP function filter_input, but it seems that this is not working with multidimensional $_POST.
I thought about a wrapper like
function checkFloat($names_of_the_fields,$min,$max)
{
// check if $_POST[$name][$of][$the][...] is set
// make the validation
}
But I'm not sure how to pass the $name_of_the_fields.
Here I thought of an array $arr['key1']['key2'][...]. but since I not know how deep this is, i have to run a lot is_array checks. Or I pass an array like $arr = ['key1','key2',...].
Is there a nice and clean way to do this? Should I ignore the notice?
Or should I go on with if(isset.. && checkFloat...)?
Changing the form and using eval() is not an option.
Thanks in advance
EDIT 1:
is_array($var) is not that slow, if $var is setted. So it would be ok, if I use a function that checks structure. But the question is still, if this is a good way, or if there is a better (maybe faster) way to do this.
How about this? You'll like it :D
function filter_input_simple($type, $name, $filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
static $vars;
if (!$vars) {
$vars = array(
INPUT_GET => filter_input(INPUT_SERVER, 'QUERY_STRING'),
INPUT_POST => file_get_contents('php://input'),
INPUT_COOKIE => filter_input(INPUT_SERVER, 'HTTP_COOKIE'),
);
$s = array('&', '&', ';[; ]++');
foreach ($vars as $t => $var) {
$tmp = array();
foreach (preg_split("#{$s[$t]}#", $var, -1, PREG_SPLIT_NO_EMPTY) as $i) {
list($k, $v) = explode('=', $i, 2) + array(1 => '');
$tmp[urldecode($k)] = urldecode($v);
}
unset($tmp['']);
$vars[$t] = $tmp;
}
$vars += array(INPUT_REQUEST =>
$vars[INPUT_COOKIE] + $vars[INPUT_POST] + $vars[INPUT_GET]
);
}
$type = (int)$type;
$name = filter_var($name);
if (!isset($vars[$type][$name])) {
return null;
}
return filter_var($vars[$type][$name], $filter, $options);
}
Usage:
// This function does not use extracted values as $_GET,
// so put QueryString on your browser for testing.
//
// ?foo[bar][validated_float]=1,234.213
$options = array(
'options' => array(
'min_range' => 1234,
'max_range' => 1235,
),
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
);
var_dump(filter_input_simple(INPUT_GET, 'foo[bar][validated_float]', FILTER_VALIDATE_FLOAT, $options));
After some thinking and testing I came up with this:
I splitted each validation function up into two functions and added a function that checks if an array key exists.
validate[Type]Var($val, $options): checks if $val contains a valid value. (no changes here)
validate[Type]Input(&$inputArr,$keys,$options): calls getArrayValue(&array,$keys) and if the key exists, it calls validate[Type]Val() to validate the value.
In detail:
public function getArrayValue(&array,$keys)
{
$key = array_shift($keys);
if(!isset($array[$key]))
return null;
if(empty($keys))
return $array[$key];
return $this->getArrayValue($array[$key],$keys);
}
public function validateTypeInput(&$inputArr, $keys, $options = [])
{
$value = $this->getArrayValue($inputArr, $keys);
if(isset($value))
return $this->validateTypeVal($value,$options);
else
return null; // or return anything else to show that the value was invalid
}
The function can be called by
ValidationClass->validateTypeInput($_POST,['key1','key2','key3'],$options);
to validate $_POST['key1']['key2']['key3'].
Notice: I wrote an validateTypeInput for each type, because some of them are different. Eg. if you validate checkbox input, you dont want a not setted input to be considered invalid.
It won't validate the structure of the data passed to it, but it will ensure that each item passes the checkFloat function:
function validate_info($info) {
if (is_array($info)) {
// $info is an array, validate each of its children
foreach ($info as $item) {
// If item is invalid, stop processing
if (validate_info($item) === false) { return false; }
}
// If each $item was valid, then this $info is valid.
return true;
} else {
// $info is a single item
return checkFloat($info, [YOUR_MIN_VAL], [YOUR_MAX_VAL]);
}
}
The function will return true if every item in $info returns true for checkFloat. If any value in $info returns false, then validate_info will return false. This function uses recursion to check the whole array, regardless of structure.
edit: This function does use the is_array function, but you seem to be mistakenly thinking that the is_array function is slow. The is_array function runs in constant time, which can be verified by checking the PHP source code.
Class Definition:
/**
* Object for filter_input_array_recursive().
*/
class FilterObject {
private $filter;
private $options;
/**
* Constructor.
*
* #param int $filter same as ones for filter_input().
* #param mixed $options same as ones for filter_input().
*/
public function __construct($filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
$this->filter = $filter;
$this->options = $options;
}
public function getFilter() {
return $this->filter;
}
public function getOptions() {
return $this->options;
}
}
Function Definition:
/**
* Apply filter_input() recursively.
*
* #param int $type INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_REQUEST are supported.
* #param array $filters Multi-demensional array, which contains FilterObject for each leaf.
* #return array
*/
function filter_input_array_recursive($type, array $filters) {
static $recursive_static;
static $flag_match;
static $is_not_array;
static $filter_array;
static $types;
if (!$flag_match) {
/* initialize static variables */
$types = array(
INPUT_GET => $_GET,
INPUT_POST => $_POST,
INPUT_COOKIE => $_COOKIE,
INPUT_REQUEST => $_REQUEST,
);
$flag_match = function ($v, $f) {
return (int)(isset($v['flags']) ? $v['flags'] : $v) & $f;
};
$is_not_array = function ($v) {
return !is_array($v);
};
$filter_array = function ($v) {
return !is_array($v) ? $v : false;
};
}
$recursive = $recursive_static;
if (!$recursive) {
/* only for first loop */
$type = (int)$type;
if (!isset($types[$type])) {
throw new \InvalidArgumentException('unknown super global var type');
}
$var = $types[$type];
$recursive_static = true;
} else {
/* after first loop */
$var = $type;
}
$ret = array();
foreach ($filters as $key => $value) {
$isset = isset($var[$key]);
if (is_array($value)) {
// apply child filters
$ret[$key] = filter_input_array_recursive($isset ? $var[$key] : array(), $value);
} else {
if (!($value instanceof FilterObject)) {
// create default FilterObject for invalid leaf
$value = new FilterObject;
}
$filter = $value->getFilter();
$options = $value->getOptions();
// check if key exists...
// true -> apply filter_var() with supplied filter and options
// false -> regard as null
try {
$ret[$key] = $isset ? filter_var($var[$key], $filter, $options) : null;
} catch (Exception $e) {
$recursive_static = false;
throw $e;
}
if ($flag_match($options, FILTER_FORCE_ARRAY | FILTER_REQUIRE_ARRAY)) {
// differently from filter_input(),
// this function prevent unexpected non-array value
if (!is_array($ret[$key])) {
$ret[$key] = array();
}
// differently from filter_input(),
// this function prevent unexpected multi-demensional array
if ($flag_match($options, FILTER_FORCE_ARRAY)) {
// eliminate arrays
$ret[$key] = array_filter($ret[$key], $is_not_array);
} else {
// change arrays into false
$ret[$key] = array_map($filter_array, $ret[$key]);
}
}
}
}
if (!$recursive) {
/* only for first loop */
$recursive_static = false;
}
return $ret;
}
Usage:
$_POST['foo']['bar']['validated_float'] = '1,234.213';
$_POST['foo']['forced_1d_array'] = array('a', array('b'), 'c');
$_POST['foo']['required_1d_array'] = array('a', array('b'), 'c');
$_POST['foo']['required_scalar'] = array('a', array('b'), 'c');
When inputs are like this,
var_dump(filter_input_array_recursive(INPUT_POST, array(
'foo' => array(
'bar' => array(
'validated_float' => new FilterObject(
FILTER_VALIDATE_FLOAT,
array(
'options' => array(
'min_range' => 1234,
'max_range' => 1235,
),
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
)
),
),
'forced_1d_array' => new FilterObject(FILTER_DEFAULT, FILTER_FORCE_ARRAY),
'required_1d_array' => new FilterObject(FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
'required_scalar' => new FilterObject,
),
)));
this snippet will output...
array(1) {
["foo"]=>
array(4) {
["bar"]=>
array(1) {
["validated_float"]=>
float(1234.213)
}
["forced_1d_array"]=>
array(2) {
[0]=>
string(1) "a"
[2]=>
string(1) "c"
}
["required_1d_array"]=>
array(3) {
[0]=>
string(1) "a"
[1]=>
bool(false)
[2]=>
string(1) "c"
}
["required_scalar"]=>
bool(false)
}
}
I use this to check if an object has properties,
function objectHasProperty($input){
return (is_object($input) && (count(get_object_vars($input)) > 0)) ? true : false;
}
But then I want to check further to make sure all properties have values, for instance,
stdClass Object
(
[package] =>
[structure] =>
[app] =>
[style] =>
[js] =>
)
Then I want to return false if all the properties have empty values. Is it possible? Any hint and ideas?
There are several ways of doing this, all the way up to using PHP's reflection API, but to simply check if all public properties of an object are empty, you could do this:
$properties = array_filter(get_object_vars($object));
return !empty($properties);
(The temporary variable $properties is required because you're using PHP 5.4.)
For deep inspection and a more advanced handling I'd go for something like the following which is easily extended. Consider it a follow up answer to dev-null-dweller's answer (which is perfectly valid and a great solution).
/**
* Deep inspection of <var>$input</var> object.
*
* #param mixed $input
* The variable to inspect.
* #param int $visibility [optional]
* The visibility of the properties that should be inspected, defaults to <code>ReflectionProperty::IS_PUBLIC</code>.
* #return boolean
* <code>FALSE</code> if <var>$input</var> was no object or if any property of the object has a value other than:
* <code>NULL</code>, <code>""</code>, or <code>[]</code>.
*/
function object_has_properties($input, $visibility = ReflectionProperty::IS_PUBLIC) {
set_error_handler(function(){}, E_WARNING);
if (is_object($input)) {
$properties = (new ReflectionClass($input))->getProperties($visibility);
$c = count($properties);
for ($i = 0; $i < $c; ++$i) {
$properties[$i]->setAccessible(true);
// Might trigger a warning!
$value = $properties[$i]->getValue($input);
if (isset($value) && $value !== "" && $value !== []) {
restore_error_handler();
return true;
}
}
}
restore_error_handler();
return false;
}
// Some tests
// The bad boy that emits a E_WARNING
var_dump(object_has_properties(new \mysqli())); // boolean(true)
var_dump(object_has_properties(new \stdClass())); // boolean(false)
var_dump(object_has_properties("")); // boolean(false)
class Foo {
public $prop1;
public $prop2;
}
var_dump(object_has_properties(new Foo())); // boolean(false)
$foo = new Foo();
$foo->prop1 = "bar";
var_dump(object_has_properties($foo)); // boolean(true)
Depending on what do you consider as 'empty value' you may have adjust the callback function that removes unwanted values:
function objectHasProperty($input){
return (
is_object($input)
&&
array_filter(
get_object_vars($input),
function($val){
// remove empty strings and null values
return (is_string($val) && strlen($val))||($val!==null);
}
)
) ? true : false;
}
$y = new stdClass;
$y->zero = 0;
$n = new stdClass;
$n->notset = null;
var_dump(objectHasProperty($y),objectHasProperty($n));//true,false
So I'd like to use the call_user_func to pass data to an optional parameter of a function.
Here's the example of a code, the optional parameter $data represents a functional called data that was declared in another file. I just want it to be called by using call_user_func that will set the parameter with the function's name and call it within the createtable function, but doesn't seem to work.
I got the example from the PHP Manual, but createTable contains many parameters. How can I make call_user_func only assign the string data to the optional parameter $data set to NULL as default?
function createTable($database, $table, $patch,$data = NULL)
{
echo "INFO: Adding '$table'in database '$database'.\n";
$sql = "SHOW TABLES FROM $database WHERE Tables_in_$database='$table';";
$result = mysql_query($sql);
$result_count = mysql_num_rows($result);
if ( $result_count != 1 ) {
echo "ERROR: Can not find table '$table' in database '$database'.\n";
$result = mysql_query($patch);
if ( false === $result ) {
echo "ERROR: Adding Table '$table' in database '$database' ... Failed\n";
return false;
}
else {
echo "INFO: Adding Table '$table'in database '$database' ... Success\n";
// using the optional parameter here
$data();
return true;
}
} else {
if ( $result_count == 1 ) {
echo "ERROR: Table '$table'already in database '$database'.\n";
return false;
}
}
}
// Now I'm passing value to the optional parameter $ data that is NULL as default.
call_user_func('createTable', "data");
Even with call_user_func you have to pass all the parameters.
Anyway, call_user_func is intended for use when the name of the function isn't necessarily known up front. For instance, you might have several functions and a variable, and the variable contains the name of the function to call.
Personally I think it's on par with eval and variable variables: A horrible idea. After all, if you have $foo = "function_name"; then you can call $foo() and it will call function_name.
Anyway, back to the point, just call it as a normal function and give it the parameters it needs. Pass null if you have to.
you must pass value like this
call_user_func('createTable', $database, $table, $patch,$data);
or this for call from class
call_user_func(array(&$objectName->{$anotherObject},$functionName), $arg1, $arg2, $arg2);
or you can use this can get arg as array
call_user_func_array("createTable", array("one", "two"));
or this for call from class can get arg as array
call_user_func_array(array($foo, "bar"), array("three", "four"));
or This can help you too it not need to pass all args
function __named($method, array $args = array())
{
$reflection = new ReflectionFunction( $method);
$pass = array();
foreach($reflection->getParameters() as $param)
{
/* #var $param ReflectionParameter */
if(isset($args[$param->getName()]))
{
$pass[] = $args[$param->getName()];
}
else
{
try{
$pass[] = $param->getDefaultValue();
}catch(Exception $e){
$pass[] = NULL;
}
}
}
return $reflection->invokeArgs( $pass);
}
I hope It Work
sample:
__named('createTable', array('data' => 'value'));
and it is for use in class
public function __named($method, array $args = array())
{
$reflection = new ReflectionMethod($this, $method);
$pass = array();
foreach($reflection->getParameters() as $param)
{
/* #var $param ReflectionParameter */
if(isset($args[$param->getName()]))
{
$pass[] = $args[$param->getName()];
}
else
{
try{
$pass[] = $param->getDefaultValue();
}catch(Exception $e){
$pass[] = NULL;
}
}
}
return $reflection->invokeArgs($this,$pass);
}
if you Don't set any value __named Put Null instead of Unset Value
It seems you just want to pass the last param, and not worry about the 1st three. I don't think call_user_func is the right tool here at all.
Why not just make a function that calls your function?
function call_createTable($data){
$database = '...';
$table = '...';
$patch = '...';
return createTable($database, $table, $patch, $data);
}
Then just simply call it like this: call_createTable("data");.
$p = (isset($_REQUEST["p"])?$_REQUEST["p"]:"");
This is the common line I usually use in my php code. I always assume is there a better(small and faster) way to write the same ?
Create your own function :
function getIfSet(&$value, $default = null)
{
return isset($value) ? $value : $default;
}
$p = getIfSet($_REQUEST['p']);
There's no other clean solution.
EDIT:
PHP 7 adds a null coalescing operator ("??")
$p = $_REQUEST["p"] ?? '';
https://www.php.net/manual/en/migration70.new-features.php
ORIGINAL:
if you want something shorter, and are content with an empty (string) default value, the following works:
$p = #$_REQUEST['p'];
# is the error-suppression operator and will keep the expression from giving a warning if the value is not set.
http://www.php.net/manual/en/language.operators.errorcontrol.php
How more shorter do you want it?
Of course, if you are using this every time you access a request value, you should create a function somewhere and then use that:
function reqVal( $val, $default = "", $no_sql = true )
{
$var = isset( $_REQUEST[$val] ) ? $_REQUEST[$val] : $default;
$var = $no_sql ? nosql( $var ) : $var;
return $var;
}
function getVal( $val, $default = "", $no_sql = true )
{
$var = isset( $_GET[$val] ) ? $_GET[$val] : $default;
$var = $no_sql ? nosql( $var ) : $var;
return $var;
}
function postVal( $val, $default = "", $no_sql = true )
{
$var = isset( $_POST[$val] ) ? $_POST[$val] : $default;
$var = $no_sql ? nosql( $var ) : $var;
return $var;
}
Now add the sql incjection check:
function nosql( $var )
{
if ( is_array( $var ) ) {
foreach ( $var as $key => $elem ) $var[$key] = nosql( $elem );
} else if ( $var === null ) {
return null;
} else {
if ( get_magic_quotes_gpc() ) $var = stripslashes( $var );
$var = mysql_real_escape_string( $var );
}
return $var;
}
And access it always simple like this:
$p = reqVal( 'p', 0 );
$p = getVal( 'p', 'something', false );
$p = postVal( 'p' ); // or just forget the 2nd and 3rd parameter
The answers that wrap your existing code in a function are good - they do indeed tidy up the code if you've got a bunch of them.
However the better solution is to sanitize your entire request array based on a set of expected values before you start.
For example:
function sanitiseRequest() {
$expected = array(
'p' => '',
'id' => 0,
//etc
);
//throw away any input that wasn't expected...
foreach($_REQUEST as $key=>$value) {
if(!isset($expected[$key]) { unset $_REQUEST[$key]; }
}
//and for any expected values that weren't passed, set them to the defaults.
foreach($expected as $key=>$defvalue) {
if(!isset($_REQUEST[$key]) { $_REQUEST[$key] = $defvalue; }
}
}
Then simply add a call to this function at the start of the code, and you won't need to worry about doing isset($_REQUEST[..]) anywhere else in your code.
This concept can be expanded to force the incoming arguments to be the correct data type as well, or any other data cleansing you may want to do. This can give you complete confidence that the incoming data is populated as you expect.
Hope that helps.
I usually take advantage of the fact that PHP is loosely typed and simply do:
$p = (string) $_REQUEST['p'];
This way, even if $_REQUEST['p'] is not set, an empty string still gets stored into $p. Keep in mind that this only works if your error handler ignores notices, as accessing an unset key will trigger an E_NOTICE along the lines of "undefined index".
This is indeed so common, that i wonder there is no native way of doing it in PHP. Most developers write their own function to read safely from an array.
/**
* Gets the value associated with the specified key from an array.
* #param array $array The array to search for the key.
* #param mixed $key The key of the value to get.
* #param mixed $default The default value to return, if the
* specified key does not exist.
* #return mixed Value that is associated with the specified
* key, or the default value, if no such key exists.
*/
function getValueFromArray($array, $key, $default = null)
{
$containsKey = isset($array[$key]);
if ($containsKey)
return $array[$key];
else
return $default;
}
/**
* Gets the value associated with the specified key from an array.
* #param array $array The array to search for the key.
* #param mixed $key The key of the value to get.
* #param mixed $value Retrieves the found value, or is set to null
* if the key could not be found.
* #return bool Returns true if the key could be found, otherwise false.
*/
public function tryGetValueFromArray($array, $key, &$value)
{
$containsKey = isset($array[$key]);
if ($containsKey)
$value = $array[$key];
else
$value = null;
return $containsKey;
}
You can find many examples of different solutions here http://php.net/manual/en/function.isset.php in the User Contributed Notes section.
Try this:
function get_if_set( $varname, $parent=null ) {
if ( !is_array( $parent ) && !is_object($parent) ) {
$parent = $GLOBALS;
}
return array_key_exists( $varname, $parent ) ? $parent[$varname] : null;
}
This one works well for me.
And you don't have to write the name twice.
It won't change the var if it is already set. So it is safe to use for quick n dirty conversion of old application using register_globals.
function getIfSet($key, $default = null)
{
global $$key;
if(!isset($$key)){
if(isset($_REQUEST[$key])){
$$key=$_REQUEST[$key];
}else{
if(!is_null($default)){
$$key = $default;
}
}
}
}
function getIfSetArray($list){
foreach($list as $item){
getIfSet($item);
}
}
getIfSet('varname');
getIfSetArray(['varname_1','varname_2']);
echo $varname;
echo $varname_1;
e.g:
$arr = array('k1'=>1,'k2'=>2,'k3'=>3);
If i want get $arr['k4'] (unexpect index),there is a notice:
Notice: undefined index......
so,can i set a dufalut value for array,like as ruby's hash:
h = {'k1'=>1,'k2'=>2,'k3'=>3}
h.default = 'default'
puts h['k4']
then,i'll get 'default';
Just do some sort of check to see if it exists:
isset($arr['k4'])?$arr['k4']:'default';
Or make a function for it:
function get_key($key, $arr){
return isset($arr[$key])?$arr[$key]:'default';
}
//to use it:
get_key('k4', $arr);
#Neal's answer is good for generic usage, but if you have a predefined set of keys that need to be defaulted, you can always merge the array with a default:
$arr = $arr + array('k1' => null, 'k2' => null, 'k3' => null, 'k4' => null);
that way, if $arr defines any of those keys, it will take precidence. But the default values will be there if not. This has the benefit of making option arrays easy since you can define different defaults for each key.
Edit Or if you want ruby like support, just extend arrayobject to do it for you:
class DefaultingArrayObject extends ArrayObject {
public $default = null;
public function __construct(array $array, $default = null) {
parent::__construct($array);
$this->default = $default;
}
public function offsetGet($key) {
if ($this->offsetExists($key)) {
return parent::offsetGet($key);
} else {
return $this->default;
}
}
}
Usage:
$array = new DefaultingArrayObject($array);
$array->default = 'default';
echo $array['k4']; // 'default'