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

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.

  1. BlackY

    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

  2. Semmu

    ez nagyon jó, köszi :)

  3. Gyarmati Péterwww.facebook.com/gyarmatipeter

    Ez nagyszerű! Köszi!

Van hozzáfűznivalód? Írj nekem közvetlenül.

Kapcsolat