Tuesday 14 August 2018

PHP isset() and multi-dimentional array

** The issue discussed in this post has been fixed since PHP5.4.0, so the below discussion and solution are for PHP 5.3.x or lower. Thanks David for clarifying.
A few weeks ago I covered how to check the existence of an array element in PHP. In the post I explained why isset() is dangerous to check the existence of elements in an array. I also proposed a better solution (the isset()+array_key_exists() method) to do the checking.
Today I’m going to discuss another strange (and dangerous) behavior brought along with isset() function and multi-dimensional arrays.

The problem

Let’s consider this simple code:
1
2
3
4
5
<!--?php $a = array('test'=-->'ABC');
var_dump(isset($a['test']));                       //true
var_dump(isset($a['test']['non_exist']));          //true?!!
var_dump(isset($a['test']['non_exist']) || array_key_exists('non_exist', $a['test'])); //true again?!!!
?>
Surprise, huh? Isset() returns true for a non-exist element!
What even worse is that the previous proposed method (the isset()+ array_key_exists() method) also gives a wrong result! This is because isset() returns true for the non_exist element so the overall OR operation will become “true”. The array_key_exists() is never implemented.

The reason

So why isset() returns true for a non-exist element? I’m not sure the exact reason but I have a guess:
PHP first look at $a[‘test’]. Since $a[‘test’] does exist, isset($a[‘test’]) returns true. Then PHP checks the 2nd dimension: the ‘non_exist’ element. As $a[‘test’] is a string, it is also considered as an array (In PHP, string is a sequential array by type-casting). When checking the sequential array where all index should be integers, the index [‘non_exist’] is **converted** to an integer which equals zero. So actually PHP is checking isset($a[‘test’][0]). Unfortunately $a[‘test’][0] does really exists (with value ‘A’). So the overall result of this checking is “true”.
To verify this guess, let’s run this code:
1
2
3
4
5
6
<!--?php $a = array(1=-->'', 2=>'ABC');
var_dump(isset($a[1])); //true
var_dump(isset($a[1]['t'])); //false => $a[1] is empty string, $a[1][0] doesn't exist
var_dump(isset($a[2])); //true
var_dump(isset($a[2]['t'])); //true => $a[2] is 'abc', so $a[2][0] exists and equals 'A'.
?>
The result has shown that my guess is pretty reasonable.

The solution

You say: OK, I know your guess is somehow right, so how to fix it?
Usually when we check the existence of elements in multiple dimensional array, we use  something like
1
array_key_exists('non_exist', $a['test']);
Yes. This is true…but if you really do so in our case, you will get this warning:
Warning: array_key_exists() expects parameter 2 to be array, string given 
Somehow for unknown reason array_key_exists() doesn’t consider string as array now and is complaining us.
So what’s the solution?

Complete array element existence checking function

Combined with what I proposed in the previous and this post, I have worked out a function that checks whether an element does exist in an array, regardless the array’s dimensions:
1
2
3
4
5
6
7
8
9
10
11
<!--?php function elementExists($key, $array){     if (is_array($key)) {         $curArray = $array;         $lastKey = array_pop($key);         foreach($key as $oneKey) {          if (!elementExists($oneKey, $curArray)) return false;           $curArray = $curArray[$oneKey];         }       return is_array($curArray) && elementExists($lastKey, $curArray);   } else {        return isset($array[$key]) || array_key_exists($key, $array);   } } $a=array(1,2,3,4, 'dim1'=-->array('dim2'=>array('dim3'=>null)));
 
//multi-dimension : check if $a['dim1']['dim2']['dim3']['dim4'] exists:
var_dump(elementExists(array('dim1', 'dim2', 'dim3', 'dim4'), $a)); //false
 
//multi-dimension : check if $a['dim1']['dim2']['dim3'] exists:
var_dump(elementExists(array('dim1', 'dim2', 'dim3'), $a)); //true
 
//single dimension : check if $a['dim1'] exists:
var_dump(elementExists('dim1', $a)); //true
?>
This piece of codes looks quite awful and dirty, and its performance  is not evaluated. I think there are more elegant (and faster) codes to do the same thing. Since I’m in a hurry and got to complete my project ASAP, I prefer to leave it as it is now.

0 comments:

Post a Comment