phpFashion

Na navigaci | Klávesové zkratky

Jak předávat závislosti v Nette

Jaké jsou best practice pro předávání závislostí presenterům, komponentám a jiným službám v Nette?

Nejprve: nezávisle na frameworku nebo typu tříd platí vždy tohle:

  • povinné závislosti předávat konstruktorem
  • volitelné buď samostatnou metodou, nebo též konstruktorem

Tečka.


Nicméně existuje případ, kdy je předávání závislostí konstruktorem problematické, tzv. constructor hell, tedy situace, kdy předáváme závislosti konstruktorem určité třídě a zároveň jejímu potomkovi. Tohle nastává často u presenterů, kde je právě zvykem mít nějaký BasePresenter. Jako workaround dává DI kontejner v Nette možnost použít v BasePresenteru místo konstruktoru metodu nazvanou injectBase() (případně jakkoliv jinak, jen musí začínat na inject).

Jde o workaround pro base presentery, takže v jiných situacích metody inject nepoužívejte.

Dále předávání závislostí přes konstruktory nebo jiné metody je otravné v tom, že musíte napsat nějaký rutinní kód. Nutnost psát rutinní kód vždy ukazuje na slabé místo samotného jazyka, jedna ze šancí na zjednodušení byla zamítnuta, nicméně některé IDE ho umí vygenerovat a taktéž DI kontejner ve frameworku nabízí zkratku v podobě @inject anotací. Public (!) proměnnou doplníte anotací např. /** @var Model\UserFacade @inject */ a framework sám do ní vloží službu a vyhnete se psaní konstruktoru nebo inject metody.

Protože to není čisté řešení, funguje opět jen pro presentery. Používání anotací rozhodně není best practice, ale je to sakra pohodlné.


Doporučuji podívat se na přednášku Filipa Procházky a přečíst článek Vojty Dobeše Tvorba komponent s využitím autowiringu.


Nette Revolution 2.2

Asi největší revoluce v dějinách Nette, která se vás nijak nedotkne.

Poměrně krátce po vydání velké verze 2.1, která přinesla řadu vylepšení nejen ve formulářích, je tu další velká verze 2.2, která přichází s úplně novou infrastrukturou. Totiž původní repozitář Nette byl smazán rozdělen do 19 nových samostatných komponent: Application, Bootstrap, Caching, ComponentModel, Nette Database, DI, Finder, Forms, Http, Latte, Mail, Neon, PhpGenerator, Reflection, RobotLoader, SafeStream, Security, Tokenizer, Tracy a Utils.

(Podívejte se na video Kvadratura Nette.)

Co se tím mění pro vás, uživatele Nette Frameworku?

Takřka nic. Stále si můžete stáhnout celý framework nebo jej nainstalovat příkazem composer require nette/nette.

Ale zároveň máte možnost používat zcela samostatně šablonovací systém Latte, Laděnku (neboli Tracy), databázovou vrstvu a vlastně cokoliv.

Rozdělení frameworku do menších částí je trend, který už započalo například Symfony nebo Zend Framework 2. Nutnou podmínkou byl vznik respektovaného nástroje pro správu závislostí v PHP, kterým se stal Composer. Pokud si s ním zatím netykáte, neváhejte a seznamte se, není daleko doba, kdy se v PHP knihovny nebudou distribuovat jinak.

Nette se na rozdíl od Symfony nebo Zendu rozdělilo i fyzicky, což znamená, že každá komponenta má vlastní repozitář, issue tracker a číslování verzí. Zároveň každý repozitář obsahuje testy a všechny související soubory. Podívejte se například na Forms, kde najdete příklady použití v examples, v adresáři src/assets JavaScripty a v adresáři src/Bridges kód, který propojuje různé Nette komponenty. V případě formulářů jde o makra pro Latte.

Stejným způsobem jsou pak strukturovány všechny repozitáře.

Rozdělení frameworku je završením dvouleté práce, s cílem zachovat maximální kompatibilitu. Vlastně i proto zůstáváme u čísla verze 2. Jediným výrazným zásekem bylo oddělení Latte, což si vyžádalo přepracovat systém šablon, který v Nette existuje v takřka původní podobně od verze 0.8. Ale postupně:

Tracy

Laděnka byla přejmenována na Tracy, nemusíte se tak trápit s psaním šíleného Nette\Diagnostics\Debugger, nyní stačí Tracy\Debugger. Původní třída je z důvodu zpětné kompatibility stále funkční.

Pokud píšete doplňky, prefix názvů CSS tříd se změnil z nette- na tracy- a třída nette-toggle-collapsed na dvojici tracy-toggle tracy-collapsed. Původní třídy jsou u starých doplňku změněny na nové automaticky.

