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:
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:
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:
<?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
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
spl_autoload_register(function($classname) {
return eval('class ' . $classname . ' extends Exception {}');
});
Harder
Profil
Korábbi hozzászólások (3)
A régi blogon érkezett hozzászólások archívuma. Új hozzászólást már nem lehet írni.
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:
if(!defined('NAMESPACE_SEPARATOR')) {
define('NAMESPACE_SEPARATOR', '\\');
}
spl_autoload_register(function($classname) {
list($namespace, $classname) = explode(NAMESPACE_SEPARATOR, $classname);
if(!$classname) {
$code = 'class ' . $namespace . ' extends Exception {}';
} else {
$code = 'namespace ' . $namespace .'; class ' . $classname . ' extends \Exception {}';
}
return eval($code);
});
BlackY
ez nagyon jó, köszi :)
Ez nagyszerű! Köszi!
Van hozzáfűznivalód? Írj nekem közvetlenül.
Kapcsolat