Asked  8 Months ago    Answers:  5   Viewed   32 times

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?

 Answers

88

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.

Wednesday, March 31, 2021
 
edorian
answered 8 Months ago
18

Once switch finds a matching case, it just executes all the remaining code until it gets to a break statement. None of the following case expressions are tested, so you can't have dependencies like this. To implement sub-cases, you should use nested switch or if statements.

switch ($category) {
case 'A':
    $msg = 'hello';
    if ($offer == 'special') {
        $id = '123';
    } elseif ($discount == '50D') {
        $id = '999';
    }
    break;
...
}
echo $id;

The fallthrough feature of case without break is most often used when you have two cases that should do exactly the same thing. So the first one has an empty code with no break, and it just falls through.

switch ($val) {
case 'AAA':
case 'bbb':
    // some code
    break;
...
}

It can also be used when two cases are similar, but one of them needs some extra code run first:

switch ($val) {
case 'xxx':
    echo 'xxx is obsolete, please switch to yyy';
case 'yyy':
    // more code
    break;
...
}
Friday, May 28, 2021
 
IcedAnt
answered 5 Months ago
66

This format is shown in the PHP docs:

switch (i) {
    case 1:
    case 3:
        code block A;
        break;
    case 2:
        code block B;
        break;
    default:
        code block default;
        break;
}

EDIT 04/19/2021:

With the release of PHP8 and the new match function, it is often a better solution to use match instead of switch.

For the example above, the equivalent with match would be :

$matchResult = match($i) {
    1, 3    => // code block A
    2       => // code block B
    default => // code block default
}

The match statement is shorter, doesn't require breaks and returns a value so you don't have to assign a value multiple times.

Moreover, match will act like it was doing a === instead of a ==. This will probably be subject to discussion but it is what it is.

Saturday, May 29, 2021
 
John_BSDthos
answered 5 Months ago
35

I throw an exception. As sure as eggs are eggs, someone will pass an integer with a bad value rather than an enum value into your switch, and it's best to fail noisily but give the program the possibility of fielding the error, which assert() does not.

Monday, August 2, 2021
 
user9751447
answered 3 Months ago
73

You can have many values for a case, all you have to do is to separate them by a comma.

I would also recommend returning an nil value than an empty string and make the function return value an String?, but that depends on how the function is going to be used.

func update(key:UserInfosKey, value:String, context:UserDefaultsMainKeys) -> String? {
    switch key {
    case .CameraICloudActivated, 
         .CameraICloudShare, 
         .CameraMyPhotoStream,
         .CameraPhotosCount, 
         .NetworkConnection, 
         .PhoneLanguage, 
         .UserIdentifier:
        return value  
    default:
        return nil
    }
}
Sunday, August 15, 2021
 
axiomer
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :