Společně s vydáním Nette Framework 2 beta dochází
k úpravě jmenných prostorů a názvů tříd. Je jasné, že
přejmenovávání nepatří mezi populární úpravy, nicméně snažil jsem se
udělat vše pro to, aby přechod byl bezbolestný.
Jaké jsou vlastně důvody?
Nette Framework je dost možná prvním PHP frameworkem na světě, který
používání jmenných prostorů zavedl. Kvůli prodlevám s vydáním PHP
5.3 se začaly v praxi používat docela pozdě a tudíž mají stále svou
první a dnes již historickou podobu. Ta neodpovídá současnému stavu
frameworku, navíc pojmenování tříd bylo často ovlivněno kompatibilitou
s verzí PHP 5.2. Před vydáním nové verze frameworku je záhodno stav
napravit.
Zpětná kompatibilita
Úpravy se týkají především verze frameworku pro PHP 5.3. Ve verzích
pro 5.2 dochází jen k drobným změnám, a to proto, aby se zbytečně
nerozevíraly nůžky mezi 5.3 a 5.2 verzemi.
Nejspíš by k tak radikálním krokům ani nedošlo, kdyby nebylo možné
přechod zcela zautomatizovat. K dispozici proto máte skript, který všechny
změny ve vašich zdrojových souborech udělá za vás! Najdete jej
v distribučním balíku v
tools/Code-Migration/class-updater.php.
Parametry pro spuštění:
php class-updater.php -d /cesta/k/app [-f]
kde /cesta/k/app nahradíte za skutečnou cestu ke složce s vašimi
zdrojovými soubory. Pokud neuvedete nepovinný parameter -f,
skript běží v read-only režimu a jen vypíše, které soubory by
změnil.
DŮLEŽITÉ UPOZORNĚNÍ: nezapomeňte si před použitím zazálohovat
zdrojové kódy a po konverzi ověřit porovnáním, zda záměny proběhly
v pořádku.
Skript vyžaduje pro spuštění PHP 5.3 a najdete jej jen v 5.3 balíku.
Lze pomocí něj konvertovat všechny verze Nette Framework (tj. pro
5.3 i pro 5.2).
Pokud z nějakého důvodu nemůžete soubory aktualizovat, můžete
využít aliasování tříd. Stačí ve své aplikaci hned po načtení
frameworku načíst skript compatibility-aliases.php, který
najdete opět v tools/Code-Migration. Příklad v
bootstrap.php:
// načteme framework
require LIBS_DIR . '/Nette/loader.php';
// načteme aliasy
require LIBS_DIR . '/compatibility-aliases.php';
Nette\Debug::enable(); // fungují původní názvy tříd
Aliasování funguje pouze pro Nette Framework ve verzi pro PHP 5.3.
Pravidla pojmenování
Novou podobu názvů tříd jsem chtěl podřídit, jako v podstatě vše
v Nette, pohodlí programátora. Jak ukázala řada diskusí, subjektivní
požadavky mohou být rozporuplné. Pro někoho znamená pohodlí mít co
nejkratší názvy tříd, pro jiného jsou to názvy co nejvíce popisné.
Přehled myšlenek, ze kterých se vycházelo, najdete v článku Best practices pro jmenné
prostory v PHP.
Nyní by všechny třídy měly mít výstižný název i bez uvedení
jmenného prostoru, preferovány jsou kratší názvy a lze pěkně využít
parciální namespaces:
use Nette\Http; // alias pro namespace
// dostupné jsou všechny třídy via Http:
$request = new Http\Request;
$response = new Http\Response;
Nová struktura pro 5.3
Nejlépe si novou strukturu můžete prohlédnout přímo v API dokumentaci. Důležité změny
jsou tyto:
výjimky, které byly bez namespace, jsou nyní
v prostoru Nette
Nette\Debug je teď Nette\Diagnostics\Debugger
(což je dlouhé, zvažuje se alias např. NDebug)
Presenter & Control jsou ve jmenném prostoru
Nette\Application\UI, routery
v Nette\Application\Routers
z AppForm se stal Nette\Application\UI\Form
String → Nette\Utils\Strings a
ArrayTools → Nette\Utils\Arrays
Finder, Json, Neon,
Paginator a Html jsou nyní taky v
Nette\Utils (třída Tools už není)
Nette\Mail\Mail → Nette\Mail\Message
prostor Nette\Templates přejmenován na
Nette\Templating
ITranslator je nyní v prostoru
Nette\Localization
úložiště pro cache jsou v Nette\Caching\Storages
low level komponenty jsou v Nette\ComponentModel
Jak bylo vysvětleno výše, nemusíte se přechodu obávat, konverzi za vás
udělá utilitka.
A pak ještě několik tříd, konverzi opět udělá utilitka.
Přechod na verzi 5.3 se
jmennými prostory
PHP ve verzi 5.2 je zatím stále podporováno, ale rozhodně doporučujeme
aktualizovat na verzi pro PHP 5.3 se jmennými prostory. S migrací vám
pomůže nový nástroj tools/Code-Migration/migrate-53.php.
Příkazová řádka i všechny upozornění jsou stejné, jako v případě
nástroje class-updater.php. Enjoy!
Pár dobře míněných tipů, jak navrhnout strukturu
jmenných prostorů (namespaces) a názvů tříd.
Jmenné prostory jsou asi nejznámější novinkou PHP verze 5.3. Jejich
hlavním smyslem je zabránit jmenným konfliktům a umožnit zkracovat
(aliasovat) názvy tříd pro používání v jednom souboru. V praxi se
ukazuje, že konfliktům lze předejít i použitím 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í své 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í.
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, že
Date je validátor, nikoliv datum?
nevhodné: Zend\Controller\Request\Http – předpokládali
byste, že Http je požadavek?
Proto krom specializace tříd je vhodné v názvu ponechat i obecnost:
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.
Ideální je, pokud název třídy a název prostoru neobsahují duplicitně
stejnou informaci.
místo Nette\Http\HttpRequest raději
Nette\Http\Request
místo
Symfony\Component\Security\Authentication\AuthenticationTrustResolver
raději třídu TrustResolver
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\Form
Symfony\Component\Finder\Finder
Nette\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 prostoru Storages
Zend\Form\Exception\BadMethodCallException – všechny
výjimky do Exception
Symfony\Component\Validator\Exception\BadMethodCallException –
opět všechny výjimky do Exception
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
S pomocí jQuery lze vytvořit HTML element docela jednoduše:
var $el = $('<a href="https://phpfashion.com">blogísek</a>');
Do proměnné $el se přiřadí objekt jQuery obalující
vytvořený HTML element (proto jsem použil dolar v názvu proměnné),
k nativnímu DOM objektu se dostanete přes $el[0].
Co ale v případě, že potřebujeme jako hodnoty použít proměnné?
Přímočaré řešení by vypadalo takto:
// špatně: riziko XSS
var url = 'https://phpfashion.com';
var title = 'blogísek';
var $el = $('<a href="' + url + '">' + title + '</a>');
Jenže, jak uvádí komentář v kódu, koledujeme si tímto o průšvih
jménem Cross-site scripting (XSS). Stačí, aby například proměnná
url obsahovala uvozovky a výsledek bude nezamýšlený. Proto je
nutné proměnné escapovat,
tj. nahradit znaky mající speciální význam za HTML entity. Na to si
můžeme napsat funkci escapeHtml:
// správné, ale ne příliš přehledné
var escapeHtml = function(s) {
return s.replace('&', '&').replace('"', '"')
.replace("'", ''').replace('<', '<');
};
var $el = $('<a href="' + escapeHtml(url) + '">' + escapeHtml(title) + '</a>');
Skládání řetězců postrádá onu lehkost a srozumitelnost první
ukázky, nemluvě o riziku, že escapeHtml zapomeneme zavolat.
Naštěstí jQuery od verze 1.4 nabízí elegantní řešení: proměnné
uvedeme ve druhém parametru (viz dokumentace)
// dokonalé
var $el = $('<a>', {
href: url,
text: title
});
Příjemné je, že takto lze kromě HTML atributů definovat i kaskádové
styly a události:
„Ty bys měl školit Nette,“ řekl mi Vašek WebExpo Stoupa a já se pak
přes půl roku rozhoupával, než v listopadu 2008 uskutečnil první
školení Vývoj
webových aplikací v Nette Framework. Od té doby prošlo kurzem asi
300 kolegů programátorů.
„Ty bys měl školit jQuery,“ řeklo mi pár účastníků školení
Nette a já se pak přes půl roku rozhoupával, než vypsal první školení jQuery a AJAX, na
které vás tímto zvu. (Než jsem se rozhoupal k napsání tohoto článku,
kurz je z poloviny naplněn. Málo houpu.)
Co vám kurz dá? Vím, že to zní jako otřepaná fráze, ale rád bych
ukázal, že oživování webové stránky pomocí JavaScriptu může být
skutečně zábavné. Seznámím vás s nejpopulárnějším JavaScriptovým
frameworkem jQuery na praktických příkladech.
Přitom budeme dbát na čistý návrh a znovupoužitelnost, protože ve
chvíli, kdy programátor zabředne do bastlení, je se zábavou smyčec. Nebo
šmitec. Celým kurzem se potáhne ústřední motiv user experience,
tj. vysvětlíme si a ukážeme, kterou cestou se vydat, aby výsledek byl pro
uživatele co nejintuitivnější. Protože o nic jiného vlastně nejde.
Bylo by fajn, kdybyste na školení vyrazili se základní znalostí
JavaScriptu (stačí rozumět tomuto
článku bez kapitol Properties a Dědičnost). Pokud zatím neznáte jQuery
a rádi byste to napravili, jsem vám k službám!
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.
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, jednahnusně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…
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 🙂
Jak jste mohli zaregistrovat, něco se událo v číslování
verzí Nette Framework. Ještě před týdnem se mohutně pracovalo na verzi
1.0, nicméně na WebExpu jsem představil verzi 2. Co se děje?
Žádné obavy, stalo se jen to, že verze doposud označovaná jako
1.0 se přejmenovala na 2.0. Povýšení přitom nemá žádný
vliv na vývoj. Beta verze zahrnující všechny chystané novinky i stabilní
verze vyjdou letos. Jen prostě nikdy nebude existovat verze Nette
Framework 1.0.
Vynechání čísla verze není nic neobvyklého, například po Microsoft
Windows 3.1 následoval skok na číslo 95, později ještě větší skok na
2000, aby se poté odečtením hodnoty 1993 vše vrátilo do jednociferných
kolejí s Windows 7. Přičemž verze 4, 5 nebo 6 nás asi teprve
čekají 😉
Nette Framework 2 přichází s celou řadou novinek, s nimiž vás brzy
seznámím prostřednictvím seriálu na serveru Zdroják nebo na školení.
Vymyslet dobré API je někdy neskutečný porod. Vedle toho skutečný porod
je procházka růžovou ordinací. Snad dva roky jsem neustále překopával
třídu na procházení adresářů. A stále nebyl spokojen. Přitom taková
blbost. Existuje totiž spousta variant toho, co a jak hledat, které soubory
vracet, které adresáře vracet a které procházet rekurzivně, nebo naopak
kterým se vyhnout. Také jsem potřeboval řešit specifické situace,
kupříkladu když během procházení adresářovou strukturou teprve
zjišťuji dodatečná pravidla. Otázkou bylo, jak to navrhnout univerzálně a
pokud možno srozumitelně.
Výsledkem snažení je třída Nette\Finder, jejíž API není dokonalé,
ale je zatím asi to nejpoužitelnější, k jakému jsem se dopracoval.
Můžete si ji stáhnout na GitHubu.
Pár příkladů použití:
// nerekurzivní hledání souborů *.txt v adresáři $dir
foreach (Finder::findFiles('*.txt')->in($dir) as $key => $file) {
echo $key; // $key je řetězec s názvem souboru včetně cesty
echo $file; // $file je objektem SplFileInfo
}
// rekurzivní hledání souborů *.txt
foreach (Finder::findFiles('*.txt')->from($dir) as $file) {
echo $file;
}
// hledání podle více masek a dokonce z více adresářů v rámci jedné iterace
foreach (Finder::findFiles('*.txt', '*.php')->in($dir1, $dir2) as $file) {
}
// rekurzivní hledání souborů *.txt obsahujících číslici v názvu
foreach (Finder::findFiles('*[0-9]*.txt')
->from($dir) as $file) {
}
// rekurzivní hledání souborů *.txt kromě těch, co obsahují v názvu X
// pozn.: exclude se tu vztahuje na findFiles()
foreach (Finder::findFiles('*.txt')->exclude('*X*')
->from($dir) as $file) {
}
// rekurzivní hledání souborů *.txt umístěných v adresáři
// začínajícím na "te" ale nikoliv "temp"
foreach (Finder::findFiles('te*/*.txt')->exclude('temp*/*')
->from($dir) as $file) {
}
Omezit hloubku procházení lze metodou limitDepth().
Kromě souborů lze hledat i adresáře přes
Finder::findDirectories('subdir*') nebo obojí
Finder::find('file.txt'). V takovém případě se maska vztahuje
na soubory, nikoliv adresáře.
Adresáře, kterým se chceme zcela vyhnout, uvedeme za klauzulí
„from“:
// tady se exclude vztahuje na klauzuli "from"
foreach (Finder::findFiles('*.php')
->from($dir)->exclude('temp', '.git') as $file) {
}
Nejen maskou lze výsledky filtrovat:
// prochází soubory v rozmezí 100B až 200B
foreach (Finder::findFiles('*.php')->size('>=', 100)->size('<=', 200)
->from($dir) as $file) {
}
// prochází soubory změněné v posledních dvou týdnech
foreach (Finder::findFiles('*.php')->date('>', '- 2 weeks')
->from($dir) as $file) {
}
// prochází soubory PHP s počtem řádku větším než 1000 filtrujeme callbackem
$finder = Finder::findFiles('*.php')->filter(function($file) {
return count(file($file->getPathname())) > 1000;
})->from($dir);
V Nette lze jít dál a třídu Nette\Finder skrze extension
methods dále rozšiřovat a poté můžete třeba:
// hledat obrázky s rozměry většími než 50px x 50px
foreach (Finder::findFiles('*')->dimensions('>50', '>50')
->from($dir) as $file) {
}
Třída funguje na Windows i Linuxu a je napsána co nejoptimálněji, měla by
tudíž fungovat velmi rychle a neprochází zbytečně adresáře, které
nemá. Enjoy!
Otázka, kterou si klade řada webmasterů: vnímají vyhledávače tyto URL
jako stejné? Jak s nimi naložit?
http://example.com/article
http://example.com/article/
http://example.com/Article
https://example.com/article
http://www.example.com/article
http://example.com/article?a=1&b=2
http://example.com/article?b=2&a=1
Stručná odpověď by byla: „URL jsou odlišné.“ Chce to ale
podrobnější rozbor.
Z pohledu uživatelů se tyto adresy liší v drobnostech, kterým
nepřikládají žádnou váhu. Tedy je vnímají jako stejné, ačkoliv
z technického hlediska jde o adresy různé. Říkejme jim třeba adresy
podobné. V zájmu uživatelského
prožitku proto dodržujte 2 zásady:
Nedovolte, aby se na podobných adresách nacházel odlišný obsah.
Jak záhy ukážu, nevedlo by to jen ke zmatení uživatelů, ale
i vyhledávačů.
Umožněte uživatelům přístup i přes podobné adresy.
Pokud se adresy liší v protokolu http / https
nebo doméně s www či bez, považují je vyhledávače za
různé. Nikoliv tak uživatelé. Bylo by tedy fatální chybou na takto
podobné adresy umístit různý obsah. Nicméně chybou by bylo
i znemožnění přístupu přes podobnou adresu. Musí tedy fungovat
adresa s www i bez www. Přičemž SEO nabádá
držet se jedné varianty a ostatní na ni přesměrovávat HTTP kódem 301. To
lze u www subdomény zajistit například souborem
.htaccess:
# presmerovani na variantu bez www
RewriteCond %{HTTP_HOST} ^www\.
RewriteRule ^.*$ http://example.com/$0 [R=301,NE,L]
# presmerovani na variantu s www
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^.*$ http://www.example.com/$0 [R=301,NE,L]
Schválně si hned vyzkoušejte, jestli vaše servery přesměrovávají a to
včetně celé adresy a správného
předání parametrů. Nezapomeňte i na varianty
www.subdomena.example.cz. Protože některé prohlížeče umí
chybějící přesměrování obcházet, zkuste raději nízkoúrovňovou
službu jako Web-Sniffer.
Velká a malá písmena se v URL rozlišují všude kromě schéma a
domény. Avšak uživatel je opět nerozlišuje a je tedy nešťastné nabízet
rozdílný obsah na adresách lišících se jen velikostí písmen. Jako
špatný příklad může posloužit Wikipedie:
Kuriózní chybou trpí Bing, který vrací stejné URL, ať už hledáte
kyselinu nebo databázi (ačkoliv textový popis je správný). Google nebo
Yahoo tímto problémem netrpí.
Bing nerozlišuje mezi kyselinou a databází
Také některé služby (webmaily, ICQ) převádí velká písmenka v URL na
malá, což jsou všechno důvody, proč se rozlišování velikosti vyhnout a
to nejlépe i v parametrech. Raději dodržujte konvenci, že všechna
písmenka v URL budou malá.
Rozlišit některé podobné adresy je oříšek i pro vyhledávače.
Udělal jsem experiment a na URL lišící se v detailech jako je přítomnost
pravostranného lomítka nebo pořadí parametrů umístil odlišný obsah.
Zaindexovat je byl schopen pouze Google. Ostatní vyhledávače dokázaly
pojmout vždy jen jednu z variant.
Jen Google tyto stránky umí indexovat jako rozdílné
Pokud jde o pravostranná lomítka, webový server přesměrovává na
kanonickou podobu obvykle za vás; přistoupíte-li k adresáři bez
pravostranného lomítka, doplní je a přesměruje. Samozřejmě to neplatí,
když URI spravujete ve vlastní režii (Cool URI apod.)
A nakonec: skutečně záleží na pořadí parametrů? Mezi adresou
article?a=1&b=2 a article?b=2&a=1 by neměl
být žádný rozdíl. Jsou ale situace, kdy to neplatí, zejména když
předáváme složitější struktury jako třeba pole. Například
?sort[]=name&sort[]=city může být něco jiného, než
?sort[]=city&sort[]=name. Nicméně přesměrovávat, nejsou-li
parametry ve stanoveném pořadí, bych považoval za zbytečnou
hyperkorektnost.
p. s. Nette Framework přesměrování na
kanonické URL provádí zcela automaticky ve své režii
Pracovat s webovým formulářem na straně JavaScriptu se poměrně snadno
může stát očistcem. Nebo jak se nazývá ta věc na čištění záchodové
mísy. Přitom za všechno může jedno nešťastné rozhodnutí.
K jeho jednotlivým prvkům přistoupíme přes vlastnost
elements:
var form = document.getElementById('myform');
var query = form.elements.query.value;
// pochopitelně také form.elements['query'].value
A iterovat nad prvky lze cyklem for:
for (var i = 0; i < form.elements.length; i++) {
alert(form.elements[i].value);
}
Z historických důvodů lze vlastnost elements v příkladech
vynechat, neboť jednotlivé prvky se mapují přímo do objektu
form. Takže by fungovalo i form.query.value,
form.length nebo form[i].value. Což se záhy ukáže
jako nemilé. Formulářové prvky totiž přepisují nativní
metody a proměnné objektu form. Například metoda submit(),
která je klíčová pro AJAXové odeslání formuláře, se stane nedostupnou,
pokud formulář obsahuje prvek nazvaný submit. A uznejte, že to
je zrovinka název pro odesílací tlačítko jako dělaný. Pokud by tedy
formulář vypadal takto
nebude jej možné příkazem form.submit() odeslat, místo toho
JavaScript zařve „form.submit is not a function“ a má pravdu,
form.submit není funkce ale objekt HtmlInputElement. Dobrá, dáme
si pozor a nebudeme formulářové prvky nazývat submit.
(prozradím trik, jak by formulář šlo odeslat i v tomto případě:
document.createElement('form').submit.call(form))
Jenže název submit není tím jediným, kterému se musíme
vyhnout. Prvek pojmenovaný elements způsobí, že
form.elements nebude očekávaná kolekce a výše uvedené
příklady skončí chybou. Název length zase znemožní nad
kolekcí iterovat. A tak by se dalo pokračovat, nativních prvků třeba DOM
Firefoxu definuje hodně přes stovku. Pro zajímavost uvádím seznam jen těch
jednoslovných (tj. vynechávám nodeName nebo
innerHTML):
Bylo by skvělé, kdyby se HTML5 dokázalo s tímto nešvarem vypořádat.
Zatím jsem ve specifikaci nic takového nenašel.
Podobně mi chybí možnost, jak v obsluze události onsubmit
zjistit, kterým tlačítkem byl formulář odeslán. Triviální a užitečná
věc a jak složitě se musí řešit.
Řeším to tak, že odchytávám událost click jednotlivých
tlačítek a název prvku ukládám do vlastní proměnné formuláře. O něco
jednodušší je využít bublání a odchytávat click přímo na
formuláři. Nicméně v HTML5 tohle už nebude fungovat spolehlivě, protože
prvek může být umístěn i mimo strom formuláře a přiřazen k němu
atributem form. Bylo by tedy fajn, kdyby HTML5 zavedlo vlastnost
například form.submitter, který by vracela název
odesílajícího tlačítka.
p. s. Nette Framework s těmito
situacemi počítá a snaží se je v rámci možností řešit za
programátora