Hlavní navigace

Zaostřeno na PHP (3)

Michal Burda

Toto je poslední (?) díl věnovaný tomu, co nám nové jádro Zend 2 jazyka PHP přinese -- pod drobnohledem se dnes ocitnou konstanty, vnořené třídy (názvové prostory) a výjimky.

Konstanty

Uvnitř tříd je nyní možno pomocí klíčového slova const definovat konstanty:

<?
class Trida {
  const X = 30;

}

echo Trida::X;
?>

Globální konstanty se ale definují pořád stejně:

<?

define('Y', 100);
echo Y;
?>

Vnořené třídy

Mechanismus vnořených tříd je pokusem tvůrců jazyka vnést do PHP něco jako názvové prostory. Problém starého jádra (Zend 1) spočíval v tom, že existovaly jen tři obory platnosti identifikátorů: globální, třídní a obor platnosti identifikátorů uvnitř funkce. Docela jednoduše pak mohla nastat kolize, kdy různé knihovny si nazvaly různé funkce (ale i třídy, konstanty či globální proměnné) stejnými jmény.

Nový Zend 2 přichází s konceptem vnořených tříd, kde každá třída vnitřně představuje zvláštní tabulku symbolů. Uvnitř třídy můžete nyní definovat statické proměnné a funkce, ale i konstanty či další třídy. Může tak vzniknout celá hierarchie názvových prostorů, aniž by identifikátory jednotlivých prvků spolu nějak kolidovaly.

<?
/* Třídy mohou obsahovat třídy */

class Tvary::Kvadr {
  function kresli() {
    echo "kvadr<br>";
  }

}

class Tvary::Koule {
  function kresli() {

    echo "koule<br>";
  }
}

$x = new Tvary::Kvadr();

$x->kresli();
$x = new Tvary::Koule();

$x->kresli();
?>

Pro přístup k lokálním prvkům třídy se zavedl předřaďovač self::. Můžete také používat staré přístupové konvence, kdy místo self:: použijete jméno třídy (tedy např. self::$static­ka_promenna ne­bo

MojeTrida::$sta­ticka_promenna).

Příklad:

<?
/* Práce se statickou proměnnou */

class Trida {
  static $promenna = 0;

  function delej() {

    self::$promenna++;
    Trida::$promenna++;
  }
}

Trida::delej(); // statické volání
echo Trida::$promenna; // vypíše "2"

?>

Pro konstanty a funkce navíc platí, že když je použijete přímo bez uvedení kontextu, bude PHP prohledávat nejprve aktuální třídu a teprve potom (v případě neúspěchu) se podívá do vnější třídy nebo globálního prostoru. Chcete-li uvnitř třídy volat přímo nějakou „globální“ funkci a chcete-li mít jistotu, že se místo ní nezavolá nějaká vnitrotřídní funkce stejného jména, musíte použít předřaďovač main::. Tak například volání main::strlen() vám zaručí, že budete volat funkci strlen() z hlavního oboru platnosti identifikátorů. Ovšem o toto všechno se musíte starat jen tehdy, pokud budete své lokální funkce pojmenovávat stejně jako globální…

Příklad:

<?
/* Lokální funkce mají přednost */

function delej_neco_jineho() {
  echo 'nic...<br>';

}

class Trida {
  function delej() {
    $this->delej_neco_jineho();
    delej_neco_jineho();
  }

  function delej_neco_jineho() {
    echo 'něco...<br>';
  }

}

$x = new Trida();
$x->delej();

?>

…výstupem bude:

něco...
něco...

Další příklad:

<?

/* Práce s konstantami */

define('KONSTANTA', '20');

class Trida {

  const KONSTANTA = 3.14;

  function delej($k) {

    echo "$k<br>";
    echo KONSTANTA . '<br>';
    echo self::KONSTANTA . '<br>';
    echo Trida::KONSTANTA . '<br>';
  }

}

$x = new Trida();
$x->delej(Trida::KONSTANTA);

?>

…výsledkem bude čtyřikrát po sobě hodnota Ludolfova čísla zaokrouhlená na dvě desetinná místa.

Krátká poznámka o statických metodách

V PHP se v definici třídy stále (alespoň jsem o opaku nikde neslyšel) nebudou rozlišovat statické (třídní) a obyčejné metody. Zřejmě nadále bude platit, že v podstatě každou metodu budete moci zavolat jak staticky (Trida::metoda()), tak dynamicky ($objekt->metoda()). Jediný rozdíl mezi oběma voláními je v tom, že při dynamickém volání metody je uvnitř funkce přístupná proměnná $this odkazující na aktuální objekt:

<?
class Trida {
  function delej() {

    if (isset($this)) {
      echo 'dynamické volání<br>';
    } else {

      echo 'statické volání<br>';
    }
  }
}

