2015-08-04 14 views
7

Stavo eseguendo alcuni test unitari e ho riscontrato un comportamento imprevisto con un'istruzione switch che stavo usando. Ho isolato la condizione qui sotto.Comportamento interruttore PHP imprevisto

function test($val) 
{ 
    switch($val) 
    { 
     case 'a': 
     case 'b': 
      return 'first'; 
     break; 
     case 'c': 
      return 'second'; 
     break; 
     default: 
      return 'third'; 
    } 
} 

qui sono il mio primo turno di prove:

test('a') => 'first' 
test('b') => 'first' 
test('c') => 'second' 
test('d') => 'third'  
test('0') => 'third' 
test('1') => 'third' 
test('true') => 'third' 
test('false') => 'third' 

Questo è abbastanza evidente a destra? ok ora controllare questi fuori:

test(0)  => 'first' // expected 'third' 
test(1)  => 'third' 
test(true) => 'first' // expected 'third' 
test(false) => 'third' 
test(null) => 'third' 
test([]) => 'third' 

Che cosa è con i risultati strani con 0 e vero? Lo digiterei per perdere digitando se 1/true e 0/false restituivano gli stessi valori. Ma loro no!

Se si converte il valore in a (stringa), lo switch funziona come previsto.

test((string) 0)  => 'third' 
test((string) 1)  => 'third' 
test((string) true) => 'third' 
test((string) false) => 'third' 

Non capisco il motivo per cui l'interruttore wont "lavoro", come ho inteso senza usare "(stringa)"

qualcuno può spiegare perché questo sta accadendo? la documentazione di

+1

Nota che l'interruttore/caso non ha confronto. http://php.net/manual/en/types.comparisons.php#types.comparisions-loose –

+5

E sì, '0' è uguale a' 'a'' in confronto libero, mentre '1' non lo è. https://eval.in/412018 – Jessica

+0

0 == 'a' .... Quindi succede perché PHP. – GolezTrol

risposta

2

Per PHP:

noti che switch/case fa il confronto sciolto.

http://php.net/manual/en/control-structures.switch.php

Se si vuole fare il confronto tipo, è necessario ristrutturare il proprio codice. Esempio:

function test($val) 
{ 
    if($val === 'a' || $val === 'b') 
     return 'first'; 

    if($val === 'c') 
     return 'second'; 

    return 'third'; 
} 

Si noti come non ho alcun else 's. Questo perché ogni istruzione restituisce qualcosa ... Altrimenti la funzione restituirà third per impostazione predefinita.

3

Questo è il comportamento previsto. Facendo confronti, PHP modificherà il tipo di un valore nella sua ricerca di una corrispondenza.

test(0)  => 'first' // 'a' is altered into int 0 and therefore matches 
var_dump((int) 'a'); // results 'int(0)' 

test(true) => 'first' // both true and 'a' are truthy statements therefore matches. 
if ('a' == true) echo "its true"; 

PHP è un linguaggio debolmente tipizzato e che a volte ti morde nel sedere. Potresti considerare il ri-factoring dell'interruttore in una struttura if/else if/else e utilizzare l'operatore === per confronti forti.

0

Hai risposte semplici: comaprison sciolto. Ma qual è la soluzione? Se si desidera che i numeri di confronto non utilizzino le stringhe. Se vuoi booleano, usa il valore booleano. Prova a convalidare (o eseguire il cast) variabili prima di chiamare funzione/metodo.Scrivere il codice come nel linguaggio fortemente tipizzato, se si vuole Int si può fare qualcosa di simile:

/** 
* @param int $val 
*/ 
function test($val) 
{ 
    //exception 
    if (!is_int($val)) { 
     throw new InvalidArgumentException('$val expected to be int'); 
    } 
    //or cast - but may beahave unexpected 
    $val = (int)$val 

    switch($val) 
    { 
     case 0: 
      return 'first'; 
     break; 
     case 1: 
      return 'second'; 
     break; 
     default: 
      return 'third'; 
    } 
} 
0

PHP è weakly (or loosely) typed, quindi è necessario lanciare $val in modo appropriato e/o affermare il suo tipo, se necessario:

/** 
* @param string $val 
*/ 

function test($val) 
{ 
    assert('is_string($val)', 'expecting string'); 
    switch((string)$val) { 
     case 'a': 

} 

un'alternativa è quella di utilizzare un dizionario:

$opts = [ 
    'a' => 'first', 
    'b' => 'first', 
    ... 
]; 
foreach ($opts as $opt => $response) { 
    if ($opt === $val) { 
     return $response; 
    } 
} 
return 'default'; 

Dal momento che si sta parlando di esecuzione di test, sappiate che l'ha sopra il probabilmente indesiderata effetto collaterale di masking la complessità ciclomatica, cioè, ci sono davvero i percorsi decisionali count($opts) +1 lì, ma la complessità ciclomatica di base ne vede solo due.

una configurazione più scomoda, che preserva la complessità (per esempio, si ottiene la copertura del codice corretto da prove) potrebbe essere

private function testA($val) { 
    return 'first'; 
} 
private function testB($val) { 
    return $this->testA($val); // or 'first' again 
} 
... 

public function test($val) { 
    if (!is_class_method($this, $method = 'test' . $val)) { 
     $method = 'testDefault'; 
    } 
    $this->$method($val); 
} 

È possibile che questo ha lo svantaggio di non lasciare avete tutte le opzioni in un unico unità (vale a dire l'interruttore) e può essere adottato solo se si dispone di valori che possono essere utilizzati in un nome di funzione e il controllo non fa distinzione tra maiuscole e minuscole. Ma dopo aver provato diversi modi, sto trovando questo un compromesso praticabile.

0

Risposta breve: il cast del valore di ingresso

Grazie per tutte le risposte. Il seguente link è stato di grande aiuto nel ripensare a cosa stava succedendo.

http://php.net/manual/en/types.comparisons.php#types.comparisions-loose

Il metodo ero test non è mai stato destinato ad essere utilizzato con gli interi. Mi è capitato di lanciare alcuni tipi diversi solo per divertimento e ho incontrato il comportamento involontario per caso.

Dopo aver letto tutte queste risposte, avrò un approccio diverso e non utilizzerò un'istruzione switch. Penso che un approccio al dizionario che @lserni ha menzionato sia la strada da percorrere per questa specifica implementazione. Tuttavia, se volevo mantenere lo stesso codice, una soluzione rapida sarebbe quella di fare ciò che @Styx ha suggerito e lanciare il valore come (stringa).

Grazie!

Problemi correlati