Best practices pro jmenné prostory v PHP
Pár dobře míněných tipů, jak navrhnout strukturu jmenných prostorů (namespaces) a názvy tříd v PHP 5.3.
Ačkoliv jmenné prostory jsou asi nejznámější novinkou PHP verze 5.3, za
tolik klíčové je nepovažuji. Respektive na seznamu přání, co bych rád
v PHP viděl, by se mezi první desítku dostaly jen s odřenými
oddělovači. Praxe totiž ukazuje, že jmenným konfliktům spolehlivě
předejde použití 1–2 písmenného prefixu, stejně tak jsem se nikdy
nedopustil názvů tříd jako
Zend_Service_DeveloperGarden_Response_ConferenceCall_AddConferenceTemplateParticipantResponseType
(97 znaků, zajímalo by mě, jak s tím dodržují pravidlo o max. délce
řádků 80 znaků
). Avšak PHP kráčí ve
šlépějích Javy a tak tu máme namespaces. Jak s nimi naložit?
Přínos jmenných prostorů
Asi nejsložitější otázka, na kterou si musíte odpovědět, zní: čemu prospěje přejmenovat třídu:
sfForm→Symfony\Component\Form\Form
Otázka je osvědčeným závdavkem k nekonečným flame wars. Z hlediska
pohodlí programátora, intuitivnosti a zapamatovatelnosti je vhodnější
původní stručné a výstižné sfForm. Odpovídá totiž tomu,
jak třídu programátoři označí hovorově, tj. „formulář v symfony“.
Nový a delší název je zase správný z jiných hledisek, u nichž si
ovšem nejsem jist, jestli je běžný uživatel ocení.
Může se tedy stát, že jmenné prostory budou jednou částí programátorské obce vnímány jako skvělá věc, pro kterou je třeba něco obětovat ve „vyšším zájmu“ (srovnej s XHTML), a druhou částí jako něco, co z dobře uchopitelných frameworků udělalo těžkopádné obludy (a tak jsme se na ně těšili).
Jak jmenné prostory rozvrhnout?
Syntaktickou stránku použití popisuje dokumentace, objevit správné vzory však chce praxi, na kterou zatím nebylo dost času. Prostory v PHP mají svá specifika daná celou řadu faktorů, proto není ideální 100% kopírovat konvence používané třeba v Javě nebo .NET. Dá se z nich však dobře vycházet.
Více si řekneme u jednotlivých pravidel pro pojmenování.
1) třída má mít výstižný název i bez uvedení NS
Název každé třídy i bez jmenného prostoru musí vystihovat její
podstatu. Nevhodné by bylo přejmenovat třídu ArrayIterator →
Spl\Iterators\Array, protože pod názvem Array by
člověk neočekával iterátor (pomiňme, že třídu nelze nazvat klíčovým
slovem). A pozor, ani z názvu Spl\Iterators\Array nemusí být
zřejmé, že jde o iterátor, protože nelze sázet na to, že namespace
Spl\Iterators obsahuje pouze iterátory. Mohou tam být třeba
i nějaké pomocné třídy.
Pár příkladů:
- nevhodné:
Nette\Application\Responses\Download– nelze dovodit, že Download je odpověď - nevhodné:
Zend\Validator\Date– předpokládali byste, žeDateje validátor, nikoliv datum? - nevhodné:
Zend\Controller\Request\Http– předpokládali byste, žeHttpje požadavek?
Proto krom specializace tříd je vhodné v názvu ponechat i obecnost:
- lepší:
Nette\Application\Responses\DownloadResponse - lepší:
Zend\Validator\DateValidator - lepší:
Zend\Controller\Request\HttpRequest
Ideální je, pokud existuje jednoslovný a přitom výstižný název. Ten lze vymyslet hlavně u tříd, které reprezentují něco z reálného světa:
- nejlepší:
Nette\Forms\Controls\Button– dvouslovnéButtonControlnetřeba (avšakHiddenControlnaHiddenzkrátit nelze)
2) jmenný prostor má mít výstižný název
Pochopitelně i samotný název jmenného prostoru musí být výstižný a
výhodou je název kratší, bez zbytečností. Takovou zbytečností se mi zdá
např. Component v Symfony\Component\Routing, protože
bez něj by název nijak neutrpěl.
V některých situacích je třeba se rozhodnout mezi jednotným a množným
číslem (Zend\Validator vs Zend\Validators), což je
podobně nerozhodný problém, jako při volbě jednotných a množných čísel
u databázových tabulek.
3) rozlište jmenné prostory a třídy
Pojmenovat třídu stejně jako jmenný prostor (tj. mít třídy
Nette\Application a Nette\Application\Request) je
technicky možné, mohlo by to však programátory mást a lépe se bude tomu
vyhnout. Myslete i na to, jak dobře se bude výsledný kód číst nebo jak
budete API někomu vysvětlovat.
4) omezte zbytečné duplicity (+ parciální namespace)
Ideální je, pokud název třídy a název prostoru neobsahují duplicitně stejnou informaci.
- místo
Nette\Http\HttpRequestradějiNette\Http\Request - místo
Symfony\Component\Security\Authentication\AuthenticationTrustResolverraději tříduTrustResolver
Třída Nette\Http\Request neodporuje pravidlu č.
1 o výstižném názvu třídy i bez uvedení jmenného prostoru, naopak
nám dovoluje elegantně využít parciální namespace:
use Nette\Http; // alias pro namespace
// dostupné jsou všechny třídy via Http:
$request = new Http\Request;
$response = new Http\Response;
// a navíc Http\Response je srozumitelnější než samotné Response
Pokud jmenné prostory chápeme jako balíčky, což je obvyklé, vede to k nešťastnému duplikování posledního slova:
Zend\Form\FormSymfony\Component\Finder\FinderNette\Application\Application
Jmenné prostory také doslova svádí sdružovat třídy (např. různé implementace téhož rozhraní apod.) do vlastních prostorů, což opět vytváří duplicity:
Nette\Caching\Storages\FileStorage– tj. všechny úložiště v samostatném prostoruStoragesZend\Form\Exception\BadMethodCallException– všechny výjimky doExceptionSymfony\Component\Validator\Exception– opět všechny výjimky doException
Sdružující namespaces prodlužují název a vytváří v něm duplicity, protože obecnost často není možné z názvu tříd odstranit (1. pravidlo). Jejich výhodou může být lepší orientace ve vygenerované API dokumentaci (téhož by však šlo dosáhnout i jinak) a snazší dostupnost při použití plnohodnotných IDE s napovídáním. Každopádně je doporučuji používat jen s rozvahou. Například pro vyčlenění výjimek se moc nehodí.
5) nezaměnitelnost tříd z více prostorů
Dle bodu 1) má mít třída výstižný název, což ale neznamená, že má být i jednoznačný v rámci celé aplikace. Obvykle stačí, aby byl jednoznačný v rámci jmenného prostoru. Pokud se však v kódu často používají vedle sebe dvě třídy z různých prostorů, nebo pokud mají mezi sebou jinou podstatnou souvislost, neměly by mít stejný název. Jinými slovy, nemělo by být nutné používat AS v klauzuli USE.
6) jednosměrné závislosti
Zvažte, jaké závislosti mají mezi sebou mít třídy z jednotlivých jmenných prostorů. Snažím se dodržovat:
- pokud má třída ze jmenného prostoru A\B závislost na třídě ze jmenného prostoru A\C, neměla by žádná třída z A\C mít závislost na A\B
- třídy ze jmenného prostoru A\B by neměly mít závislost na třídě z prostoru A\B\C (tohle berte s rezervou)
p.s.: neberte prosím tento článek jako dogma, jde jen o zachycení aktuálních myšlenek
Recenze knihy 1001 tipů a triků pro PHP
Když jsem se dozvěděl, že Jakub Vrána píše knížku 1001 tipů a triků o PHP, samozřejmě mě napadlo pár trapných fórků („1001 vtipů o PHP“, „to jich bude devět?“ nebo „č. 1: znáte Ruby?“) ale pak jsem si uvědomil, jak je to vlastně šíleně velké číslo. Schválně, zkuste vymyslet a napsat padesátku. Dáte to? Já leda s odřenýma játrama. No a pak další. A další. A další! Uff. Když pak Jakub hlásil, že se blíží do třetiny, vzpomněl jsem si na Marvinovo „Nejhorší bylo těch prvních deset miliónů.“
Humor mi došel ve chvíli, kdy mě Jakub požádal, jestli bych mu udělal korekturu. Co si budeme povídat, když váš kamarád napíše knihu, tak ji od něj dostanete, někam ji důmyslně založíte a až se setkáte, tak mu řeknete, že jste ji samozřejmě četli a jako že to fakt bylo dobrý a poplácáte ho po rameni nebo tam někde. Alespoň tak velí bonton. Ovšem když máte dělat korekturu, ocitáte se vstříc hrozbě, že ji doopravdy budete muset přečíst! Že bych mu ji po týdnu vrátil netknutou a bez jediného škrtu („čéče, sem to fakt čet a žádnou vyloženě jako chybu sem tam nenašel…“), o tom jsem vůbec neuvažoval, by mi to nezbaštil. Pustil jsem se do čtení a zjistil, že to je hodně dobrý materiál.

