PHP switch működése. Típusos switch és típuskonverziók.

Most egy érdekességről fogok írni, aminek igazán gyakorlati hasznát nem látom, viszont jó tudni. A switch elágazás nem egyedi a php-ben. Más nyelvek is megvalósítják, mint például a java, C vagy a pascal, csak más néven és/vagy egy kicsit másképp. Bizonyos esetekben az IF elágazás felváltására szolgál. Két érték pedig többnyire akkor lesz egyenlő, ha a típus is egyezik. Ám PHP-ben az értelmező mondhatni, kénye kedvére változtathatja a típusokat egy-egy ellenőrzés alkalmával. Persze adott, hogy mit mire tud konvertálni. De ha biztosak akarunk lenni a típusok egyezésében is, akkor a == helyett a === -re van szükség. Switch esetén viszont mindig az első verzió, azaz a típusegyeztetés nélküli ellenőrzés történik. Ez egy részt lehet probléma. Más részt ha ez probléma, akkor az if-eket se nehezebb megírni sok esetben. Mégis bizonyos speciális helyzetekben igen csak el kéne gondolkozni, hogy ugyanazt a működést egy IF hogy váltja fel. Ez pedig a break utasítások használatával és a default ág elhelyezésével függ össze.

Első körben nézzünk egy egyszerű switch elágazást.

$a = 2;

switch ($a)
{
        case 1:
                print "Ez egy 1-es";
                break;
        case 2:
                print "Ez egy 2-es";
                break;
        case 12:
                print "Ez a 12-es";
                break;
        default:
                print "A megadott érték: ".$a;
}

Ugyanez if-ekkel.

if ($a == 1)
{
        print "Ez egy 1-es";
}
else if ($a == 2)
{
        print "Ez egy kettes";
}
else if ($a == 12)
{
        print "Ez a 12-es";
}
else
{
        print "A megadott érték: ".$a;
}

Tehát kiírja, hogy Ez egy 2-es. Gondolom látható, hogy azok az ágak, amik egy case-el kezdődnek a switch-ben és a végén van egy break, az if-es megoldásnál megfelelő sorrendben bekerülnek egy-egy if avagy else if ágba. A default pedig az if elágazás else ágának felel meg. De mi a helyzet, ha nem teszek valahova break-et? Amint megtalálja az igaz feltételt, az aktuális ágtól kezdve az összes ágba bele fog futni amíg nem talál egy break-et.

$a = 12;

switch ($a)
{
        case 1:
                print "Ez egy 1-es";
                break;
        case 2:
                print "Ez egy 2-es";
        case 12:
                print "Ez a 12-es";
        default:
                print "A megadott érték: ".$a;
}

Kiírja, hogy Ez egy2-esEz a 12-esA megadott érték: 2
Na ugye ezt már nem is olyan kézenfekvő átírni. Leszámítva azt a megoldást, ahol minden lehetőségnek készítünk egy új ágat. Ráadásul ha a default ágat valahova a két case közé tesszük, vagy az összes case elé, az megint nehezít a dolgon. Mert ugye ha egyik feltétel sem teljesül, akkor a default lesz a nyerő. De akkor ha nincs a végén break, azok is lefutnak, amik feltételtől függöttek.

Erre talán jó példa lehet, ha elképzelünk egy diagnosztikai programot. Ahol meg lehet adni, milyen szintű diagnosztikát végezzen. A szinteket az egyes case ágak jelentik. De azt akarjuk, hogy ha nem adják meg a szintet, akkor a legalaposabb vizsgálat történjen. És erről még figyelmeztessen is.

$level = 0;
switch ($level)
{
        default:
                print "Definiálatlan szint! Minden ellenőrzést elvégzek.<br />";
        case 3:  
                print "3-es szintű diagnosztika!<br />";
        case 2:
                print "2-es szintű diagnosztika!<br />";
        case 1:
                print "1-es szintű diagnosztika!<br />";
}

Sehol sincs break, tehát a 3-as választásakor a 2-es és az 1-es is lefut. Ha pedig nincs szint megadva, akkor maximális szintet választ, de azért figyelmeztet, hogy ez nem a mi döntésünk volt. Nem túl életszerű, de ez van.

Szép és jó tehát a switch, de térjünk vissza a típusokhoz. A példa maradjon a régi, de most a 2-es case ágába nem a számot írom, hanem magát a szöveget, hogy 2-es

$a = 2;

switch ($a)
{
        case 1:
                print "Ez egy 1-es";
                break;
        case '2-es':
                print "Ez egy 2-es";
                break;
        case 12:
                print "Ez a 12-es";
                break;
        default:
                print "A megadott érték: ".$a;
}

Minden gond nélkül kiírja a következőt: Ez egy 2-es. Pedig abban a case ágban csak az első két karakter egyezik meg a számmal. Nézzük hogy néz ki ugyanez if-ekkel.

$a = 2;

if ($a == 1)
{
        print "Ez egy 1-es";
}
else if ($a == '2-es')
{
        print "Ez egy kettes";
}
else if ($a == 12)
{
        print "Ez a 12-es";
}
else
{
        print "A megadott érték: ".$a;
}

Mivel a '2-es' egy string, úgy hasonlítja össze az $a változóval, hogy az első karaktertől addig nézi, amíg talál szám karaktert. A tizedespontot is beleértve. De itt a 12 után már kötőjel van. Azzal nem tud mit kezdeni, tehát nem is foglalkozik vele. Számként kezeli. A feltétel teljesül.

Na és itt jön a trükk. PHP-ben a case után nem csak egyszerű értékeket szabad írni. Lehet egy összetettebb feltétel is. Ebben az esetben ez az összetett feltétel kerül az őt helyettesítő IF-ben a vizsgált érték helyére. Ami persze egy logikai értéket fog adni, de minden olyan értékre helyes lesz az eredmény, ami logikai igazzá konvertálható. Jöjjön egy példa a típusfüggő switch-re

