Best way to do a PHP switch with multiple values per case?
Asked Answered
L

18

77

How would you do this PHP switch statement?

Also note that these are much smaller versions, the one I need to create will have a lot more values added to it.

Version 1:

switch ($p) { 
    case 'home': 
    case '': 
        $current_home = 'current';
    break; 
    
    case 'users.online': 
    case 'users.location': 
    case 'users.featured': 
    case 'users.new': 
    case 'users.browse': 
    case 'users.search': 
    case 'users.staff': 
        $current_users = 'current';
    break;
     
    case 'forum': 
        $current_forum = 'current';
    break; 
} 

Version 2:

switch ($p) { 
    case 'home': 
        $current_home = 'current';
    break; 
    
    case 'users.online' || 'users.location' || 'users.featured' || 'users.browse' || 'users.search' || 'users.staff': 
        $current_users = 'current';
    break;
    
    case 'forum': 
        $current_forum = 'current';
    break; 
} 

Test Results:

I ran some speed test on 10,000 iterations,

Time 1: 0.0199389457703 // If statements
Time 2: 0.0389049446106 //switch statements
Time 3: 0.106977939606 // Arrays

Lemmy answered 21/8, 2009 at 1:52 Comment(3)
Version 2 doesn't do what you think.Leftwards
version 2 doesn't work, read here for more info: nutt.net/2004/12/28/multiple-cases-for-switch-constructMelosa
@jasondavis If you use variable variables you'll have a code that SMALL AND SIMPLE, just the way I like it =) Check out my answer, it's tested and works.Softhearted
L
56

For any situation where you have an unknown string and you need to figure out which of a bunch of other strings it matches up to, the only solution which doesn't get slower as you add more items is to use an array, but have all the possible strings as keys. So your switch can be replaced with the following:

// used for $current_home = 'current';
$group1 = array(
        'home'  => True,
        );

// used for $current_users = 'current';
$group2 = array(
        'users.online'      => True,
        'users.location'    => True,
        'users.featured'    => True,
        'users.new'         => True,
        'users.browse'      => True,
        'users.search'      => True,
        'users.staff'       => True,
        );

// used for $current_forum = 'current';
$group3 = array(
        'forum'     => True,
        );

if(isset($group1[$p]))
    $current_home = 'current';
else if(isset($group2[$p]))
    $current_users = 'current';
else if(isset($group3[$p]))
    $current_forum = 'current';
else
    user_error("\$p is invalid", E_USER_ERROR);

This doesn't look as clean as a switch(), but it is the only fast solution which doesn't include writing a small library of functions and classes to keep it tidy. It is still very easy to add items to the arrays.

Leftwards answered 21/8, 2009 at 2:50 Comment(1)
to be technically correct, this gets slower as you add more items, it just gets slower much more slowly - hash tables are blazingly fast but they aren't magic :-)Annoyance
L
31

Version 2 does not work!!

case 'users.online' || 'users.location' || ...

is exactly the same as:

case True:

and that case will be chosen for any value of $p, unless $p is the empty string.

|| Does not have any special meaning inside a case statement, you are not comparing $p to each of those strings, you are just checking to see if it's not False.

Leftwards answered 21/8, 2009 at 2:19 Comment(3)
ok, would you think that using a switch or if statement is faster then creating multiple arrays and checking if a value is in an array multiple times?Lemmy
I have never heard or read anything that makes me think a switch() would be faster. An route is still going to do a whole heap of string comparisons.Leftwards
here is something that shows an array may be slower #325165Lemmy
S
8

Put those many values into an array and query the array, as the switch-case seems to hide the underlying semantics of what you're trying to achieve when a string variable is used as the condition, making it harder to read and understand, e.g.:

$current_home = null;
$current_users = null;
$current_forum = null;

$lotsOfStrings = array('users.online', 'users.location', 'users.featured', 'users.new');

