如何避免 isset()和 void()

我有几个旧的应用程序,抛出了很多“ xyz 是未定义的”和“未定义的偏移量”消息时,在 E _ NOTICE 错误级别上运行,因为变量的存在没有显式检查使用 isset()和配偶。

我正在考虑通过它们使它们与 E _ NOTICE 兼容,因为关于丢失变量或偏移量的通知可能是救命稻草,可能会获得一些小的性能改进,总的来说这是更干净的方式。

然而,我不喜欢什么造成数以百计的 isset() empty()array_key_exists()的做我的代码。它变得臃肿,变得不那么可读,没有获得任何价值或意义方面的任何东西。

我怎样才能结构我的代码没有过多的变量检查,同时也是 E _ NOTICE 兼容?

25453 次浏览

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.

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 ''0 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 ''1/false flag, then use ''1 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.

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'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.

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 ABC1 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? ;)

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 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 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";
}

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) { /* ... */ }

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"];
}
?>

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.