phpFashion

Nové jmenné prostory v Nette

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
  • StringNette\Utils\Strings a ArrayToolsNette\Utils\Arrays
  • Finder, Json, Neon, Paginator a Html jsou nyní taky v Nette\Utils (třída Tools už není)
  • Nette\Mail\MailNette\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.

Nová struktura pro 5.2

Zde jsou důležité vlastně jen tyto změny:

  • DebugDebugger, StringStrings, ArrayToolsArrays, MultiRouterRouteList a DownloadResponse → FileResponse

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!


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á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:

  • sfFormSymfony\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 ArrayIteratorSpl\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:

  • 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é ButtonControl netřeba (avšak HiddenControl na Hidden zkrá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\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


Vytváření elementů v jQuery

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('&', '&amp;').replace('"', '&quot;')
		.replace("'", '&#039;').replace('<', '&lt;');
};

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:

// <a style="color: red;" onclick="..." href="https://phpfashion.com">blogísek</a>
var $el = $('<a>', {
	href: url,
	text: title,
	click: function(e) {
		alert(this.href);
	},
	css: {
		color: 'red'
	}
});

Dokonce dovnitř můžeme vložit další element:

// <a href="https://phpfashion.com"><img alt="Logo" src="images/logo.gif"></a>
var $el = $('<a>', {
	href: url,
	html: $('<img>', {
		src: 'images/logo.gif',
		alt: 'Logo'
	})
});

Fajnové, že?

p.s. pokud se chcete o jQuery dozvědět víc, přijďte na školení jQuery a AJAX.


Školení jQuery a AJAX startuje!

„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!


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


Kde je Nette Framework 1?

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í.


Pohodlné procházení filesystémem

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!


Jsou tyto URL stejné?

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:

  1. 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čů.
  2. 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


Formuláře a HTML5 - co mi ještě chybí

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í.

Mějme jednoduchý formulář

<form id=myform>
	<input type=text name=query>
	<input type=submit value="Vyhledat">
</form>

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

<form id=myform>
	<input type=text name=query>
	<input type=submit name=submit value="Vyhledat">
</form>

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):

action, attributes, blur, children, dir, draggable, elements, encoding, enctype, focus, id, lang, length, method, name, normalize, prefix, reset, spellcheck, style, submit, target, title

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.

<form id=myform onsubmit="kterým tlačítkem byl odeslán?">
	<input type=hidden name=id value="123">
	<input type=submit name=save value="Uložit">
	<input type=submit name=delete value="Smazat">
</form>

Ř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


phpFashion © 2004, 2025 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.