Byť školy nemám a z odborné literatury jsem přečetl jen Pixyho, Kruga
a Koska, cítím se v PHP poměrně zdatný. Co mi může kniha od Jakuba dát?
Ejhle, může. Jakub totiž na vydavatele ušil pěknou kulišárnu, ta kniha je
o PHP spíš mimochodem. Je to prostě hromada zajímavých tipů a nápadů
pro všechny, kdo se zabývají webovými aplikacemi. Navíc originálních
tipů, nejde totiž o tištěnou verzi Jakubova blogu. V žádném případě
nečekejte referenční příručku PHP nebo nudnou přehlídku funkcí, kdepak.
Najdete tu tipy počínaje návodem, jak skrýt emailovou adresu před roboty,
přes generování sitemap až po pádné argumenty, zda mít doménu s
www. nebo bez.
U toho bych se zastavil. Ač s Jakubem máme na mnohé věci odlišné názory, s ním je radost nesouhlasit. On má totiž svůj postoj vždycky podpořený jasnou argumentací. U něj neexistuje „to se dělá tak, protože to tak dělá každý“ nebo „protože tak to dělá Nielsen|Zeldman|Lerdorf|Torvalds|(doplň si svou kapacitu)“, ale protože nad tím přemýšlel a došel k nějakému závěru. A to se jako červená niť táhne celou knihou. Takže ačkoliv bych mohl s něčím nesouhlasit, stěží bych v ní hledal chybu. Takovou preciznost jsem zažil snad jen u knih Douglase Crockforda.
Ale ať jen nechválím – kniha je sbírkou nápadů, které jsou sice
tematicky uspořádané, ale chybí mi tam průvodní slovo, co by z toho
dělalo souvislé čtivo. Chybí mi tam Miloš Frýba. A na můj vkus obsahuje
příliš málo humoru a nadsázky ![]()
Každopádně knihu vřele doporučuji každému PHPčkaři, své si v ní
najdou jak začátečníci, tak matadoři. Za cenu jednoho bifteku není nad
čím váhat. A neříkám to proto, že je v ní zmíněné Nette
V knihkupectvích by měla být od pondělí,
nicméně dokud Jakub
nabízí možnost nechat si ji za stejnou cenu poslat s jeho podpisem, tak
knihkupectví nemají šanci.
Jak se píše generátor API dokumentace?
Dlouhá léta mi ze zdrojáků generoval API dokumentace phpDocumentor. Vývoj tohoto nástroje ustrnul a PHP 5.3 je mu cizí, zejména chybí podpora jmenných prostorů. Začal jsem hledat alternativu. Bohužel žádná sláva.
On ani samotný phpDocumentor nebyl žádný zázrak. Dodáván se sadou šablon, jedna hnusnější než druhá, vedle toho i taková Lupa.cz vypadá jako fešanda. Chápu, „programátoři sobě“, oželel bych nějaké eye-candy, ale šablony jsou tak zoufale nepřehledné a odpudivé, že si neumím představit takovou dokumentaci používat dobrovolně. Užitná hodnota nulová. A nabídka alternativních šablon není.
Vyzkoušel jsem phpDoctor, doxygen a PHP_UML, u kterého jsem
nakonec zůstal. Jeho standardně vygenerovaná dokumentace asi nejvíc
odpovídala tomu, co jsem hledal (ukázka). Jenže
aplikace je taková PEARovská, hned v úvodu člověka přivítá
Notice: Undefined variable: errorLevel, musel jsem se hrabat ve
zdrojácích a opravit několik bugů, strávil moře času úpravou XSL
šablon. Ještě štěstí, že PHP_UML generuje dokumentaci neskutečně
rychleji než phpDocumentor, takže jsem viděl výsledek každého zásahu
takřka ihned a ne až po dlouhých minutách. Stále tomu ale chyběly dost
podstatné věci, a tehdy mě napadlo…
…že si prostě napíšu vlastní generátor. Za čas, co jsem investoval do úprav, jsem ho mohl mít hotový několikrát. Ach ta narušená schopnost používat cizí knihovny.
Pokusím se vysvětlit postup, jak jsem Apigen psal. Dopředu prozradím, že se vešel do pouhých 150 řádků (!) kódu + šablony.
Jak na to
Nejprve: zdrojový kód lze analyzovat buď vlastním parserem, nebo využít
reflection. Jedním
z hlavních požadavků bylo, že generátor bude umět plnohodnotně pracovat
s interními třídami PHP (třeba takto). Což mi jinde
citelně chybělo. Jelikož interní třídy žádný zdrojový kód nemají,
zvolil jsem reflexi. To znamená, že všechny třídy, pro které chci
generovat dokumentaci, musím načíst. Projít adresářovou strukturu třeba
Finderem a postupně volat
require pro každý soubor nejde – mezi třídami jsou
závislosti a je nutné je načítat v pořadí: rozhraní, rodiče, potomci.
Nelehký úkol, ale vyřešil jej RobotLoader:
$robot = new Nette\Loaders\RobotLoader;
$robot->addDirectory($dir); // adresář, který chceme dokumentovat
$robot->register(); // zapne autoloading
// getIndexedClasses vrací seznam nalezených tříd a souborů
foreach ($robot->getIndexedClasses() as $file) {
require_once $file;
}
$robot->unregister();
Když nyní načtu třídu, která má vazbu na dosud nenačtenou třídu nebo rozhraní, přijde ke slovu RobotLoader a situaci vyřeší. Takže to bychom měli. Paráda.
Když operuješ sám sebe
Avšak objevil se tu oříšek. Jak generovat API dokumentaci pro knihovnu, kterou pro generování API dokumentace používám? Slepice a vejce hadr. Řešení vidím dvě: buď použít jako vstup přímo tu kopii Nette, kterou používá generátor, nebo vytvořit alternativní vesmír, kde se Nette nejmenuje Nette. Druhý způsob se mi zdá rozumnější, nakonec ho používám pro generování distribučních balíčků Nette. Jde o to, že generátor používá knihovnu NetteX, která se liší jen v tom, že sídlí v „oiksovaném“ jmenném prostoru.
Ano, anotace
Aby byl model plnohodnotný, potřeboval jsem přihlížet k anotacím.
Například vynechat všechny elementy s anotací @internal,
určovat vrácené hodnoty metod dle anotace @return atd. Tady jsem
opět využil skutečnosti, že Nette Framework podporuje anotace a pro
reflektování použil Nette\Reflection.
$class = new Nette\Reflection\ClassReflection($name);
if ($class->hasAnnotation('internal')) {
...
}
Třídy z Nette\Reflection podporují tzv. properties,
tudíž se příjemněji používají v šablonách, kde místo
$tmp = $method->getAnnotations();
foreach ($tmp['params'] as $value) ...
mohu psát stručnější
foreach ($method->annotations['params'] as $value) ...
Nakonec jsem pro reflexi tříd použil vlastního potomka
Nette\Reflection\ClassReflection s přidanou funkcionalitou.
Přičemž Nette automaticky zajišťuje, aby metody jako
getDeclaredClass() opět vracely instanci mé třídy.
Svěží ![]()
Ša-la-la-blo-ny
Při generování HTML souborů jsem naplno využil sílu šablonovacího jazyka Latte. Ten lze používat nejen ve spojitosti s MVC aplikacemi, ale naprosto kdekoliv. Využil jsem oddělený layout, dědičnost bloků i tzv. n:attributy. Výsledkem jsou skutečně dobře čitelné šablony. Příklad:
{* $implementers je pole objektů ClassReflection *}
<div n:if="$implementers">
<h4>Direct Known Implementers</h4>
{foreach $implementers as $item}
<a href="{$item|classLink}">{$item->name}</a>{sep}, {/sep}
{/foreach}
</div>
Uvedený <div> se vykreslí pouze v případě, že pole
$implementers je neprázdné. Jednotlivé položky vykreslí jako
odkazy oddělené čárkou. A makro {sep}...{/sep} zajistí, aby
se čárka (tj. separátor) neobjevila za poslední položkou. Živé to
můžete vidět třeba tady.
Jiným příkladem je šablona, která vygeneruje v JavaScriptu seznam všech tříd pro potřeby našeptávače (výsledek):
// $classes je opět pole objektů ClassReflection
{contentType javascript}
var classes = {$classes|values|map:'return $value->name;'};
Pro generování obarvených zdrojových kódů jsem použil skvělou knihovnu FSHL, kde stačilo doplnit seznam klíčových slov PHP 5.3. Obsah doc-bloků jsem zkusil formátovat pomocí Texy ve spojení s FSHL a výsledek se mi zdá dostačující.
Světlo světla spatřil Apigen
Naprogramovat celý generátor trvalo pár hodin, což je řádově méně, než bych strávil úpravou existujících knihoven. Výsledkem totiž bylo, jak jsem zmínil, pouhých 150 řádků kódu. Ač jsem ho nijak rychlostně neoptimalizoval, dokumentaci k Nette vygeneruje za cca 11s, zatímco phpDocumentor se s tím trápí přes dvě minuty. Ani jsem nečekal, že se Nette Framework tak výborně hodí na tak netypický úkol.
Mnohem víc času jsem pak strávil vylaďováním šablon, připojil jsem
jQuery a hrál si s tříděním metod a dalšíma opičkama. Generátor jsem
opatřil rozhraním pro ovládání z příkazové řádky, přidal další
fíčůrky a dopsal komentáře, takže v tuto chvíli je těch řádků
určitě alespoň jednou tolik ![]()
Jestli chcete, tak si Apigen můžete stáhnout.
Tentokrát bez jakékoliv technické podpory. Určitě neumí spoustu věcí, které by se vám tuze hodily a bez nich to nemá smysl a ostatní generátory je mají a vůbec, tak si je prosím doprogramujte.
Programátoři chyby neignorují
Tedy alespoň by neměli. PHP je jazyk s poměrně laxním přístupem k chybám a tudíž vyžaduje od programátora vyvinout větší úsilí při jejich ošetřování. Nenechte si namluvit opak. Článek je reakcí na dobře míněnou radu Jakuba Vrány.
Existují dva tradiční způsoby, jak chyby oznamovat:
- vyhozením výjimky
- návratovou hodnotou
Takřka všechny knihovny dodávané s PHP používají druhý způsob, protože výjimky byly do jazyka zavedeny až v páté verzi. Což je v mnoha případech na škodu. Programátor totiž musí neustále otrocky kontrolovat návratové hodnoty a ošetřovat chybové stavy. A zároveň je tu riziko, že na to zapomene. Přesněji řečeno, ani ne tak „zapomene“, jako spíš se na to „vy…kašle“, protože to ve světě PHP platí za normu. Nakonec, podívejte se třeba do dokumentace fread a spočítejte, kolikrát v příkladech ošetřili návratovou hodnotu jakékoliv funkce.
Při takovém stylu práce pak není divu, že programátoři považují výjimky za něco otravného, co se (cituju Jakuba) „nedá ignorovat“, zatímco „chyby indikované návratovou hodnotou ignorovat lze a program nejspíš nějak pracovat bude.“ Notykrávo.
Co přesně znamená ono nějak pracovat? Třeba:
// přesouváme z disku na disk
copy('c:/oldfile', 'd:/newfile');
unlink('c:/oldfile');
// pokud první operace selže, soubor se nenávratně smaže
$link = new mysqli('localhost', $user, $pass, 'eshop');
...
$link->query('USE testdata'); // přepneme se z ostré do testovací databáze
$link->query('DELETE FROM orders');
// pokud předchozí operace selže, vymažou se objednávky z ostré databáze
// BTW věřili byste, že výchozí nastavení query() při chybě nevyhodí ani noticku?
// smažeme soubor z adresáře 'test'
ftp_chdir($connection, 'test');
ftp_delete($connection, 'database.sdb');
// pokud předchozí operace selže, vymaže se kupříkladu ostrá databáze
A pak jsou případy, kdy nějak pracovat znamená zařvat s neošetřitelnou fatální chybou:
// načtení konfigurace
$defaults = array('host' => 'localhost', 'charset' => 'utf8');
$config = parse_ini_file('config.ini');
$config = $config + $defaults;
// pokud předchozí operace selže a vrátí FALSE, skončí sčítání fatální chybou
function saveConfig(array $config) { ... }
$config = parse_ini_file('config.ini');
saveConfig($config);
// pokud předchozí operace selže a vrátí FALSE, skončí opět fatální chybou
mysqli_connect('localhost', $user, $pass)
->query("SET NAMES 'utf8'");
// pokud první operace selže, skončí opět fatální chybou
Bastlení zdar!
Víte, komu ublížil mod_rewrite?
Schválně, který software má v dokumentaci uvedeno, že se jedná o voodoo? No jistě, jde o mod_rewrite. Ze zkušenosti mohu říci, že programátoři se dělí do dvou skupin:
- ti, kteří mod_rewrite nerozumí
- ti, kteří si myslí, že mod_rewrite rozumí, avšak mýlí se
Do které skupiny patříte vy? Zkuste nahlédnout do svých souborů
.htaccess a podívejte se, zda vám u pravidel pro
přesměrování (příznak
R) nechybí také příznak NE (noescape)?
Vysvětlím na příkladu: do kořenového adresáře webu
www.example.cz vložím soubor .htaccess s pravidlem
pro přesměrování:
RewriteEngine On RewriteRule .* http://www.example.com/$0 [R=301] #tohle je spatne!
Server pak přesměruje
- z
http://www.example.cz/index.php?title=d%C3%ADvka(parametrtitleobsahuje slovodívka) - na
http://www.example.com/index.php?title=d%25C3%25ADvka(parametrtitleobsahuje řetězecd%C3%ADvka)
Jak vidíte, mod_rewrite ublížil dívce! Je to jeho přirozené chování,
aby to nedělal, musíte mu říct NE:
RewriteEngine On RewriteRule .* http://www.example.com/$0 [R=301,NE] #tohle uz je spravne
novější články
