Home » Php » php – Odd behaviour in a switch statement

php – Odd behaviour in a switch statement

Posted by: admin July 12, 2020 Leave a comment

Questions:

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.

<?php
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?

How to&Answers:

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.

Answer:

The key point is that a switch() statement performs comparisons between the parameter and the labels. That means that you have to deal with PHP comparison and type casting rules. Just see some examples:

<?php

var_dump( ''==0 );    // bool(true)
var_dump( 'foo'==0 ); // bool(true)
var_dump( '0'==0 );   // bool(true)
var_dump( '0m'==0 );  // bool(true)
var_dump( '01'==0 );  // bool(false)

The reference can be found at:

Initializing an unset GET parameter to 0 is a pretty strange design decision. You should deal with that particular case in a separate fashion to make it clear that it’s a special situation:

if( $name===0 ){
    return '';
}
switch($name){
    // ...
}