if(empty($p)) {
    $current_home = 'current';
}

if(in_array($p,$lotsOfStrings)) {
    $current_users = 'current';
}

if(0 === strcmp('forum',$p)) {
    $current_forum = 'current';
}
Scene answered 21/8, 2009 at 2:11 Comment(11)
+1 The first time I read the question, this solution came to mind first. As the number of cases increase, all you need to do is add values to the array and rely on the in_array to do it's job.Ingurgitate
in my case to do it this way, I would have many different arrays so I would be doing the in_array against several arrays, do you think there is any performance gain in 1 way vs another?Lemmy
I haven't tried benchmarking it it's easier to read, especially it you'll use constants. How many different arrays (and sizes) are you expecting to use?Ingurgitate
I don't think there will be any perceptible difference in performance. Even if there was, I would probably still stick with the above because it just makes more sense to me in terms of readability and maintainability.Scene
@Ingurgitate this is used for a menu selector on a hgih traffic site so there will only be about 8 arrays and some of them can be up to about 15 items each, I know it's not BIG but it is extra work being done and there is going to be a lot of traffic and this would be something that is done on every single page load. Thats why I am askingLemmy
@jasondavis - That should be nothing to worry about at all. You can always use microtime() to compare the performance, but I still think the difference would be negligible or nothing to worry about.Scene
just for knowledge, here is a similar test I found, in this case it seems in_array might be slower then I had thought #325165Lemmy
I'm assuming that in_array is using hash behind the scene so it's still faster than checking the cases one by one. And again, it's more readable. You might also want to check the design of your menu. I'm wondering why there are 8 arrays. In any case,i'll go with karim79's answer.Ingurgitate
Whoa! I didn't expect that. The difference is negligible. You don't want to sacrifice code readability for that.Ingurgitate
If performance is really an issue here (see numerous posts about the danger of optimizing too soon), and the look up tables really big enough to worry about the speed of in_array, then a properly indexed database is the right solution. Not to be considered until you KNOW you need to optimize.Journalist
in_array will not perform well for large arrays, because there is no hash of values in an array, so it has to loop over each item in the array until it finds a match (or runs out of items). There is also some overhead in allocating the array holding the list of matches in the first place, as opposed to the compiled-in tests in a switch statement.Claque
B
8

For the sake of completeness, I'll point out that the broken "Version 2" logic can be replaced with a switch statement that works, and also make use of arrays for both speed and clarity, like so:

// used for $current_home = 'current';
$home_group = array(
    'home'  => True,
);

// used for $current_users = 'current';
$user_group = array(
    'users.online'      => True,
    'users.location'    => True,
    'users.featured'    => True,
    'users.new'         => True,
    'users.browse'      => True,
    'users.search'      => True,
    'users.staff'       => True,
);

// used for $current_forum = 'current';
$forum_group = array(
    'forum'     => True,
);

switch (true) {
    case isset($home_group[$p]):
        $current_home = 'current';
        break;
    case isset($user_group[$p]):
        $current_users = 'current';
        break;
    case isset($forum_group[$p]):
        $current_forum = 'current';
        break;
    default:
        user_error("\$p is invalid", E_USER_ERROR);
}    
Between answered 10/10, 2012 at 12:38 Comment(0)
N
6

Nowadays you can do...

switch ([$group1, $group2]){
    case ["users", "location"]:
    case ["users", "online"]:
        Ju_le_do_the_thing();
        break;
    case ["forum", $group2]:
        Foo_the_bar();
        break;
}

Here's a ready ready-to-run set of code demonstrating the concept:

<?php