$a = 2;

switch ($a)
{
        case $a === 1:  //$a == ($a === 1) --> 2 == (2 === 1) --> 2 == false --> false
                print "Ez egy 1-es";
                break;
        case $a === '2-es':  //$a == ($a === '2-es') --> 2 == (2 === '2-es') --> 2 == false --> false
                print "Ez egy '2-es' string";
                break;
        case $a === 2:   //$a == ($a === 2) --> 2 == (2 === 2) --> 2 == true --> true
                print "Ez a 2-es";
                break;
        default:
                print "A megadott érték: ".$a;
}

Hogy ez miért működik? A case-ben egy logikai érték szerepel: $a === '2-es'. Ami persze nem igaz, mert itt már figyeltünk a típusra. Így az $a változó ezzel a false értékkel lesz összehasonlítva: $a == false. Ez szintén nem igaz, mert az $a változó értéke 2 volt. Azt pedig a legnagyobb rosszindulattal sem tudja az értelmező hamissá konvertálni. Ha már ennyire mondom, mondjam azt is, mit tudna. Ugye? Például a 0 számot, a null értéket, az üres stringet ( $a == "" ), az üres tömböt ( array() ). És persze a logikai false értéket már nem is kell konvertálnia. A fenti példában most kezdőértéknek null-t adok.

$a = null;

switch ($a)
{
        case $a === 1:  //$a == ($a === null) --> null == (null === 1) --> null == false --> true
                print "Ez egy 1-es";
                break;
        case $a === '2-es':  //$a == ($a === '2-es') --> null == (null === '2-es') --> null == false --> true
                print "Ez egy '2-es' string";
                break;
        case $a === 2:   //$a == ($a === 2) --> null == (null === 2) --> null == false --> true
                print "Ez a 2-es";
                break;
        default:
                print "A megadott érték: ".$a;
}

Így pedig már látszólag borul az egész elmélet. De nem teljesen. Tudjuk, hogy hamissá konvertálható érték nem kerülhet ebbe a switch-be. De írható olyan inverz switch, ahova viszont csak azok kerülhetnek.

$a = null;

switch ($a)
{
        case $a !== 1: //$a == ($a !== 1) --> null == (null !== 1) --> null == true --> false
                print "Ez egy 1-es";
                break;
        case $a !== '2-es': //$a == ($a !== '2-es') --> null == (null !== '2-es') --> null == true
                print "Ez egy '2-es'";
                break;
        case $a !== null: // $a == ($a !== null) --> null == (null !== null) --> null == false --> true
                print "Ez a null érték.";
                break;
        default:
                print "A megadott érték: ".$a;
}

Első ránézésre lehet, hogy marhaságnak tűnik. De végignézve a kiértékelés alakulását ( a megjegyzésekben ), belátható, hogy ez tényleg működik. Tehát ha olyan switch-re van szükség, ami mindkettőt jól kezeli, két switch kell és egy IF. A következőképpen:

$a = null;

if ($a) : switch ($a)
{
        case $a === 1:
                print "Ez egy 1-es";
                break;
        case $a === '2-es':
                print "Ez egy '2-es'";
                break;
        case $a === 2:
                print "Ez a 2-es";
                break;
        default:
                print "A megadott érték: ".$a;
}
else: switch ($a)
{
        case $a !== 0:
                print "Ez egy 0";
                break;
        case $a !== "":
                print "Ez egy üres string";
                break;
        case $a !== null:
                print "Ez a null érték";
                break;
        case $a !== false:
                print "Ez a false érték";
                break;
        default:
                print "A megadott érték: ".$a;
}
endif;

A lényeg tehát, hogy hamissá konvertálható értékeknél pont a feltétel nem teljesülését kell vizsgálni a case-ben. Ami végeredményül pont az elvárt működést adja. Elég nyakatekert megoldása ez a switch használatának, de ki tudja. Lehet, hogy egyszer még szükség lesz rá.

Végül egy bónusz példa, ha már végképp nem tudod, mi olyant írhatnál, amire senki sem gondol switch-ekkel:

$a = 1;
$b = 9;

switch (true)
{
        case $a === 1:
                print "\$a egy 1-es";
                break;
        case $b === 2:
                print "\$b egy 2-es";
                break;
}

Bármilyen feltétel írható tehát a case-ek után és ráadásként még a switch paramétere is lehet logikai igaz. Így visszakapjuk az IF-et switch formában. Annyi extrával, hogy továbbra is játszhatunk a break-ekkel és a default ág helyzetével.

Ez a példa egyébként kapásból kiváltja az if+switch-es megoldást. Köszönet Suttogó-nak, hogy felhívta rá a figyelmem (2011. január 8.). Tehát:

$a = 0;

switch (true)
{
        case $a === 0: //true == (0 === 0) --> true == true
                print "Ez nulla";
                break;
        case $a === 1: //true == ($a === 1) --> true == (0 === 1) --> true == false --> false
                print "Ez egy 1-es";
                break;
        case $a === '2-es': //true == ($a === '2-es') --> true == (0 === '2-es') --> true == false --> false
                print "Ez egy '2-es' string";
                break;
        case $a === 2: //true == ($a === 2) --> true == (0 === 2) --> true == false --> false
                print "Ez a 2-es";
                break;
        default:
                print "A megadott érték: ".$a;
}

Őrült vagyok, hogy ilyeneken jár az agyam. Tudom. De az őrült gondolatokból születnek sokszor a zseniális ötletek. Remélem valakinek hasznos lesz ez a cikk. Ha másra nem is, altató helyett :)

Kategóriák: 
Megosztás/Mentés