PHP - way to validate many parameters of a function call quickly - php

I have identified an issue that always produce bugs in my application. It is that PHP is generally quite lax about passing null or empty variables to a function. For example
function do_this($a, $b, $c) {
....
}
One error-prone call could be
do_this($request['a'], $request['b'], $request['c']);
As PHP just silently passes null if any of the keys is not found. I have tried use doing error checking inside the function, as below:
function do_this($a, $b, $c) {
if (empty($a)) throw new Exception('$a is not defined!');
if (empty($b)) throw new Exception('$b is not defined!');
if (empty($c)) throw new Exception('$c is not defined!');
}
It's a headache when the function takes many parameters.
I could use E_STRICT, but I am using many third-party plugins and working off Wordpress, so I'll be getting warnings from other packages.
What's a good way to validate many parameters of a function call in PHP?

First off. you shouldn't be passing values without checking your indices like that. That's very very bad.
As for your question, why not just define default values and then loop through your variables to print an error?
function do_this($a = null, $b = null, $c = null) {
$numargs = func_num_args();
$arg_list = func_get_args();
for ($i = 0; $i < $numargs; $i++) {
if ($arg_list[$i] == null) {
//fail
}
}
}
You don't need all that code of course, just an example.
See here for more info : http://www.php.net/manual/en/function.func-get-args.php

You don't need to use empty inside your function, since the variables are certainly set. if (!$a) will do.
It's always possible to pass incorrect values, regardless of whether this happens due to variables not being set in the scope calling the function or just because the values are bad. At some point you need to check anyway. See PHP function param type best practices.
You must check in the scope calling the function for non-existing variables, not within the function. I.e.:
if (isset($foo, $bar, $baz)) {
do_this($foo, $bar, $baz);
}
You must always do this if there's a legitimate chance the variables may not exist, this is not specific to passing them as parameters to functions.

Have a look at args module from NSPL. It makes argument validation an easy process. To check all arguments in the function from your example you just have to add only one line of code:
function do_this($a, $b, $c)
{
expectsAll([nonEmpty, int], [$a, $b, $c]);
// do this...
}
More examples here.

Related

PHP Function to cascade through a flexible number of full/empty values

