Ebben a rövid tutorialban bemutatok egy módszert, amellyel anélkül dobálhatunk tetszőleges osztályú kivételeket, hogy azokat előre be kéne töltenünk (és leprogramoznunk).
A megfelelő hibakezelés régóta probléma volt a programozási nyelvekben, a C++ idejében (pontosabban már valamivel korábban, megfelelő forrás híányában most maradjunk a C++-nál, úgyis annak a szintaxisát örökölte a PHP) nyelvi szinten bevezettek erre egy mechanizmust, az ún. exception-öket (kivételek).
A legtöbb kivételeket támogató nyelvben a nyelv alap csomagjai többnyire rengeteg kivételt definiálnak, mivel a kivétel típusa (vagy osztálya, nyelvtől függően) további információval szolgál a hibáról – és ami lényegesebb, a vezérlés további sorsáról.
Egy Java példa:
1 2 3 4 5 6 7 8 9 10 11 | try { double x = 1 / Integer.parseInt(args[1]); } catch(ArrayIndexOutOfBoundsException exception) { /* Nem adták meg a parancssori paramétert */ } catch(NumberFormatException exception) { /* Nem számot adtak meg */ } catch(DivisionByZeroException exception) { /* Nullát adtak meg, azzal nem lehet osztani */ } catch(Exception exception) { /* Valami egyéb hiba */ } |
A lényeg gondolom érthető. PHP-ben ez egy kicsit nehézkesebb lenne, mivel ahhoz, hogy ezt így használjuk 3 új osztályt (ArrayIndex.., NumberFormat… és DivisionBy…) kéne definiálnunk – csak azért, hogy kihasználhassuk a kivételkezelés előnyeit.
Szerencsére a PHP fejlesztői nem úgy kódolták le az értelmezőt, hogy ha nem létező osztályú Exception-t próbálunk elkapni, akkor hibát dobjon, így kicsit szabadabban játszhatunk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class cExceptionGenerator { private static $generatedInstances = array(); public static function generate($className) { if(isset(self::$generatedInstances[strtolower($className)])) { return self::$generatedInstances[$className]; } if(!class_exists($className, false)) { eval('class ' . $className . ' extends Exception { }'); return self::$generatedInstances[strtolower($className)] = $className; } else { return 'Exception'; } } public static function generateAndThrow($className, $message, $code = null) { $class = self::generate($className); throw new $class($message, $code); } } |
Ez az osztály futás közben (az eval-al) hozza létre a kivétel osztályainkat, megfelelő kóddal csak akkor, amikor tényleg szükségünk van rá. Így például a fenti Java kód PHP-beli megfelelője:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php try { if(!isset($argv[1])) { $class = cExceptionGenerator::generate('ArrayIndexOutOfBoundsException'); throw new $class('Nincs 1. elem'); } if(is_numeric($argv[1]) == false) { $class = cExceptionGenerator::generate('NumberFormatException'); throw new $class('Ervenytelen szamformatum'); } if((int)$argv[1] == 0) { $class = cExceptionGenerator::generate('DivisionByZeroException'); throw new $class('Ize...'); } $x = (int)$argv[1] / 2; } catch(ArrayIndexOutOfBoundsException exception) { /* Nem adták meg a parancssori paramétert */ } catch(NumberFormatException exception) { /* Nem számot adtak meg */ } catch(DivisionByZeroException exception) { /* Nullát adtak meg, azzal nem lehet osztani */ } catch(Exception exception) { /* Valami egyéb hiba */ } |
Futás közben ez a megfelelő ágon fog tovább haladni, és csak azokat a kivételeket kellett előállítani, amelyeket valóban használunk is. A másik metódussal (generateAndThrow) rövidebb kódot is lehetett volna írni (pl.: isset($argv[1]) or cExceptionGenerator::generateAndThrow(…)) viszont ekkor a „dobás helye” (ami pontosabban a létrehozás sora) a cExceptionGenerator::generateAndThrow metódus törzsében lesz, így ha nem kapjuk el a, akkor a stack trace-t kell vizsgálnunk hogy megtudjuk, valójában honan jött a kivétel.
Megjegyzés: ugyanez a működési elv elérhető az autoload funkcionalitás kihasználásával, azonban ennek több hátránya is van:
- A szerkesztők nem tudják feloldani a nevet, így nincs auto complete
- Nehezebben olvasható lesz a kód, mivel nem létező osztályokra hivatkozunk benne
- Más fejlesztő/hosszabb-rövidebb kihagyás után sokáig keresgélhetjük az adott kivételosztályokat, míg a kivétel-generáló osztályok esetében elsőre látható (vagy pár kattintás után megnézhető) hogy honnan jön az új osztály
Mindenesetre:
PHP 5.2 és korábbi változatokhoz
1 2 3 4 5 6 7 8 | spl_autoload_register(create_function('$class', "return eval('class ' . \$class . ' extends Exception {}')")); /* és egy ellenőrzés: */ try { throw new GeneratedException('Class generated on the fly'); } catch(GeneratedException $x) { echo $x; } |
Ugyanez PHP 5.3-al és lambda függvényekkel
1 2 3 | spl_autoload_register(function($classname) { return eval('class ' . $classname . ' extends Exception {}'); }); |
Az 5.3-as kódhoz azért még hozzátartozik, hogy névtér kezeléssel együtt egy kicsit bonyolódik a kód:
BlackY
ez nagyon jó, köszi :)
Ez nagyszerű! Köszi!