Latte

Šablonovací systém byl vždy poměrně úzce provázán s dalšími částmi frameworku, zejména třídami z jmenného prostoru Nette\Templating. Aby bylo Latte samostatně použitelné, bylo potřeba mu vymyslet nové snadno použitelné API, které se obejde bez těchto pomocných tříd. A vypadá takto:

$latte = new Latte\Engine; // nikoliv Nette\Latte\Engine
$latte->setTempDirectory('/path/to/cache');

$latte->addFilter('money', function($val) { return ...; }); // dříve registerHelper()

$latte->onCompile[] = function($latte) {
    $latte->addMacro(...); // when you want add some own macros, see http://goo.gl/d5A1u2
};

$latte->render('template.latte', $parameters);
// or $html = $latte->renderToString('template.latte', $parameters);

Jak vidíte, Latte si řeší samo načítání šablon a jejich kešování, čímž pádem původní FileTemplate a vlastně celý Nette\Templating z velké míry pozbývá na smyslu existence. Tyto třídy i nadále fungují a snaží se zajistit kompatibilitu s novým Latte, nicméně jsou zavržené. Ze stejného důvodu jsou zavržené i třídy Nette\Utils\LimitedScope, Nette\Caching\Storages\PhpFileStorage a služba templateCacheStorage.

Naopak Application přináší vlastní třídu Template (nahrazující FileTemplate a zajišťují kompatibilitu) a továrnu TemplateFactory, která rozšiřuje možnosti, jak v presenterech a komponentách pracovat se šablonami.

Ostatní

Třídy Nette\ArrayHash, ArrayList, DateTime, Image a ObjectMixin jsou nyní součástí balíčku Utils, proto i jejich namespace byl změněn z Nette na Nette\Utils. Obdobně Nette\Utils\Neon se stalo součástí balíčku Neon a bude mít namespace Nette\Neon\Neon. Aby změna byla transparentní, vytváří se pro tyto a některé další třídy tiše aliasy. Aliasy ostatních tříd vytváří Nette Loader spuštěný v loader.php a vypisuje přitom varování (abyste mohli svůj kód upravit). Pokud loader.php nepoužíváte (tj. instalujete Nette přes Composer), můžete Nette Loader načíst ručně třeba v bootstrap.php. Od RC4 se Nette Loader spouští vždy.

Alternativně je možné vytvořit v bootstrap.php rovnou všechny aliasy a obejít tak varování. Ale jelikož PHP při vytváření aliasů načítá zdrojový kód tříd, může to vést k mírnému snížení výkonu:

@array_walk(Nette\Loaders\NetteLoader::getInstance()->renamed, 'class_alias');

Zavržena (tj. stále funguje, jen vyhodí E_USER_DEPRECATED) je třída Nette\Utils\MimeTypeDetector, která od PHP 5.3 není potřeba, neboť ji plně nahrazuje rozšíření Fileinfo (pod Windows jej nezapomeňte zapnout v php.ini).

Byla zrušena podpora anotace @serializationVersion a dohledávání tříd pro vlastní anotace – tyto věci nebyly známé ani používané, ale měly negativní vliv na výkon.

A nakonec, chybné odkazy v šabloně nyní začínají hashem, tj. místo error:... se vypisuje #error:, aby když na takový odkaz omylem kliknete, browser nevypsal strašidelnou hlášku. Upravte si proto CSS.

Novinky

Verze 2.2.0 přichází i s řadou novinek.

Formuláře nyní přenášejí parametr do v POST datech, takže nebude strašit v URL. Přibyly nové validátory Form::MIN a Form::MAX. A do funkcí obsluhující událost onSuccess se nyní jako druhý parametr předávají hodnoty formuláře, takže ušetříte psaní $values = $form->getValues().

V databázi přibyla nová funkce fetchAssoc(). Můžete se podívat na pár příkladů použití v testech.

Anotace @inject nyní respektují aliasy definované pomocí use.

Pokud do data- atributů třídy Html vložíte pole, bude se serializovat do JSONu.

V souboru config.neon můžete jednotlivým uživatelů definovat také jejich role.

A přibyla nová třída Nette\Security\Passwords, která řeší hashování hesel.


Právě jsem smazal Nette Framework

Nemáte náhodou zálohu?

Po dnešním commitu s 80.000 smazanými řádky se z Nette stal ultralehký framework. Nová verze vám ušetří místo na disku i datový traffic při stahování! Sám jsem z toho všeho ještě takový nevzpamatovaný, honí se mi hlavou nesmazatelné vzpomínky na všechny ty smazatelné soubory, kolik jsme toho spolu prožili, při debugování, někdy to byla i legrace, jindy spíš moc ne, a hlavně bych chtěl poděkovat rodičům a přítelkyním, klukům z Nette Foundation a číšníkům z hospody, zejména taky sponzorům, osvětlovačům a všem, co se podíleli na tomto ocenění, moc si toho vážím, dovolte abych se nyní vzdálil mezi novináře a sdělil jim své tiskové dojmy a plány do budoucna i minula, ještě však pozvedněme čísi číši šampaňského a NA NETTE!


Tak ať to nevypadá jako trapný aprílový žertík: Nette jsem fakt smazal. Ale na oplátku vyprodukoval pár nových open source projektů: Application, Caching, ComponentModel, Nette Database, DI, Finder, Forms, Http, Latte, Mail, Neon, PhpGenerator, Reflection, RobotLoader, Routing, SafeStream, Security, Tokenizer, Tracy a Nette Utils. Endžoj!


Proč používám Nette Tester

„Přesvědč mě, proč bych měl používat Nette Tester, v čem je lepší než PHPUnit?“ Tyhle otázky mě vždycky trošku uvádějí do rozpaků, neboť nemám potřebu nikoho přesvědčovat, aby Tester používal. Sám bych se bez něj ale neobešel.

Samotné testování je poněkud zakletá věc. Pět let se na všech konferencích stále dokola mluví o testování a přitom skoro v žádné firmě se kód „netestuje“. Píšu to v uvozovkách, protože ve skutečnosti všichni programátoři dnes a denně testují, co ale nedělají je, že nepíšou testy v PHPUnit, z mnoha důvodů. Pravda je, že sám jsem v oné krátké době, kdy byl Nette Framework testován PHPUnitem, také ztratil chuť testovat. Přičemž testování je pro vývoj frameworku natolik zásadní prvek, jako třeba verzovací systém.

Ale popořadě. Proč všichni programátoři testují, i když „netestují“?

Představte si, že naprogramujete funkci foobar()

function foobar($x, $y) {
    // tady se počítají nějaké věci
    return $val;
}

První věc, kterou každý programátor udělá, je, že vyzkouší, jestli to funguje:

echo foobar(10, 20);

Spustí to, vypíše to 30, což je správně, vypadá to, že to funguje a třeba ještě vyzkouší pár jiných vstupů.

Jinými slovy funkci otestuje. Tedy testuje!

Potom následně udělá to, že testovací skript smaže. A to je právě průšvih! Všechny ty argumenty vývojářů, že na testování nemají čas, v tento moment padají, protože realita je taková, že na testování čas je, dokonce jsou napsané i testy, jenže pak je programátoři smažou. Ironie.

Testem není pouze třída v PHPUnit, testem je i tento jednořádkový skript. Přesněji řečeno, test musí obsahovat ještě informaci, jaká je správná návratová hodnota, takže by vypadal takto:

assert( foobar(10, 20) === 30 );
assert( foobar(-1, 5) === 4 );
...

Až někdy v budoucnu upravím implementaci funkce foobar, nebo do ní sáhne kolega, stačí tento skript spustit, a pokud vyhodí warning, hned vím, že je funkce rozbitá.

A to je vše. Tohle je testování. Tak je to jednoduché. Děláme to všichni, bohužel hodně z vás si ty testy pak maže.


Časem se nám nahromadí obrovská spousta takovýchto testovacích skriptíků a vznikne otázka, jak je spouštět hromadně. Dá se to řešit nějakým shell skriptem, já jsem si na to napsal PHP skript. Jeho podstatnou výhodou je, že umí testy pouštět paralelně (běžně spouštím 40 vláken), což otestování celé sady dramaticky zrychlí. A taky přehledně vypisuje, ve kterém souboru kde přesně došlo k selhání.

Také místo PHP funkce assert jsem si napsal vlastní funkce (třída Assert), které se liší především v tom, že při selhání hezky a čitelně vypíšou, co funkce měla vrátit a co místo toho vrátila, abych rychle věděl, kde je problém.

A ten spouštěč, třída Assert a ještě dalších pár věcí tvoří zmíněný Nette Tester. Čtyři roky spolehlivě testuje Nette Framework.


Když mi někdo nahlásí, že ve funkci foobar je chyba, že pro vstupy 0 a -1 vrací prázdný řetězec místo čísla -1, tak začnu tím, že to nejprve ověřím:

Assert::same( -1, foobar(0, -1) );

Spustím to a skutečně, vypíše se:

Failed: '' should be -1

Tedy jsem napsal failující test. Neudělal jsem to proto, že v příručkách o testování se píše, že test musí nejdřív failovat, nebo proto, že bych se řídil TDD, dělám to proto, že mě nenapadá, co jiného by se dalo dělat, než prostě napsat krátký kód, který bugreport ověří, tedy failující test.

Chyba tam vážně je a je třeba ji opravit. V IDE začnu kód krokovat a hledám místo, kde je zakopaný pes. (O programátorech, kteří píší kód v poznámkovém bloku, ať už se jmenuje TextMate nebo Sublime, namísto plnohodnotného IDE, a nemohou proto kód krokovat, napíšu článek někdy příště.) Ano, kde je chyba bych asi zjistil i bez krokování, zíráním do kódu a umisťováním var_dump, echo nebo console.log, ale trvalo by to mnohem déle. Chci zdůraznit, že krokování a testování nejsou alternativy, ale zcela odlišné činnosti, které je fajn používat dohromady.

Odhalím a opravím chybu, Assert::same je spokojený a do repozitáře komitnu nejen opravu funkce foobar, ale také onen soubor s testem. Díky tomu se tahle chyba už nikdy v budoucnu nevyskytne. A věřte mi, že chyby mají tendenci se opakovat, dokonce pro tento jev existuje název: regrese.


Tohle povídání vám asi muselo připadat strašně samozřejmé. A to je dobře, ono je samozřejmé a já chci bourat předsudky a obavy z testování. Ale stále jsem neodpověděl na úvodní otázku: proč nepoužívám PHPUnit? Protože s ním takto přímočaře pracovat neumím.

Abych mohl otestovat foobar, musel bych napsat celou třídu, která dědí od jiné třídy, jejíž jméno si nejsem schopen zapamatovat. No budiž, použiji šablonu. PHPUnit neumí testy spouštět paralelně, takže otestování celé sady trvá násobně déle. V případě Nette Frameworku jde o 35 sekund versus 2 minuty, což už saje. Ale především, testy psané v PHPUnitu lze spouštět jen v PHPUnitu, nejde o samostatně spustitelné skripty. Takže není způsob, jak napsat failující test a potom ho úplně snadno krokovat a hledat výše uvedeným způsobem chybu.

Nejjednodušší řešení proto bylo napsat si vlastní triviální testovací udělátko. Za ty čtyři roky velmi pozvolna vyspěl v plnohodnotný nástroj, už ho nevyvíjím sám a díky klukům z Oracle má dnes integrovanou podporu v NetBeans 8.0. Jelikož generuje výstup ve formátu TAP, neměl by být problém ani s integrací do jiných nástrojů.

Nebudu vás přesvědčovat, abyste používali Nette Tester, ale rád bych vás přesvědčil, abyste si testy, které píšete, nemazali :-)


Kdy používat preprocesory a kdy ne?

Složitá otázka, těžké kodérovo dilema, které se pokusím rozseknout: používejte preprocesory vždy!

Jak se Internet přesunul do mobilů, staly se největší zabijáci rychlosti načítání stránek tyhlety úhledné sloupečky:

<link rel="stylesheet" href="/css/reset.css">
<link rel="stylesheet" href="/css/content.css">
<link rel="stylesheet" href="/css/layout.css">
<link rel="stylesheet" href="/css/screen.css" media=screen>
<link rel="stylesheet" href="/css/fancybox/jquery.fancybox.css" media=screen>
<link rel="stylesheet" href="/css/print.css" media=print>
<script src="/js/jquery.js"></script>
<script src="/js/jquery.autocomplete.js"></script>
<script src="/js/fancybox/jquery.fancybox.js"></script>
<script src="/js/tooltips/jquery.tooltips.js"></script>
<script src="/js/jquery.scrollspy.js"></script>
<script src="/js/main.js"></script>

To nechceš! Chceš tohle:

<head><link rel="stylesheet" href="/assets/combined.css"></head>

<body>
...
<script src="/assets/combined.js"></script>
</body>

Taky chceš, aby se ti všechny soubory posílaly zazipované a vypadaly hutně: combined.css a hustě: combined.js.

Byly doby, kdy se komprimované skripty psaly ručně. Vynechávaly se komentáře, v JavaScriptu používaly jednopísmenné proměnné atd.

Nedělejte to. Vaším úkolem je psát čitelný kód a komprimování JavaScriptu nechte na bedrech Google Closure Compiler, který to dokáže řádově lépe než vy a navíc upozorní na chyby, o minifikaci stylů se postará například Less.

Ten dokonce zvládá vychytávku, kdy u všech miniobrázků můžete nahradit:

textarea {
    background: #fff url("img/input.gif") repeat-x;
}

za

textarea {
    background: #fff data-uri("img/input.gif") repeat-x;
}

a preprocesor je přímo vloží do combined.css, čímž odpadne hromada HTTP requestů na 100bajtové drobky.

Preprocesory prostě a jednoduše chcete!