I'm trying to write a function that will take any number of arguments, and will return the first argument, in order, that is valid (in my case !empty).
I've been able to pretty much get it working how I want, except I'm getting some notices because of undefined variables. See below for examples:
function cascade()
{
if (func_num_args() < 1) return false;
foreach (func_get_args() as &$arg) {
if (!empty($arg)) return $arg;
}
return false;
}
You can see that I've tried to declare that each $arg of the foreach is passed by reference, but that doesn't seem to have done the trick.
To elaborate on how I plan to use this function, see below:
$a = 'a';
$c = 'c';
echo cascade($z, $b, $a, $c);
Since $z and $b are undefined, the first non-empty variable in the list is $a so the output is a as expected. However, you then get the undefined variable notices, which I wish to get rid of.
I realise I can just say echo #cascade($z, $b, $a, $c); which would suppress the errors, but I want to know if there is a way around this issue, so that the reference can be passed somehow. Any thoughts?
Edit:
To further highlight what I'm trying to acheive, see the following function that DOES work without throwing errors, even when passed an undefined variable:
// returns default value if input variable is not set
function ifset(&$var, $default = false) {
return isset($var) ? $var : $default;
}
With this function, if param 1 is not set, then the default value in param 2 is returned. Either way, no error is thrown.
What I am trying to achieve is the same result, but with ANY number of arguments, as this function is limited to 1, unless I nest them (gets messy).
A real life example:
This is WHY I want this function and how I would use it in a real life scenario:
<input type="text" name="customerName" value="<?= cascade($order->fullName, $currentUser->fullName, 'Anonymous') ?>">
So if we have an order in the making, and there is a name available from that, we use that, if that info hasn't been saved yet, we use the logged in user's name as the default value, if no one is logged in, we use 'Anonymous'.
Not exactly what I would do in real life, but perhaps it highlights example usage?
For those who are suggesting defining the variables to mitigate the errors, the who point of this function is to work though a chain of values, giving priority to the ones that come first, then moving to the next if that is 'empty' and so on, until eventually a FALSE default value is returned if all are empty.
The Notice you're seeing is triggered at the function call, not inside the function itself. Therefore, there is nothing you can do inside the function to solve the problem. However, there are a number of ways of solving this problem depending on how your variables are prepared before the function call.
Solution #1: Define the variables before the function call.
for instance:
$z = ''; OR $z = null;
or any falsy value like : null, "", 0, "0", 0.0, [], ..., your function will still work as expected and you won't see the notice.
Solution #2: Test for validity before the function call.
if( !isset($z) ){ $z = ''; }
echo cascade($z);
Solution #3: Test for validity as part of the function call.
This is the same thing as solution #2 but a bit more elegant. Use the Ternary function to pass the variable value or an empty string depending on whether or not the variable is set.
echo cascade(
isset($z)?$z:'',
isset($b)?$b:'',
isset($a)?$a:'',
isset($c)?$c:''
);
Solution #4: If using PHP 7 or above, you can use the new Null Coalescing Operator. This is the same thing as solution #3, but more elegant still.
echo cascade($z ?? '', $b ?? '', $a ?? '', $c ?? '');
If you want a clean alternative to your function, you can try this:
function cascade()
{
$args = func_get_args();
while (!($arg = array_shift($args)));
return $arg ? $arg : false;
}
BIG THANKYOU to JBH for Editing significantly this answer.
The undefined variable notice is not from the code in function but it is from the function call cascade($z, $b, $a, $c)
You are passing undefined variables ($z and $b) as arguments to the function. Since they are not defined any where in the code, you are getting notice.
To get rid of the the notices, define the variables before passing them as arguments.
$a = 'a';
$c = 'c';
$z = '';
$b = '';
echo cascade($z, $b, $a, $c);
OR
echo cascade('', '', $a, $c);
Don't know if this is "good enough".
Create an array of the variables and use array filter to remove the null values.
Use array values to reset the keys.
Now $arr[0] is the first non empty item.
https://3v4l.org/PlmrS
$a = 'a';
$c = 'c';
$arr =array_values(array_filter([$z, $b, $a, $c]));
Var_dump($arr);
Thank you for all your kind answers, but unfortunately none of them solved my problem. Perhaps I didn't explain it well enough, or I over-complicated it with too much information confusing the issue, but anyway I've been able to find the answer.
The following function does exactly what I wanted. It uses the variadic syntax (php 5.6 and over) which allows a variable number of arguments, all of which are passed by reference.
function cascade(&...$args)
{
if (count($args) < 1) return false;
foreach ($args as &$arg) {
if (!empty($arg)) return $arg;
}
return false;
}

initialize a PHP variable if not set [duplicate]