function show_off_array_switch(){
  $first_array = ["users", "forum", "StubbornSoda"];
  $second_array = ["location", "online", "DownWithPepsiAndCoke"];

  $rand1 = array_rand($first_array);
  $rand2 = array_rand($second_array);

  $group1 = $first_array[$rand1];
  $group2 = $second_array[$rand2];

  switch ([$group1, $group2]){
      case ["users", "location"]:
      case ["users", "online"]:
          echo "Users and Online";
          break;
      case ["forum", $group2]:
          echo "Forum and variable";
          break;
      default:
          echo "default";
  }
  echo "\nThe above result was generated using the array: \n" . print_r([$group1, $group2], true);
}

for ($i = 0; $i < 10; $i++){
  show_off_array_switch();
}

The following has (for one random run) output the following:

 Users and Online
The above result was generated using the array: 
Array
(
    [0] => users
    [1] => online
)
Users and Online
The above result was generated using the array: 
Array
(
    [0] => users
    [1] => online
)
default
The above result was generated using the array: 
Array
(
    [0] => users
    [1] => DownWithPepsiAndCoke
)
Users and Online
The above result was generated using the array: 
Array
(
    [0] => users
    [1] => location
)
Forum and variable
The above result was generated using the array: 
Array
(
    [0] => forum
    [1] => DownWithPepsiAndCoke
)
Forum and variable
The above result was generated using the array: 
Array
(
    [0] => forum
    [1] => DownWithPepsiAndCoke
)
Forum and variable
The above result was generated using the array: 
Array
(
    [0] => forum
    [1] => online
)
default
The above result was generated using the array: 
Array
(
    [0] => StubbornSoda
    [1] => location
)
Users and Online
The above result was generated using the array: 
Array
(
    [0] => users
    [1] => location
)
Users and Online
The above result was generated using the array: 
Array
(
    [0] => users
    [1] => location
)
Namedropping answered 6/11, 2018 at 0:38 Comment(3)
did anyone test this ?Russ
I have, and just to be sure, I just now checked it again. Still works well!Namedropping
@Tanzeel For you, just added a fully runnable code example to show that it works.Namedropping
H
5

Some other ideas not mentioned yet:

switch(true){ 
  case in_array($p, array('home', '')): 
    $current_home = 'current'; break;

  case preg_match('/^users\.(online|location|featured|new|browse|search|staff)$/', $p):
    $current_users = 'current'; break;

  case 'forum' == $p:
    $current_forum = 'current'; break; 
}

Someone will probably complain about readability issues with #2, but I would have no problem inheriting code like that.

Huggins answered 21/2, 2014 at 2:17 Comment(2)
Funny, a buddy of mine just sent me very similar code saying it works and I immediately came to Stack Overflow to see if anyone else had thought of this as a solution. I think it's pretty coolMoss
Even if you think it is "cool" this verbose version of an if-elseif-elseif block removes the only advantage that a switch can provide because switch(true) means that cases must be evaluated. Conversely, the whole point of using a switch() is to evaluate a condition only once and find the static case that satisfies it.Hayseed
P
4

If anyone else was ever to maintain your code, they would almost certainly do a double take on version 2 -- that's extremely non-standard.

I would stick with version 1. I'm of the school of though that case statements without a statement block of their own should have an explicit // fall through comment next to them to indicate it is indeed your intent to fall through, thereby removing any ambiguity of whether you were going to handle the cases differently and forgot or something.

Poisson answered 21/8, 2009 at 2:9 Comment(0)
C
3

Version 1 is certainly easier on the eyes, clearer as to your intentions, and easier to add case-conditions to.

I've never tried the second version. In many languages, this wouldn't even compile because each case labels has to evaluate to a constant-expression.

Ceroplastics answered 21/8, 2009 at 1:56 Comment(0)
T
1

I definitely prefer Version 1. Version 2 may require less lines of code, but it will be extremely hard to read once you have a lot of values in there like you're predicting.

(Honestly, I didn't even know Version 2 was legal until now. I've never seen it done that way before.)

Tijerina answered 21/8, 2009 at 2:0 Comment(0)
H
1

No version 2 doesn't actually work but if you want this kind of approach you can do the following (probably not the speediest, but arguably more intuitive):