$x = new Trida();

$x->delej();
Trida::delej();
?>

Jako výsledek můžete čekat:

dynamické volání
statické volání

Výjimky

Některý z tvůrců jazyka PHP se dal slyšet, že „Java programmers would love PHP,“, tj. že programátoři v Javě budou mít PHP rádi. Přestože plamenné debaty provázející tento seriál tomu zatím moc nenasvědčují, nelze přehlédnout, že právě tomuto jazyku se PHP začíná po syntaktické stránce (třebaže přes značné porodní bolesti) nějak nápadně podobat. (Nebo je to radši C++?) Důkazem budiž třeba další jeho nová vlastnost – zpracování výjimek.

Princip výjimek se v jiných jazycích velmi dobře osvědčil. V podstatě jde o umožnění programátorovi předčasně opustit určitou část kódu, pokud se při jeho zpracovávání vyskytne chyba.

Bez mechanismu výjimek se obvykle programuje tak, že každá funkce, která může skončit neúspěchem, vrací nějaký chybový kód, nebo potvrzení, že vše proběhlo v pořádku. Úkolem programátora pak je výsledek každé funkce ručně testovat a na případnou chybu příslušně reagovat. Takové programování pak je velice nudné, těžkopádné, stereotypní, zbytečně prodlužuje zdrojový kód a vůbec – často se na kontroly chybových stavů zapomíná (nebo vykašle) úplně.

Naproti tomu programování s výjimkami předpokládá, že vše probíhá úspěšně. Pouze pro případ chyby (výjimky) se na konec kritického úseku napíše obslužný kód, který na případná selhání nějak reaguje. V PHP podobně jako v Javě se kritický kód označuje klíčovým slovem try, obslužná rutina příkazem catch a výjimka se generuje konstrukcí throw. Vše si předvedeme na příkladu:

<?
/* definice třídy výjimky */
class MojeVyjimka {

  var $hlaseni;

  function __construct($hlaseni) {
    $this->hlaseni = $hlaseni;
  }

  function vypis() {
    echo '<p>Chyba! ' . $this->hlaseni;
  }

}


/* definice funkce, která v případě chyby generuje výjimku */
function deleni($x, $y) {

  if ($y == 0) {
    throw new MojeVyjimka('Dělení nulou!');
  }

  return $x / $y;
}


try {

  // kritický úsek - chceme zachytávat výjimky
  $a = deleni(10, 0);
  echo "Výsledek: $a";

} catch (MojeVyjimka $e) {
  // tento kód se provede jen v případě výskytu výjimky MojeVyjimka
  $e->vypis();

}
?>

V příkladu jsme si nadefinovali svou třídu pro výjimku a obohatili jsme ji základní schopností přenášet nějakou informaci o chybě. Dále jsme vytvořili ukázkovou funkci deleni(), která provádí obyčejnou operaci dělení; navíc však testuje hodnotu druhého operandu a při pokusu o dělení nulou generuje výjimku. Poté už jen následuje kód, který funkci deleni() používá. Blok je uzavřen v příkazu try, který indikuje, že v něm může dojít k výjimce. Co se má dělat v případě výskytu výjimky, říká příkaz catch: pokud se vyskytne výjimka MojeVyjimka, uloží se do proměnné $e a zpracuje se blok příkazů za tímto klíčovým slovem.

Výsledkem ukázky bude pouze chybové hlášení: Chyba! Dělení nulou!

Prosím, všimněte si, že v případě výskytu výjimky se ve zpracovávání kritického úseku nepokračuje, ale ihned se skočí na obslužný kód výjimky, takže text o výsledku operace následující za chybným voláním funkce deleni() se nikdy nevypíše.

Úseků catch může být za sebou hned několik, s tím, že každý obsluhuje jiný druh výjimky. Také, podobně jako v jiných jazycích, i v PHP může být díky dědičnosti definována hierarchie výjimek. Pak platí, že se vždy provede ten blok za příkazem catch, který je označen jménem stejné třídy nebo nadtřídy k třídě generované výjimky.

Pochopitelně, mechanismus výjimek je k ničemu (nebo téměř k ničemu), pokud není hojně podporován samotným jazykem a zejména jeho knihovními funkcemi. Doufejme, že si toho jsou vědomi i tvůrci Zendu 2 a že se dočkáme i toho, že všechny funkce jazyka PHP na tento systém hlášení chybových stavů postupně přejdou.

A to by bylo vše, co se mi o připravovaném PHP podařilo zjistit. Pokud víte ještě o něčem jiném, nezapomeňte se o své znalosti podělit v diskusi. :-)

Našli jste v článku chybu?