Tuesday, 31 January 2017

php - Odd behaviour in a switch statement



I'm writing code for a sortable table, where clicking the links in the header change the ORDER BY executed when generating a set of search results (the case where there there is no valid order by causes the query to not be run with order by and just return the results in the order the database returns. this is as designed). The code is being written within a framework provided by my employer.




To validate the ORDER BY part of the query I run the input through the following validation function.



function sortMode ($name)
{
$mode = '';
switch ($name)
{
case 'resnum' : $mode = 'b_resnum'; break;
case 'state' : $mode = 'st_id'; break;

case 'name' : $mode = 'lastname, firstname'; break;
case 'phone' : $mode = 'phone'; break;
case 'email' : $mode = 'email'; break;
case 'opened' : $mode = 'cs_created'; break;
default : $mode = ''; break;
}
return ($mode);
}
?>



Under testing I discovered that if no parameter was provided then the sort order would be resnum. After some experimentation, I discovered that the filtering built into the framework would cause a request for an uninitialized variable such as an unset GET parameter to return integer 0. If the above code got fed a 0 integer as its input it would always follow the first path of execution available to it.



As an experiment I tried rearranging the order of the cases in the switch statement, and found whatever was at the top would be what was executed if this function was passed a 0.



The solution to the problem was using switch (strval($name)) so the particular problem is solved, but now I'm curious as to the general behaviour of PHP switch statements. Is the behaviour I witnessed the correct behaviour for PHP? Is there some fault in PHP that's causing this, or have I made an error in my code of which I'm not aware?


Answer



It's because of the way php casts strings to ints. When you pass in a 0, you are asking it to do an integer comparison, so it will convert all of your case keys to integers. When php casts a string to an int, it looks for an actual number at the beginning of the string, and gobbles the number up until it hits a non-number. Since the string "resnum" has no numbers, it returns 0. See here:



php > echo (int)"100";

100
php > echo (int)"300 dogs";
300
php > echo (int)"resnum";
0
php > echo (int)"resnum 100";
0


Since all of those strings cast to 0, the first case would evaluate to true since 0 == 0.




Resources:
String conversion to numbers
Type comparison tables






Nitpick time. When you're doing simple case statements that map a string to a string, use an array. It's much clearer and actually faster:



function sortMode ($name)
{
$modeMap = array(

'resnum' => 'b_resnum',
'state' => 'st_id',
'name' => 'lastname, firstname',
'phone' => 'phone',
'email' => 'email',
'opened' => 'cs_created'
);

return isset($modeMap[$name]) ? $modeMap[$name] : '';
}



If the $name is set in the map, we return the value that key is mapped to. Otherwise, we return an empty string, which takes the place of the default case.



As a bonus, you would have noticed the bug earlier if you did the above method, because it would be trying to access $modeMap[0] and would have returned your default case instead.


No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...