switch (true) {
case ($var === 'something' || $var === 'something else'):
// do some stuff
break;
}

Haith answered 28/4, 2011 at 23:8 Comment(1)
Not intuitive at all. This could be replaced by a simple if statement.Futures
E
0

I think version 1 is the way to go. It is a lot easier to read and understand.

Elaelaborate answered 21/8, 2009 at 2:12 Comment(0)
V
0
if( in_array( $test, $array1 ) )
{
    // do this
}
else if( stristr( $test, 'commonpart' ) )
{
    // do this
}
else
{
    switch( $test )
    {
        case 1:
            // do this
            break;
        case 2:
            // do this
            break;
        default:
            // do this
            break;
    }
}
Velocity answered 17/4, 2012 at 22:33 Comment(0)
S
0

Switch in combination with variable variables will give you more flexibility:

<?php
$p = 'home'; //For testing

$p = ( strpos($p, 'users') !== false? 'users': $p);
switch ($p) { 
    default:
        $varContainer = 'current_' . $p; //Stores the variable [$current_"xyORz"] into $varContainer
        ${$varContainer} = 'current'; //Sets the VALUE of [$current_"xyORz"] to 'current'
    break;

}
//For testing
echo $current_home;
?>

To learn more, checkout variable variables and the examples I submitted to php manual:
Example 1: http://www.php.net/manual/en/language.variables.variable.php#105293
Example 2: http://www.php.net/manual/en/language.variables.variable.php#105282

PS: This example code is SMALL AND SIMPLE, just the way I like it. It's tested and works too

Softhearted answered 16/11, 2012 at 20:6 Comment(1)
OP is asking how to, in general, combine multiple case conditions most efficiently.Misdemeanant
D
0

maybe

        switch ($variable) {
        case 0:
            exit;
            break;
        case (1 || 3 || 4 || 5 || 6):
            die(var_dump('expression'));
        default:
            die(var_dump('default'));
            # code...
            break;
    }
Disappoint answered 29/3, 2019 at 9:36 Comment(2)
better if you make some explanation your answer and re-formatted your codeDivertimento
1 || 3 || 4 || 5 || 6 will always result in true.Animal
M
0

You are simply looking for common patterns in strings. I would have thought a regular expression would be a more efficient way of doing this as PHP implements this with preg_match so little code to write and probably massively quicker. For example:

case preg_match('/^users./'):
// do something here
break;
Magna answered 2/1, 2021 at 8:0 Comment(0)
L
0

From PHP documentation you can do that: https://www.php.net/manual/en/control-structures.switch.php#41767

$input = 'users.online';
$checked = null;
        
switch (true):
    case ($input === 'home'):
        $checked = 'ok 1';
        break;

    case (in_array($input , ['users.online', 'users.location', 'users.featured', 'users.browse', 'users.search', 'users.staff'])):
        $checked = 'ok 2';
        break;

    case ($input === 'forum'):
        $checked = 'ok 3';
        break;
endswitch;

echo $checked;
Lying answered 13/6, 2021 at 19:45 Comment(0)
D
0

Another option would be Version 2:

if (in_array($p,['users.online','users.location','users.featured','users.browse','users.search','users.staff'])){
$p="users";
}
switch ($p) { 
    case 'home': 
        $current_home = 'current';
    break;
    case 'users': 
        $current_users = 'current';
    break;
    case 'forum': 
        $current_forum = 'current';
    break; 
}
Darcee answered 22/10, 2022 at 17:25 Comment(0)
M
0

I used something like this and it works

switch ($variable) {
    case 0:
        exit;
        break;
    case 1:
    case 2:
    case 3:
        die(var_dump('expression'));
    default:
        die(var_dump('default'));
        # code...
        break;
}

Case 1,2 and three will execute same piece of code.

Muniments answered 12/5, 2023 at 16:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.