I have several older applications that throw a lot of "xyz is undefined" and "undefined offset" messages when running on the E_NOTICE error level, because the existence of variables is not explicitly checked using isset() and consorts.
I am considering working through them to make them E_NOTICE compatible, as notices about missing variables or offsets can be lifesavers, there may be some minor performance improvements to be gained, and it's overall the cleaner way.
However, I don't like what inflicting hundreds of isset() empty() and array_key_exists() s does to my code. It gets bloated, becomes less readable, without gaining anything in terms of value or meaning.
How can I structure my code without an excess of variable checks, while also being E_NOTICE compatible?
For those interested, I have expanded this topic into a small article, which provides the below information in a somewhat better structured form: The Definitive Guide To PHP's isset And empty
IMHO you should think about not just making the app "E_NOTICE compatible", but restructuring the whole thing. Having hundreds of points in your code that regularly try to use non-existent variables sounds like a rather badly structured program. Trying to access non-existent variables should never ever happen, other languages balk at this at compile time. The fact that PHP allows you to do it doesn't mean you should.
These warnings are there to help you, not to annoy you. If you get a warning "You're trying to work with something that doesn't exist!", your reaction should be "Oops, my bad, let me fix that ASAP." How else are you going to tell the difference between "variables that work just fine undefined" and honestly wrong code that may lead to serious errors? This is also the reason why you always, always, develop with error reporting turned to 11 and keep plugging away at your code until not a single NOTICE is issued. Turning error reporting off is for production environments only, to avoid information leakage and provide a better user experience even in the face of buggy code.
To elaborate:
You will always need isset or empty somewhere in your code, the only way to reduce their occurrence is to initialize your variables properly. Depending on the situation there are different ways to do that:
Function arguments:
function foo ($bar, $baz = null) { ... }
There's no need to check whether $bar or $baz are set inside the function because you just set them, all you need to worry about is if their value evaluates to true or false (or whatever else).
Regular variables anywhere:
$foo = null;
$bar = $baz = 'default value';
Initialize your variables at the top of a block of code in which you're going to use them. This solves the !isset problem, ensures that your variables always have a known default value, gives the reader an idea of what the following code will work on and thereby also serves as a sort of self-documentation.
Arrays:
$defaults = array('foo' => false, 'bar' => true, 'baz' => 'default value');
$values = array_merge($defaults, $incoming_array);
The same thing as above, you're initializing the array with default values and overwrite them with actual values.
In the remaining cases, let's say a template where you're outputting values that may or may not be set by a controller, you'll just have to check:
<table>
<?php if (!empty($foo) && is_array($foo)) : ?>
<?php foreach ($foo as $bar) : ?>
<tr>...</tr>
<?php endforeach; ?>
<?php else : ?>
<tr><td>No Foo!</td></tr>
<?php endif; ?>
</table>
If you find yourself regularly using array_key_exists, you should evaluate what you're using it for. The only time it makes a difference is here:
$array = array('key' => null);
isset($array['key']); // false
array_key_exists('key', $array); // true
As stated above though, if you're properly initializing your variables, you don't need to check if the key exists or not, because you know it does. If you're getting the array from an external source, the value will most likely not be null but '', 0, '0', false or something like it, i.e. a value you can evaluate with isset or empty, depending on your intent. If you regularly set an array key to null and want it to mean anything but false, i.e. if in the above example the differing results of isset and array_key_exists make a difference to your program logic, you should ask yourself why. The mere existence of a variable shouldn't be important, only its value should be of consequence. If the key is a true/false flag, then use true or false, not null. The only exception to this would be 3rd party libraries that want null to mean something, but since null is so hard to detect in PHP I have yet to find any library that does this.
Just write a function for that. Something like:
function get_string($array, $index, $default = null) {
if (isset($array[$index]) && strlen($value = trim($array[$index])) > 0) {
return get_magic_quotes_gpc() ? stripslashes($value) : $value;
} else {
return $default;
}
}
which you can use as
$username = get_string($_POST, 'username');
Do the same for trivial stuff like get_number(), get_boolean(), get_array() and so on.
I believe one of the best ways of coping with this problem is by accessing values of GET and POST (COOKIE, SESSION, etc.) arrays through a class.
Create a class for each of those arrays and declare __get and __set methods (overloading). __get accepts one argument which will be the name of a value. This method should check this value in the corresponding global array, either using isset() or empty() and return the value if it exists or null (or some other default value) otherwise.
After that you can confidently access array values in this manner: $POST->username and do any validation if needed without using any isset()s or empty()s. If username does not exist in the corresponding global array then null will be returned, so no warnings or notices will be generated.
I don't mind using the array_key_exists() function. In fact, I prefer using this specific function rather than relying on hack functions which may change their behavior in the future like empty and isset (strikedthrough to avoid susceptibilities).
I do however, use a simple function that comes handy in this, and some other situations in dealing with array indexes:
function Value($array, $key, $default = false)
{
if (is_array($array) === true)
{
settype($key, 'array');
foreach ($key as $value)
{
if (array_key_exists($value, $array) === false)
{
return $default;
}
$array = $array[$value];
}
return $array;
}
return $default;
}
Let's say you've the following arrays:
$arr1 = array
(
'xyz' => 'value'
);
$arr2 = array
(
'x' => array
(
'y' => array
(
'z' => 'value',
),
),
);
How do you get the "value" out of the arrays? Simple:
Value($arr1, 'xyz', 'returns this if the index does not exist');
Value($arr2, array('x', 'y', 'z'), 'returns this if the index does not exist');
We already have uni and multi-dimensional arrays covered, what else can we possibly do?
Take the following piece of code for instance:
$url = 'https://stackoverflow.com/questions/1960509';
$domain = parse_url($url);
if (is_array($domain) === true)
{
if (array_key_exists('host', $domain) === true)
{
$domain = $domain['host'];
}
else
{
$domain = 'N/A';
}
}
else
{
$domain = 'N/A';
}
Pretty boring isn't it? Here is another approach using the Value() function:
$url = 'https://stackoverflow.com/questions/1960509';
$domain = Value(parse_url($url), 'host', 'N/A');
As an additional example, take the RealIP() function for a test:
$ip = Value($_SERVER, 'HTTP_CLIENT_IP', Value($_SERVER, 'HTTP_X_FORWARDED_FOR', Value($_SERVER, 'REMOTE_ADDR')));
Neat, huh? ;)
Welcome to null coalescing operator (PHP >= 7.0.1):
$field = $_GET['field'] ?? null;
PHP says:
The null coalescing operator (??) has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with isset(). It returns its first operand if it exists and is not NULL; otherwise it returns its second operand.
I'm here with you. But PHP designers has made a lot more worse mistakes than that. Short of defining a custom function for any value reading, there isn't any way around it.
I use these functions
function load(&$var) { return isset($var) ? $var : null; }
function POST($var) { return isset($_POST[$var]) ? $_POST[$var] : null; }
Examples
$y = load($x); // null, no notice
// this attitude is both readable and comfortable
if($login=POST("login") and $pass=POST("pass")) { // really =, not ==
// executes only if both login and pass were in POST
// stored in $login and $pass variables
$authorized = $login=="root" && md5($pass)=="f65b2a087755c68586568531ad8288b4";
}
Make a function which returns false if not set, and, if specified, false if empty. If valid it returns the variable. You can add more options as seen in the code below:
<?php
function isset_globals($method, $name, $option = "") {
if (isset($method[$name])) { // Check if such a variable
if ($option === "empty" && empty($method[$name])) { return false; } // Check if empty
if ($option === "stringLength" && strlen($method[$name])) { return strlen($method[$name]); } // Check length of string -- used when checking length of textareas
return ($method[$name]);
} else { return false; }
}
if (!isset_globals("$_post", "input_name", "empty")) {
echo "invalid";
} else {
/* You are safe to access the variable without worrying about errors! */
echo "you uploaded: " . $_POST["input_name"];
}
?>
Software does not magically run by the grace of god. If you are expecting something that is missing, you need to properly handle it.
If you ignore it, you are probably creating security holes in your applications. In static languages accessing a non-defined variable it is just not possible. It won't simply compile or crash your application if it's null.
Furthermore, it makes your application unmaintainable, and you are going to go mad when unexpected things happen. Language strictness is a must and PHP, by design, is wrong in so many aspects. It will make you a bad programmer if you are not aware.
I'm not sure what your definition of readability is, but proper use of empty(), isset() and try/throw/catch blocks, is pretty important to the whole process.
If your E_NOTICE is coming from $_GET or $_POST, then they should be checked against empty() right along with all the other security checks that that data should have to pass.
If it's coming from external feeds or libraries, it should be wrapped in try/catch.
If it's coming from the database, $db_num_rows() or its equivalent should be checked.
If it's coming from internal variables, they should be properly initialized. Often, these types of notices come from assigning a new variable to the return of a function that returns FALSE on a failure. Those should be wrapped in a test that, in the event of a failure, can either assign the variable an acceptable default value that the code can handle, or throwing an exception that the code can handle.
These things make the code longer, add extra blocks, and add extra tests, but I disagree with you in that I think they most definitely add extra value.
What about using the # operator?
For example:
if(#$foo) { /* Do something */ }
You may say this is bad because you have no control of what happens "inside" $foo (if it was a function call that contains a PHP error for example), but if you only use this technique for variables, this is equivalent to:
if(isset($foo) && $foo) { /* ... */ }

Using reference to nonexistent value sets variable to NULL?

When passing a non-existent value by reference, PHP creates the value and sets it to NULL. I noticed it when memory increases were occurring while checking empty values in some functions. Take the following function:
function v(&$v,$d=NULL){return isset($v)?$v:$d;}
$bar = v($foo, $default);
This would be shorthand for:
if(isset($foo))
{
$bar = $foo;
}
else
{
$bar = $default;
}
However, when passing non-existent variables PHP creates them. In the case of variables - they are removed as soon as the method/function ends - but for checking super global arrays like $_GET or $_POST the array element is never removed causing extra memory usage.
$request_with = v($_SERVER['HTTP_X_REQUESTED_WITH']);
Can anyone explain why this happens and if it is a PHP todo fix or a feature for some other crazy use of values?
XeonCross' function v is a shorthand for the often used:
$val= isset($arr['elm']) ? $arr['elm'] : 'default'
to avoid the dreaded 'Undefined index: elm' notice. A nice helper function would be:
function ifset(&$v1, $v2 = null) {
return isset($v1) ? $v1 : $v2;
}
as Xeoncross suggested, so you could write the much nicer
$val = ifset($arr['elm'],'default')
however, this has a lot of interesting (?) quirks in our beloved "language" that we call PHP:
inside the function ifset, $v1 seems UNSET, so it correctly returns the value $v2 and you might conclude that ifset works ok. But afterwards $arr['elm'] is silently set to NULL. So consider the following:
function wtf(&$v) {
if (isset($v))
echo "It is set";
else
echo "It is NOT set";
}
$p=[];
wtf($p['notexist']); => It is NOT set
$p; => [ 'notexist' => NULL ]
But this is another delusion, as the isset() function returns false for NULL values as well:
$x=NULL;
isset($x) => false... huh??
Did we expect this? well.. it is in the documentation, so this is by design as well. Welcome to the wonderful world of php.
The reason you have the memory leak, is because you're telling it to.
When you ask for a reference parameter, PHP will provide you with one. When you are calling a function with an unset variable, PHP will set the variable and then pass the reference to that new variable. When you call it with a superglobal, it creates the missing index. That's because you told it to.
However, I must ask why specifically do you need variable references? 99.9% of the time you don't really need them. I suspect that it'll work just fine to do:
function v($v, $d = null) { return isset($v) ? $v : $d; }
Or, if you really must use references (which you can't get around your original problem with), you should also return a reference:
function &v(&$v, $d = null) {
if (isset($v)) {
return $v;
}
return $d;
}
Otherwise it's pointless to take a reference and not return one...

How to avoid isset() and empty()

I have several older applications that throw a lot of "xyz is undefined" and "undefined offset" messages when running on the E_NOTICE error level, because the existence of variables is not explicitly checked using isset() and consorts.
I am considering working through them to make them E_NOTICE compatible, as notices about missing variables or offsets can be lifesavers, there may be some minor performance improvements to be gained, and it's overall the cleaner way.
However, I don't like what inflicting hundreds of isset() empty() and array_key_exists() s does to my code. It gets bloated, becomes less readable, without gaining anything in terms of value or meaning.
How can I structure my code without an excess of variable checks, while also being E_NOTICE compatible?
For those interested, I have expanded this topic into a small article, which provides the below information in a somewhat better structured form: The Definitive Guide To PHP's isset And empty
IMHO you should think about not just making the app "E_NOTICE compatible", but restructuring the whole thing. Having hundreds of points in your code that regularly try to use non-existent variables sounds like a rather badly structured program. Trying to access non-existent variables should never ever happen, other languages balk at this at compile time. The fact that PHP allows you to do it doesn't mean you should.
These warnings are there to help you, not to annoy you. If you get a warning "You're trying to work with something that doesn't exist!", your reaction should be "Oops, my bad, let me fix that ASAP." How else are you going to tell the difference between "variables that work just fine undefined" and honestly wrong code that may lead to serious errors? This is also the reason why you always, always, develop with error reporting turned to 11 and keep plugging away at your code until not a single NOTICE is issued. Turning error reporting off is for production environments only, to avoid information leakage and provide a better user experience even in the face of buggy code.
To elaborate:
You will always need isset or empty somewhere in your code, the only way to reduce their occurrence is to initialize your variables properly. Depending on the situation there are different ways to do that:
Function arguments:
function foo ($bar, $baz = null) { ... }
There's no need to check whether $bar or $baz are set inside the function because you just set them, all you need to worry about is if their value evaluates to true or false (or whatever else).
Regular variables anywhere:
$foo = null;
$bar = $baz = 'default value';
Initialize your variables at the top of a block of code in which you're going to use them. This solves the !isset problem, ensures that your variables always have a known default value, gives the reader an idea of what the following code will work on and thereby also serves as a sort of self-documentation.
Arrays:
$defaults = array('foo' => false, 'bar' => true, 'baz' => 'default value');
$values = array_merge($defaults, $incoming_array);
The same thing as above, you're initializing the array with default values and overwrite them with actual values.
In the remaining cases, let's say a template where you're outputting values that may or may not be set by a controller, you'll just have to check:
<table>
<?php if (!empty($foo) && is_array($foo)) : ?>
<?php foreach ($foo as $bar) : ?>
<tr>...</tr>
<?php endforeach; ?>
<?php else : ?>
<tr><td>No Foo!</td></tr>
<?php endif; ?>
</table>
If you find yourself regularly using array_key_exists, you should evaluate what you're using it for. The only time it makes a difference is here:
$array = array('key' => null);
isset($array['key']); // false
array_key_exists('key', $array); // true
As stated above though, if you're properly initializing your variables, you don't need to check if the key exists or not, because you know it does. If you're getting the array from an external source, the value will most likely not be null but '', 0, '0', false or something like it, i.e. a value you can evaluate with isset or empty, depending on your intent. If you regularly set an array key to null and want it to mean anything but false, i.e. if in the above example the differing results of isset and array_key_exists make a difference to your program logic, you should ask yourself why. The mere existence of a variable shouldn't be important, only its value should be of consequence. If the key is a true/false flag, then use true or false, not null. The only exception to this would be 3rd party libraries that want null to mean something, but since null is so hard to detect in PHP I have yet to find any library that does this.
Just write a function for that. Something like:
function get_string($array, $index, $default = null) {
if (isset($array[$index]) && strlen($value = trim($array[$index])) > 0) {
return get_magic_quotes_gpc() ? stripslashes($value) : $value;
} else {
return $default;
}
}
which you can use as
$username = get_string($_POST, 'username');
Do the same for trivial stuff like get_number(), get_boolean(), get_array() and so on.
I believe one of the best ways of coping with this problem is by accessing values of GET and POST (COOKIE, SESSION, etc.) arrays through a class.
Create a class for each of those arrays and declare __get and __set methods (overloading). __get accepts one argument which will be the name of a value. This method should check this value in the corresponding global array, either using isset() or empty() and return the value if it exists or null (or some other default value) otherwise.
After that you can confidently access array values in this manner: $POST->username and do any validation if needed without using any isset()s or empty()s. If username does not exist in the corresponding global array then null will be returned, so no warnings or notices will be generated.
I don't mind using the array_key_exists() function. In fact, I prefer using this specific function rather than relying on hack functions which may change their behavior in the future like empty and isset (strikedthrough to avoid susceptibilities).
I do however, use a simple function that comes handy in this, and some other situations in dealing with array indexes:
function Value($array, $key, $default = false)
{
if (is_array($array) === true)
{
settype($key, 'array');
foreach ($key as $value)
{
if (array_key_exists($value, $array) === false)
{
return $default;
}
$array = $array[$value];
}
return $array;
}
return $default;
}
Let's say you've the following arrays:
$arr1 = array
(
'xyz' => 'value'
);
$arr2 = array
(
'x' => array
(
'y' => array
(
'z' => 'value',
),
),
);
How do you get the "value" out of the arrays? Simple:
Value($arr1, 'xyz', 'returns this if the index does not exist');
Value($arr2, array('x', 'y', 'z'), 'returns this if the index does not exist');
We already have uni and multi-dimensional arrays covered, what else can we possibly do?
Take the following piece of code for instance:
$url = 'https://stackoverflow.com/questions/1960509';
$domain = parse_url($url);
if (is_array($domain) === true)
{
if (array_key_exists('host', $domain) === true)
{
$domain = $domain['host'];
}
else
{
$domain = 'N/A';
}
}
else
{
$domain = 'N/A';
}
Pretty boring isn't it? Here is another approach using the Value() function:
$url = 'https://stackoverflow.com/questions/1960509';
$domain = Value(parse_url($url), 'host', 'N/A');
As an additional example, take the RealIP() function for a test:
$ip = Value($_SERVER, 'HTTP_CLIENT_IP', Value($_SERVER, 'HTTP_X_FORWARDED_FOR', Value($_SERVER, 'REMOTE_ADDR')));
Neat, huh? ;)
Welcome to null coalescing operator (PHP >= 7.0.1):
$field = $_GET['field'] ?? null;
PHP says:
The null coalescing operator (??) has been added as syntactic sugar for the common case of needing to use a ternary in conjunction with isset(). It returns its first operand if it exists and is not NULL; otherwise it returns its second operand.
I'm here with you. But PHP designers has made a lot more worse mistakes than that. Short of defining a custom function for any value reading, there isn't any way around it.
I use these functions
function load(&$var) { return isset($var) ? $var : null; }
function POST($var) { return isset($_POST[$var]) ? $_POST[$var] : null; }
Examples
$y = load($x); // null, no notice
// this attitude is both readable and comfortable
if($login=POST("login") and $pass=POST("pass")) { // really =, not ==
// executes only if both login and pass were in POST
// stored in $login and $pass variables
$authorized = $login=="root" && md5($pass)=="f65b2a087755c68586568531ad8288b4";
}
Make a function which returns false if not set, and, if specified, false if empty. If valid it returns the variable. You can add more options as seen in the code below:
<?php
function isset_globals($method, $name, $option = "") {
if (isset($method[$name])) { // Check if such a variable
if ($option === "empty" && empty($method[$name])) { return false; } // Check if empty
if ($option === "stringLength" && strlen($method[$name])) { return strlen($method[$name]); } // Check length of string -- used when checking length of textareas
return ($method[$name]);
} else { return false; }
}
if (!isset_globals("$_post", "input_name", "empty")) {
echo "invalid";
} else {
/* You are safe to access the variable without worrying about errors! */
echo "you uploaded: " . $_POST["input_name"];
}
?>
Software does not magically run by the grace of god. If you are expecting something that is missing, you need to properly handle it.
If you ignore it, you are probably creating security holes in your applications. In static languages accessing a non-defined variable it is just not possible. It won't simply compile or crash your application if it's null.
Furthermore, it makes your application unmaintainable, and you are going to go mad when unexpected things happen. Language strictness is a must and PHP, by design, is wrong in so many aspects. It will make you a bad programmer if you are not aware.
I'm not sure what your definition of readability is, but proper use of empty(), isset() and try/throw/catch blocks, is pretty important to the whole process.
If your E_NOTICE is coming from $_GET or $_POST, then they should be checked against empty() right along with all the other security checks that that data should have to pass.
If it's coming from external feeds or libraries, it should be wrapped in try/catch.
If it's coming from the database, $db_num_rows() or its equivalent should be checked.
If it's coming from internal variables, they should be properly initialized. Often, these types of notices come from assigning a new variable to the return of a function that returns FALSE on a failure. Those should be wrapped in a test that, in the event of a failure, can either assign the variable an acceptable default value that the code can handle, or throwing an exception that the code can handle.
These things make the code longer, add extra blocks, and add extra tests, but I disagree with you in that I think they most definitely add extra value.
What about using the # operator?
For example:
if(#$foo) { /* Do something */ }
You may say this is bad because you have no control of what happens "inside" $foo (if it was a function call that contains a PHP error for example), but if you only use this technique for variables, this is equivalent to:
if(isset($foo) && $foo) { /* ... */ }

Do you consider foreach((array)$foo as $bar) a code smell?

Do you consider this a code smell?
foreach((array)$foo as $bar)
{
$bar->doStuff();
}
Should i use that instead?
if (isset($foo) && is_array($foo))
{
foreach($foo as $bar)
{
$bar->doStuff();
}
}
Any other good practices to cover not set variables and assert an array?
They're both code smells. The second one is just evading all the error messages, kind of like turning off the fire alarm before you set your kitchen on fire. Both of those tell you that you have no idea what's in the variable $foo or if it's even been defined in the code above. You need to go back up through the code and find out exactly what's going on with $foo.
If it was my code, $foo would probably be always defined either as an array, or else false to indicate the array isn't needed:
if(do_we_need_an_array())
$foo = function_returning_an_array();
else
$foo = false;
[...snip...]
if($foo)
foreach($foo as $f) { ... }
If you are testing if variables are set, you can initialize them:
if (! $foo or !is_array($foo))
$foo = array();
foreach($foo as $bar)
{
$bar->doStuff();
}
Personally, I would never do the first method and always opt for the second.
If $foo should always be an array, then the second form would be much better if you did some kind of handling for the error case, e.g.:
if (isset($foo) && is_array($foo))
{
foreach($foo as $bar)
{
$bar->doStuff();
}
}
else
{
// This should not happen, exit angrily.
exit("Oh crap, foo isn't an array!");
}
Of course you don't have to just exit the application, but do whatever is appropriate in that case, maybe logging or some alternate logic.
(array)$foo != if (isset($foo) && is_array($foo))
The (array) cast can be useful for casting objects to arrays or scalars to arrays so you can create consistent interfaces to variables that may contain single values or arrays.
(array)$foo == array($foo)
As defined in the PHP Manual for Array Types.
So if you need to always use an array then the first code snippet you presented would be the answer. However type casting rules still apply so you may not get what you want, so look to the manual for more info. Otherwise the second option would prevent accessing unset variables that are not arrays.
As far as a code smell, I would say that checking for unset variables can certainly be avoided, however always knowing that a variable is going to have an array is more often than not, going to creep up. So I would aim to keep code wrapped in is_array($foo) if-then statements to a minimum.
I usually do this to make sure a foreach can handle both scalars and collections:
<?php
foreach (makeSureTraversable($scalarOrCollection) as $val)
{
// Do something.
}
function
makeSureTraversable($anything)
{
if (is_array($anything) || ($anything instanceof Traversable))
{
return $anything;
}
else
{
return array($anything);
}
}
This way I also handle classes that implement Traversable (from the SPL), which means allowing them to be used in foreaches.
if (!isset($foo) && !is_array($foo)) {
throw new InvalidArgumentException('Wrong array passed');
// Or do something to recover lost array
}
foreach($foo as $bar) {
$bar->doStuff();
}
There's quite a few times that you'd like to write a function to take one or more values for a parameter:
function getNamesById($id) { }
In this case, it would make sense that if this function was called with an array of ids, it should probably return an array of names. Similarly, to save the calling code from having to wrap the input in an array and then unwrap the output, if you just pass a scalar, then a scalar should be returned. Consider the likely contents of the function designed to handle both scalar and array parameters:
function getNamesById($id) {
$returnAnArray = is_array($id);
$output = array();
foreach ((array)$id as $theId) {
// perform some logic
$output[] = someFunction($theId);
}
return $returnAnArray ? $output : $output[0];
}
You can see that in this case, casting to an array definitely makes things a lot easier for everyone. As they say, be liberal in what you accept... As long as it is documented that it is expected that a variable could be either, then I see no problem. PHP is a duck-typed language, which has both benefits and drawbacks, but this is one of the benefits, so enjoy it!

Categories