Nette\Object
Jak známo, že objekty se do jazyka PHP dostaly spíš jako nezvaní hosté.
Docela příznačným rozdílem mezi jazykem, který byl jako objektový již
navrhován, a jazykem, který se jím stal k velkému překvapení samotných
tvůrců, je v absenci resp. přítomnosti společného předka všech tříd.
Object Pascal deklaruje TObject, DOT.NET má
System.Object a v Javě nebo Ruby stojí v hierarchii nejvýše
Object (je delikátní, že nejzákladnější třída
bývá pojmenována objekt). V PHP nic takového neexistuje.
Základní třída obvykle deklaruje metody pro sebereflexi. Například
metodu vracející název třídy. Je to sice detail, ale vždycky mi připadalo
tuze ošklivé mixovat hezký objektový kód s voláním
pro-objektové funkce get_class:
$a = $obj->myMethod();
$class = $obj->getClass(); // hezké
$class = get_class($obj); // ošklivé
Nekonzistentní generování chyb
Teď trošku odbočím. Objektový model a vůbec chování jazyka PHP mi v mnoha směrech nevyhovuje. Třeba takový přístup k nedeklarovaným členům. To je téměř vždy známka vážné chyby v programu, způsobené třeba překlepem v kódu. Jenže PHP na ni reaguje značně nekonzistentně:
$obj->undeclared = 1; // projde bez hlášení
echo $obj->undeclared2; // generuje Notice
MyClass::$undeclared = 1; // generuje Fatal error
$obj->undeclared(); // generuje Fatal error
Což potvrzuje názor, že chyby úrovně Notice patří k těm nejdůležitějším a nesmí se při ladění vypínat. Druhým extrémem je Fatal error, který by se spíš měl jmenovat Fatal disaster. Proč? Protože jej nelze obsloužit uživatelským skriptem. Nemáme možnost vygenerovat zprávu pro uživatele. Ten zírá na prázdnou stránku a netuší, co se děje. A hlavně, PHP nemá ani tolik slušnosti, aby odeslalo HTTP kód 500, takže rozbitou stránku (v nejhorším i s chybovou hláškou) zaindexují vyhledávače. PHP fůůůj!
Velice by se hodilo, kdyby přístup k nedeklarovaným členům vygeneroval výjimku. Takovou, která se nechá probublat až nahoru, kde ji zachytí krizová bariéra a promění v korektní hlášení.
Skvělé „vlastnosti“
Zase odbočím. Termínem property se označují speciální členy tříd, které umožňují pracovat s metodami tak, jako by to byly proměnné. Mám na mysli tzv. gettery a settery:
$obj->caption = 'Hello World';
// přeloží se na $obj->setCaption('Hello World')
echo $obj->caption;
// přeloží se na volání $obj->getCaption();
To je nesmírně šikovná věc. Zvenku to vypadá jako obyčejná proměnná, ale přístup k ní máme plně pod kontrolou. Můžeme validovat vstupy. Můžeme generovat výstupy, až když jsou skutečně potřeba. A navíc, pokud neexistuje setter, tak máme read-only proměnnou, což nelze u obyčejné proměnné zajistit.
Property podporují všechny moderní objektově orientované jazyky, v PHP však nejsou a na jejich brzkou implementaci bych si ani nesázel.
Extension method
Ještě jednou odbočím. Naposled. Fakt.
Novinkou třetí verze C# jsou extension method. Jedná se o obdobu prototypování, které znáte z JavaScriptu nebo Ruby. Umožňují připsat nové metody do již existující třídy (JavaScript a Ruby dovoluje metody nejen připsat, ale i přepsat, což však považuji za zlo).
K čemu je to dobré? Máme třeba třídu List reprezentující seznam
položek, která implementuje metody pro jejich přidávání, rušení a
podobně. Z této třídy je odvozena hierarchie podtříd. V praxi zjistíme,
že by se nám docela hodila metoda List::shuffle() pro náhodné
zamíchání pořadí položek. Jenže kód třídy List nemůžeme modifikovat
(je třeba součástí cizí knihovny). Vytvořit potomka, který by zmíněnou
metodu měl, také nepomůže, protože bychom museli upravit stávající kód,
co objekty List vytváří. A navíc by to neřešilo absenci metod
v hierarchii tříd z List odvozených.
Řešením je vytvořit samostatnou funkci, které objekt předáme a ona na
něm operaci vykoná. Jen místo $list->suffle() je třeba volat
Tools::suffleList($list).
Extension methods jsou rozšíření jazyka, které doplňování metod do
tříd na ryze syntaktické úrovni umožní. Můžeme v programu psát
$list->suffle() a interpreter bude tiše volat
Tools::suffleList($list).
Nette\Object
Po několika odbočkách jsem zpět u diskutované nejvyšší třídy hierarchie. Myslíte, že něco takového PHP potřebuje? S přihlédnutím k celkové koncepci jazyka bych řekl, že ani ne. Ale co kdyby existence této třídy dokázala vyřešit všechny zde zmíněné problémy a rozšířit objektový model jazyka o property a extension method? V tom případě – sem s ní!
Nette\Object je onen zázračný táta všech objektů (dříve NObject). Nenechte se zmást prefixem, nejde o základní třídu jen pro Nette. Používám ji v Texy, dibi a vůbec ve všech svých aplikacích, u tříd, z nichž lze vytvářet objekty.
Samozřejmostí je podpora sebereflexe:
class MyClass extends Object {}
// včetně jmenného prostoru:
// class MyClass extends Nette\Object
$obj = new MyClass();
$class = $obj->getClass();
$has = $obj->getReflection()->hasMethod('test');
Následuje příklad použití vlastností (property):
class Circle extends Object
{
private $radius;
public function getRadius()
{
return $this->radius;
}
public function setRadius($radius)
{
// validate value
$this->radius = max(0, (float) $radius);
}
public function getArea()
{
return $this->radius * $this->radius * M_PI;
}
}
$circle = new Circle;
$circle->radius = 12; // as $circle->setRadius(12);
echo "Radius: $circle->radius"; // as $circle->getRadius();
echo "Area: $circle->area";
// but $circle->area = XXX; throws Exception
Technickou stránku implementace jsem se snažil co nejlépe promyslet. Špatně navržená cesta by mohla narušit zapouzdření objektu, mohla by veřejně zpřístupnit protected a private metody a podobně. Toho se v případě Nette\Object obávat nemusíte. Property fungují pouze jako syntactic sugar, jako něco, co může zpřehlednit kód a těšit programátora, ale pokud nechcete, tak to používat nemusíte. Proto platí, že
- getter i setter musí být veřejná metoda
- getter je povinný, setter volitelný
- názvy jsou citlivé na velikost písmen (case-sensitive)
Možná vás napadá, jestli i metody sebereflexe lze volat „oslazené“. Jasně:
echo "Class: $obj->class"; // $obj->getClass();
$has = $obj->reflection->hasMethod('test');
A nakonec extension method:
class MyClass extends Object
{
public $a;
public $b;
}
// declare method MyClass::join()
function MyClass_prototype_join(MyClass $_this, $separator)
{
return $_this->a . $separator . $_this->b;
}
echo $obj->join(' ');
I zde jsem uvažoval nad několika způsoby implementace, nakonec jsem zvolil tento vycházející ze zvyklostí JavaScriptu. Deklarace v podobě globální funkce má tu výhodu, že unikátnost je zaručena přímo jazykem a existenci lze ověřovat klasickými prostředky.
Nette\Object kultivuje jazyk
Používám Nette\Object jako předka všech tříd, protože mi to usnadňuje vývoj (překlep vyhodí výjimku atd). Jde o třídu dostatečně transparentní, neměla by způsobovat žádné kolize, proto ji můžete jako základní třídu používat i ve svých aplikacích.
Viz také:
