Je nejvyšší čas rozseknout FUD a nejasnosti kolem
databázových vrstev.
Databázovou vrstvu dibi jsem
začal psát cca před devíti lety se záměrem shrnutým v tomto historickém
článku. Šlo mi především o to sjednotit API různorodých klientů,
ošetřovat chybové stavy, přidat uživatelsky pohodlné bindování
parametrů a také dynamické generování základních konstrukcí SQL, jako
jsou například podmínky, řazení a INSERT & UPDATE:
$db = new DibiConnection(...); // or via monostate dibi::connect(...)
$pairs = $db->fetchPairs('SELECT id, name FROM countries');
$arr = array(
'name' => 'John',
'modified%d' => time(),
);
$db->query('UPDATE users SET ', $arr, ' WHERE `id`=%i', $id);
// UPDATE users SET `name`='John', `modified`= '2005-10-12' WHERE `id` = 123
Časem se v PHP objevila nativní knihovna PDO, která v podstatě řešila polovinu
věcí, co dibi, nicméně její API pro bindování parametrů bylo
těžkopádné, neporadilo si s datumy a skládání základních konstrukcí
SQL chybělo úplně. Takže dibi nenahradilo.
V dibi jsem si hrál i s experimenty, jako DibiTable, DibiFluent nebo
DibiDataSource, ale sám jsem je nikdy nepoužíval. Jsou tam pochopitelně
i věci, které bych dnes udělal lépe, ale z hlediska zpětné kompatibility
je takřka nemožné do nich zasahovat. Třeba mám zmatek v tom, co který
modifikátor znamená – je jich příliš mnoho. (Moc se to neví, ale
místo přemýšlení, který modifikátor použít, můžete obvykle použít
otazník.)
Protože téměř v každém demu pro Nette bylo potřeba pracovat
s databází, vyžadovalo to nainstalovat dibi nebo Doctrine (jiné vrstvy se
v podstatě nepoužívají). Dnes je to díky Composeru otázka pár úderů
do klávesnice, ale tehdy neexistoval. Přemýšlel jsem proto, že bych si
v příkladech vystačil jen s čistým PDO. Jenže pokud jste rozmlsaní
z dibi, není návratu zpět. Chybí vám nejen přívětivé API, ale
i pohodlí Tracy (tj. Laděnky) pro
monitorování dotazů či chyb.
Tehdy mě napadlo, že by nebylo od věci udělat „dibi model 2010“,
nově, bez historických zátěží, založené čistě nad PDO. Vyhodit hromadu
driverů, všechny modifikátory nahradit jedním otazníkem a implementovat jen
vlastnosti, které budou skutečně potřeba.
Nette Database
Takhle vzniklo Nette Database
(NDB). Moderní ekvivalent dibi:
$db = new Nette\Database\Connection(...);
$pairs = $db->fetchPairs('SELECT id, name FROM countries');
$arr = array(
'name' => 'John',
'modified' => new DateTime,
);
$db->query('UPDATE users SET ', $arr, ' WHERE `id`= ?', $id);
Brzy jsem narazil na hromadu nedostatků PDO, nareportoval a obešel mraky
chyb a když bylo Nette Database odladěné, vyšlo v únoru 2012 jako
součást finálního Nette Framework 2.0.
Tady musím zdůraznit, že navzdory šiřitelům FUD v Nette Database
skutečně takřka žádné chyby nebyly a jediným větším problém se
ukázal bug v PDO způsobující memory
leaky, kvůli němuž musely být třídy NDB přepsány a v Nette
2.1 již Connection není potomkem PDO (dědit od PDO byla z dnešního
pohledu stejně blbost.)
Dnes nevidím důvod, proč pro nový projekt použít staré dibi namísto
NDB. Chybí asi jen:
- bohatší možnosti skládání v klauzuli WHERE (zatím se nezdá, že by
byla potřeba)
- statická obálka
dibi::
(tu v Nette nahrazuje DI
Container)
- samostatnost (vyřeší Nette 2.2)
- fetchAssoc (tu v Nette nahrazuje NDBT,
eventuálně by ji šlo
doplnit je v 2.2-dev)
A tím se dostáváme k Nette Database Table
(NDBT), prapříčině mnoha zmatků.
Nette Database Table (nyní
Explorer)
V prosinci 2010 jsem do tehdy beta verze Nette 2.0 začlenil knihovnu
Jakuba Vrány NotORM, ve kterém jsem viděl
úžasný nástroj pro dolování dat z databáze:
$pairs = $db->table('countries')->fetchPairs('id', 'name');
// SELECT `id`, `name` FROM `countries`
$name = $db->table('book')->get($id)->author->name;
// SELECT `id`, `author_id` FROM `book` WHERE `id` = 123
// SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (456))
$arr = array(
'name' => 'John',
'modified' => new DateTime,
);
$db->table('users')->where('id', $id)->update($arr);
// UPDATE users SET `name`='John', `modified`= '2005-10-12' WHERE `id` = 123
Kód i API jsem upravil tak, aby zapadlo do koncepce Nette, v podstatě
z myšlenky využít NotORM v Latte pochází i nápad na tzv. předbíhání
budoucnosti, neboli načítání jen těch sloupců, které budou později
potřeba, taktéž s Latte může skvěle fungovat koncept NotORM 2.
A právě NotORM v NDB nese označení NDBT (nyní Nette Database
Explorer). Přičemž její použití je volitelné.
Zapojení do Nette vyvolalo o NDBT resp. NotORM velký zájem a ukázalo se,
že byť byla knihovna pro potřeby Jakuba odladěná, pro různorodější
požadavky bylo třeba odvést ještě hodně práce. Té se od poloviny roku
2011 ujal Hrach a z velké části původní NDBT přepsal a pečlivě doplnil
testy. Knihovna procházela mnohem rychlejším vývojovým cyklem než zbytek
frameworku, nicméně nebýt jeho součástí, nevyvíjí se asi vůbec.
Nette tedy má
- NDB (nyní Nette Database Core), obdobu dibi, stabilní od verze 2.0.0
- NDBT (nyní Database Explorer), obdobu NotORM, vhodnou pro produkční
nasazení až od verze 2.1
Dibi nadále udržuji, koneckonců běží mi na něm většina webů, ale
žádné novinky nechystám. V podstatě ani do NDB ne. Naopak s NDBT, jenž
pohání mimo jiné i tento blog, má Hrach ambiciózní plány.
Aktualizace 2017: Původní Nette Database Table (NDBT) nese nový název
Nette Database Explorer a samotné jádro je Nette Database Core
Jaké novinky přináší Nette
Framework 2.1 ve formulářích? Začnu perličkou: formuláře byly
představeny před sedmi lety, jaksi mimochodem, v komentářích
pod jiným článkem, a jejich kód včetně dema se vešel do 800 řádků. Už tehdy šlo
o stěžejní část frameworku, která uměla třeba vedle serverové validace
generovat i tu JavaScriptovou.
Původní návrh založený na explicitním vyjmenovávání prvků se
ukázal jako správný, zejména když propukla kauza se zranitelností Mass
Assignment, kterou trpěly weby psané v Rails a jimi inspirovaných
frameworcích. Na druhou stranu, tvorbu některých dynamických formulářů to
činilo těžkopádnou. Dlouho jsem hledal kompromis a pak to
přišlo:
Nyní lze používat i prvky, které zapíšeme pouze v šabloně a
nepřidáme je do formuláře některou z metod
$form->addXyz()
. Když například vypisujeme záznamy
z databáze a dopředu nevíme, kolik jich bude a jaké budou mít ID, a chceme
u každého řádku zobrazit checkbox nebo radio button, stačí jej nakódovat
v šabloně:
{foreach $items as $item}
<p><input type=checkbox name="sel[]" value={$item->id}> {$item->name}</p>
{/foreach}
A po odeslání hodnotu zjistíme:
$values = $form->getHttpData($form::DATA_TEXT, 'sel[]');
kde první parametr je typ elementu (DATA_FILE
pro
type=file
, DATA_LINE
pro jednořádkové vstupy jako
text
, password
, email
apod. a
DATA_TEXT
pro všechny ostatní) a druhý parametr
sel[]
odpovídá HTML atributu name
.
Podstatné je, že getHttpData()
vrací sanitizovanou
hodnotu, v tomto případě to bude vždy pole validních
UTF-8 řetězců, ať už se pokusíte serveru podstrčit cokoliv. Jde
o obdobu přímé práce s $_POST
nebo $_GET
avšak
s tím podstatným rozdílem, že vždy vrací čistá data, tak, jak jste
zvyklí u standardních prvků Nette formulářů.
CheckboxList
Nový prvek pro výběr z více možností je CheckboxList. Stejně jako
v případě selectboxů nebo radiolistů kontroluje, zda odeslané hodnoty
jsou z těch, které nabízíme:
$form = new Form;
$form->addCheckboxList('colors', 'Favorite colors:', array(
'r' => 'red',
'g' => 'green',
'b' => 'blue',
));
Multiple file upload
Najednou lze uploadovat i více souborů, všimněte si
true
:
$form = new Form;
$form->addUpload('avatar', 'Picture:', true);
Zároveň formuláře mají integrovanou kontrolu, zda nebyl překročen
povolený limit velikosti odesílaných dat.
Nové vykreslovací zbraně
Velmi snadno můžete propojit formulář s existující šablonou. Stačí
jen doplnit atributy n:name
:
function createComponentSignInForm()
{
$form = new Form;
$form->addText('user')->setRequired();
$form->addPassword('password')->setRequired();
$form->addSubmit('send');
return $form;
}
<form n:name=signInForm class=form>
<p><label n:name=user>Username: <input n:name=user size=20></label>
<p><label n:name=password>Password: <input n:name=password></label>
<p><input n:name=send class="btn btn-default">
</form>
Atribut n:name
lze používat i s elementy
<select>
, <button>
nebo
<textarea>
.
Dále můžete vykreslovat prvky jako je RadioList, Checkbox nebo nový
CheckList pěkně po jednotlivých HTML elementech. Říká se tomu partial
rendering:
{foreach $form[gender]->items as $key => $label}
<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>
{/foreach}
Nebo lze použít klasická makra {input gender:$key}
a
{label gender:$key}
, trik je tom názvu s dvojtečkou.
S tím úzce souvisí i aktualizovaný způsob vykreslování checkboxů a
RadioListů. Místo dřívějšího
<label>...</label><input>
se nyní vykreslují v praktičtějším tvaru
<label><input>...</label>
pročež si myslím, že odpadne většina důvodů, proč jste tyto prvky
potřebovali vykreslovat po částech.
Zároveň také odpadá nutnost kreslit
checkboxy trošku jinak než jiné prvky, tj. myslet na to, aby label byl na
správném místě. Metoda getLabel()
či makro
{label}
totiž u checkboxů nyní nevrací nic a
getControl()
či {input}
vrací HTML v onom novém
tvaru. Pokud ale potřebujete staré chování, přepněte se do zmíněného
partial renderingu přidáním dvojtečky: {label checkbox:}
a
{input checkbox:}
.
Podpora pro Bootstrap
V příkladech najdete ukázky, jak nakonfigurovat vykreslování
formulářů pro Twitter
Bootstrap 2 a Bootstrap
3.
Chytřejší validátory
Validační pravidla Form::INTEGER
, NUMERIC
a
FLOAT
rovnou převádí hodnotu na integer resp. float. A dále
pravidlo Form::URL
, které akceptuje i řetězec ve tvaru např.
nette.org
, jej automaticky doplní na plnohodnotné
https://nette.org
.
Přibyla nová validační pravidla Form::BLANK
(prvek nesmí
být vyplněn) a Form::NOT_EQUAL
.
A v argumentech všech validátorů se můžete dynamicky odkazovat na
jiné prvky. Takže třeba tady prvek value
musí být v rozmezí
určeným aktuálními hodnotami prvků min
a max
:
$form->addText('min');
$form->addText('max');
$form->addText('value')
->addRule($form::RANGE, 'from %d to %d', array($form['min'], $form['max']));
Chybové zprávy
Makro {control form}
nyní vypisuje chybové zprávy přímo
vedle souvisejících prvků a nad formulářem se objeví jen ty, které
žádnému prvku nepřiřadíme (tj. když místo
$form['name']->addError()
použijeme
$form->addError()
). Je to mnohem uživatelsky příjemnější a
doporučuji, abyste stejným způsobem vykreslovali i formuláře manuálně,
třeba
takto. Pomůže vám metoda $form->getOwnErrors()
, která
vrací chybové zprávy přiřazené jen k formuláři.
Píšeme vlastní prvky
Výrazného zjednodušení doznala tvorba vlastních formulářových prvků.
Podívejte se na příklad
DateInput, což je prvek pro zadávání data. Zobrazovat se bude jako
trojice políček den, měsíc, rok a z pohledu API bude přijímat a vracet
objekt DateTime
.
Interně se datum reprezentuje jako trojice privátních proměnných
$day, $month, $year
, které metoda getValue()
převede
na požadovaný objekt DateTime
(tedy pokud půjde o platné
datum) a setValue()
naopak vstup do této trojice rozloží.
Přičemž by měla kontrolovat validitu vstupu a v případě chyby vyhodit
výjimku.
Výjimky naopak nevyhazuje loadHttpData()
, která se volá po
odeslání formuláře, a hodnoty, které uživatel odeslal, získá metodou
getHttpData()
a uloží do zmíněné trojice proměnných. Jen
pozor, tentokrát mluvím o metodě třídy BaseControl
, nikoliv
Form
. Každopádně i v tomto případě
getHttpData()
vrací očištěná data.
A nakonec metoda getControl()
generuje HTML. Pokud je prvek
reprezentován jedním HTML elementem, jeho atribut name
určí
metoda getHtmlName()
. Jenže máme prvky tři, tak za název
ještě dolepíme řetězec [day]
, [month]
a
[year]
(včetně těch hranatých závorek). Stejný postfix pak
uvádíme při volání getHttpData()
ve zmíněné
loadHttpData()
.
Co když místo obyčejného textového pole budeme chtít vykreslit
selectbox? Pak oceníte funkci
Nette\Forms\Helpers::createSelectBox()
. Prvním parametrem je pole
nabízených hodnot, druhým pole HTML atributů elementu
<option>
. V příkladu uvedené selected?
s otazníkem znamená, že atribut se uvede pouze u položky s uvedenou
hodnotou. Šlo by také uvést např.
'title:' => array(1 => 'January', 2 => ...)
s dvojtečkou, což dává možnost každé položce dát jiný
title
.
Existuje také obdobná funkce createInputList()
pro
generování skupin inputů. Té lze jako třetí parametr předat pole HTML
atributů pro element label
, taktéž podporující otazník a
dvojtečku.
Dále autoři nových prvků mohou ocenit dvě nové abstraktní třídy ChoiceControl
a MultiChoiceControl
.
A co ještě?
Pomocí $control->setOmitted()
vyjmete prvek z dat, která
vrací $form->getValues()
. To se hodí pro různé hesla pro
kontrolu, antispamové prvky atd. I všechny prvky, které označíte jako
$form->setDisabled()
, budou takto vyjmuty.
Vylepšeno bylo togglování, nyní by mělo fungovat přesně podle
očekávání. Navíc $form->getToggles()
vrátí informaci
o viditelnosti všech id.
Metoda setValue()
u jednotlivých prvků kontroluje datový typ
a dále v případě SelectBoxů a podobně vás nenechá nastavit hodnotu,
která v nabízených není.
V HTML atributech data-nette-rules
se používá čistý JSON,
takže nezapomeňte nasadit aktuální netteForms.js
.
A nakonec – u jednotlivých tlačítek můžete omezit seznam prvků,
které se mají při odeslání formuláře tímto tlačítkem validovat:
$form->addSubmit('edit')
->setValidationScope(array($form['name'], $form['password']));
Před chvílí vyšel Nette
Framework 2.1. Ačkoliv číselně jde o desetinkový posun, novinek je
hromada. K těm se překvapivě dostanu až v příštích článcích,
nyní mi půjde o kompatibilitu.
Na tu vždycky Nette Framework velmi dbal:
- nikdy mezi verzemi nebyla tlustá čára, vývoj je evoluční
- ačkoliv je psán v PHP 5.3, generovala se i verze pro PHP 5.2
- s přechodem na jmenné prostory dostali vývojáři nástroj, jenž jim
všechny třídy ve zdrojových kódech přejmenoval
Přechod na verzi 2.1 by měl být snadný. Teď si říkáte: „kilometr
dlouhý článek a snadný přechod?“ Inu, snaží se být vyčerpávající.
Mám weby, na kterých nebylo potřeba měnit nic.
Byť se pár věcí přejmenovalo, v případě tříd existují aliasy a
fungují i staré názvy metod, jen se vypíše či zaloguje upozornění.
Nicméně kvůli technikáliím doporučuji stejně těch pár tříd
přejmenovat, máte k tomu i nástroj na automatické
přejmenování tříd.
Zamáčkněte slzu, Nette Framework 2.1 opouští PHP 5.2, verzi, kterou už
3 roky nepodporuje ani samotné PHP.
Minimální požadovaná verze je tak 5.3.1 a Nette by mělo jet prakticky na
každém pětrojkovém hostingu (je testováno i na nejnovější 5.5.7).
Minimalizovaná verze se nyní generuje ve formátu PHAR, takže
v distribuci místo nette.min.php
najdete soubor
nette.phar
, se kterým se však pracuje úplně stejně.
Nette Database (NDB)
NDB společně s Dependency Injection byly čerstvé části frameworku a
bylo zřejmé, že nejvíce změn bude právě tady.
Nette\Database\Connection
již není potomkem PDO
- přejmenujte metody
exec()
→ query()
,
fetchColumn()
→ fetchField()
a
lastInsertId()
→ getInsertId()
Nette\Database\Statement
je nyní
Nette\Database\ResultSet
a též už není potomkem PDOStatement
- přejmenujte metody
rowCount()
→ getRowCount()
a
columnCount()
→ getColumnCount()
Používáte Nette Database Table (NDBT), tedy skvělou část NDB, ke které
se přistupuje přes $database->table(...)
?
- metoda
table()
byl přesunuta z Connection
do
nové třídy Nette\Database\Context
. Ta obsahuje
obsahuje všechny důležité metody pro práci s databází, takže klidně
změňte Connection
za Context
a máte hotovo.
- proměnné řádku
ActiveRow
jsou nyní read-only, pro změnu
slouží metoda $row->update(array('field' => 'value'))
.
Věřte, že dřívější chování mělo tolik úskalí, že jiná cesta
nebyla.
- změnila se tzv. backjoin syntaxe z
book_tag:tag.name
na
:book_tag.tag.name
(dvojtečka na začátku)
- místo druhého parametru
$having
v metodě
group()
použijte metodu having()
(Pokud jste používali SelectionFactory
v dev-verzi, změňte
ji také na Context
.)
Dependency Injection (DI)
- třída
Nette\Config\Configurator
→
Nette\Configurator
(původní název zněl, jako když se člověk
zakoktá)
- v konfiguračním souboru se sloučily definice
factories
a
services
do společného services
. Jen těm, co byly
původně factories, přidejte klíč autowired: no
.
- a zavedl se „odrážkový“ zápis anonymních služeb:
services:
Jmeno\Tridy: self # dříve, ukázalo se jako matoucí
- Jmeno\Tridy # nyní
Pracovat přímo s DI kontejnerem není obvykle dobrý nápad, ale pokud už
tak činíte:
- tovární metody volejte jako
$container->createService('nazevsluzby')
namísto $container->createNazevSluzby()
- zavrženy jsou všechny výchozí továrničky jako
createLatte()
, createCache()
,
createMail()
a createBasicForm()
- a ke službám přistupujte raději přes
$container->getService()
či getByType()
namísto $container->nazevSluzby
Pokud píšete vlastní rozšíření, vězte, že došlo k přejmenování
jmenných prostorů Nette\Config
→ Nette\DI
a
Nette\Utils\PhpGenerator
→ Nette\PhpGenerator
.
Oproti dev-verzi jsou anotace @inject
a metody
inject()
automaticky zpracovány jen na presenterech. Na jiných
službách je zapnete uvedením klíče inject: yes
v definici.
Používáte-li ještě stařičký Environment
, bude po vás
vyžadovat nastavenou konstantu TEMP_DIR
, kvůli výkonu.
Ufff, máme za sebou tu náročnou část. Teď už to bude brnkačka.
UI\Presenter a Control
- Presenter nyní zabraňuje, aby vám někdo podstrčil do persistentního
parametru pole. Pokud ale pole chcete, uveďte ho jako výchozí hodnotu,
- zavržené jsou metody
getService()
(použijte
getContext()->getService()
), dále getHttpContext()
a getApplication()
- magické
getParameter(null)
→
getParameters()
- místo divného
invalidateControl()
lze používat
redrawControl()
Tak to je easy, ne? Pojďme si dát Latte.
Latte
- výchozím režimem je HTML (namísto XHTML), což lze přepnout v konfiguraci
- automaticky ouvozovkuje
atributy v
<a title={$title}>
, což by nemělo způsobit
žádnou komplikaci, ale raději to zmiňuji
- atribut
n:input
se mění na n:name
, aby šel
použít nejen na <input>
, ale i label, select, form a
textarea
- zavržená jsou makra
{attr}
(nahrazuje n:attr
) a
{assign}
→ {var}
- doporučujeme místo vykřičníkového zápisu
{!$var}
přejít na {$var|noescape}
, je to zřejmější
- pokud jste v dev-verzi používali zkrácený zápis bloků
{#block}
, tak do 2.1 se nedostal, nebyl srozumitelný
V Latte je novinka, která v <a href={$url}>
automaticky
kontroluje, zda proměnná $url
neobsahuje něco jako
javascript:hackniWeb()
. Povolené jsou pouze protokoly http, https,
ftp, mailto a pochopitelně relativní cesty a kontroluje i atributy src,
action, formaction a také <object data=...>
. Pokud někde
potřebujete vypsat URL bez kontroly, použijte modifikátor
|nosafeurl
.
A nakonec: drobná změna souvisí s ručním vykreslování checkboxů,
ale o tom níže.
Přes obrovskou spoustu novinek ve formulářích je možných
nekompatibilit málo.
Checkboxy a RadioListy se nyní vykreslují v praktičtějším tvaru
<label><input>...</label>
namísto
<label>...</label><input>
. Jako důsledek
u Checkbox metoda getLabel()
či {label}
nevrací nic
a getControl()
či {input}
HTML v onom novém tvaru.
Pokud ale potřebujete staré chování, přepněte se do tzv. partial
renderingu přidáním dvojtečky: {label nazevprvku:}
a
{input nazevprvku:}
. Easy.
Makro {control form}
nyní vždy vypisuje chybové zprávy
u jednotlivých prvků a nad formulářem jsou jen ty nepřiřazené.
Doporučujeme to tak dělat i při manuálním vykreslování, třeba
takto.
setValue()
u prvků kontroluje hodnotu a v případě chyby
vyhodí výjimku namísto dřívějšího mlčení
- validační pravidla jako
Form::INTEGER
, NUMERIC
a
FLOAT
převádí hodnotu na integer resp. float
- TextArea: zrušeny výchozí hodnoty atributů
cols
a
rows
(existovaly jen proto, že to HTML4 vyžadovalo)
- prvky označené
setDisabled()
se neobjeví ve
$form->getValues()
(prohlížeč je totiž vůbec
neposílá)
- zavrženo
SelectBox::setPrompt(true)
, místo true použijte
řetězec
- přejmenováno
MultiSelectBox::getSelectedItem()
→
getSelectedItems()
- v HTML atributech
data-nette-rules
se používá JSON, takže
nezapomeňte nasadit aktuální netteForms.js
Debugger
Nette\Diagnostics\Debugger::$blueScreen
→
Debugger::getBlueScreen()
- a adekvátně
$bar
→ getBar()
,
$logger
→ getLogger()
a $fireLogger
→ getFireLogger()
- zavrženo
Nette\Diagnostics\Debugger::tryError()
,
catchError()
a také toStringException()
, místo
kterého použijte obyčený trigger_error()
- zavrženy interní
Nette\Diagnostics\Helpers::clickableDump()
a
htmlDump()
, které nahrazuje nová
třída Dumper
Mail
Zavržená metoda Nette\Mail\Message::send()
, použijte mailer,
viz dokumentace.
ostatní
- Nette nemusí fungovat s eAccelerator a minifikovaný PHAR nemusí fungovat
s APC
Nette\Utils\Finder::find($mask)
filtruje podle masky nejen
soubory, ale i adresáře
- do
Nette\Security\User
se v konstruktoru předává
autentikátor, pozor na kruhové závislosti
- v loaderu se už nenastavuje
iconv_set_encoding()
a
mb_internal_encoding()
- zavrženy konstanty
NETTE, NETTE_DIR a NETTE_VERSION_ID
- a třída
Nette\Loaders\AutoLoader
- a proměnná
Nette\Framework::$iAmUsingBadHost
- doporučujeme přestat používat
callback()
a třídu
Nette\Callback
, neboť globální funkce mohou způsobit komplikace
- přejmenoval se jmenný prostor
Nette\Utils\PhpGenerator
→
Nette\PhpGenerator
- Nette varuje hláškou „Possible problem: you are sending a cookie while
already having some data in output buffer,“ pokud se snažíte odeslat HTTP
hlavičku nebo cookie a byl již odeslán nějaký výstup – byť do bufferu.
Buffer totiž může přetéct a proto to varování.
Vyzkoušejte!
Stáhněte si verzi 2.1 a
vyzkoušejte ji! A těšte se na články o novinkách 
Téma testování presenterů by vydalo na celý seriál, ale
ušetříme si čas a místo toho popíšu, jak v několika krocích začít.
Aktualizováno pro Nette 2.3.
Jako testovací framework budu používat Nette Tester. Pochopitelně by šel
použít třeba i PHPUnit.
A jako vzorovou aplikaci můžeme vzít třeba Nette Sandbox, protože jej najdete
v každé distribuci Nette, nebo si
ho můžete stáhnout, včetně frameworku, pomocí Composeru:
composer create-project nette/sandbox myApplication
V něm už máme připravený testovací
bootstrap, který vytváří DI kontejner (a vlastně se moc neliší od
klasického app/bootstrap.php
).
Vyrobíme si tedy instanci presenteru. Buď ručně operátorem
new
a předáme všechny závislosti, nebo jednodušeji za
využití PresenterFactory
:
// z DI kontejneru, který vytvořil bootstrap.php, získáme instanci PresenterFactory
$presenterFactory = $container->getByType('Nette\Application\IPresenterFactory');
// a vyrobíme presenter Sign
$presenter = $presenterFactory->createPresenter('Sign');
A bude vhodné vypnout autoCanonicalize
, aby presenter
nepřesměrovával na kanonické URL:
$presenter->autoCanonicalize = false;
A rovnou můžeme začít testovat, třeba akci Sign:in
:
// zobrazení stránky Sign:in metodou GET
$request = new Nette\Application\Request('Sign', 'GET', array('action' => 'in'));
$response = $presenter->run($request);
Presenter je stavěn na jedno voláním run()
, pro další
requesty vytvoříme vždy nový presenter.
Ověříme, zda odpověď je skutečně šablona:
Assert::type('Nette\Application\Responses\TextResponse', $response);
Assert::type('Nette\Bridges\ApplicationLatte\Template', $response->getSource());
Necháme šablonu vygenerovat HTML kód:
$html = (string) $response->getSource();
A nyní třeba zkontrolujeme, zda se na stránce nacházejí formulářová
políčka pro jméno a heslo. Syntax je stejná jako u CSS selektorů.
$dom = Tester\DomQuery::fromHtml($html);
Assert::true( $dom->has('input[name="username"]') );
Assert::true( $dom->has('input[name="password"]') );
Toliko úvodem.
V Nette jsme narazili na zajímavý problém, který úzce
souvisí s článkem Dependency
Injection a předávání závislostí a komplikacemi způsobenými
dědičností.
Stěžejním bodem tvorby aplikací v Nette je vytváření vlastních
presenterů, tedy potomků třídy Nette\Application\UI\Presenter
(dále Presenter s velkým P). Ta má své závislosti. Byl bych rád, aby
uživatelé stavěli aplikace v souladu s DI a tedy se i jejich presentery
hlásily ke svým závislostem.
Jenže jak to realizovat? Možná řešení:
Presenter i potomek uvedou závislosti v konstruktoru – to
by bylo peklo
pro každou závislost se použije samostatný setter –
zbytečně moc režijního kódu
- použije se jedna metoda pro závislosti Presenteru a jedna pro potomka
a) Presenter využije konstruktor, potomek metodu inject() b) potomek
využije konstruktor, Presenter metodu inject() c) nebudeme do toho tahat
konstruktor, použijí se jiné metody
- použije se třída PresenterDependencies sdružující závislosti do
jedné proměnné
- injektování přímo do proměnných – nevím, jak pojmout
ad 1) Jak už jsem psal v odkazovaném článku, pokud by Presenter i jeho
potomek pro předání závislostí použili konstruktor, musel by se ten
překrývající postarat o předání všech rodičovských závislostí. Ale
jakmile se rodičovské závislosti změní (refactoring Presenteru), všechny
třídy se rozbijí. Neakceptovatelné.
ad 2) Zda předávat všechny závislosti jednou metodou nebo zda vytvořit
metodu/injektor pro každou závislost zvlášť, je v principu jedno. Protože
nemám rád, když je moc režijního kódu a zbytečně bobtná API, volím
raději jednu metodu.
ad 3) Šikovně vypadají přístupy 3a a 3b, tedy použít konstruktor pro
závislosti jedné strany a metodu inject()
pro závislosti druhé.
O tom, který způsob použít, jsme se přeli s Honzou Tichým. On preferoval
způsob 3b s tím, že když už nemáme žádné ideální řešení, zvolme
z možných to, které bude funkční, pokud uživatel začne věc řešit
intuitivně, aniž by věděl o nějaké metodě inject(). A intuitivně
použije konstruktor.
Namítal jsem, že ho tak utvrdíme v mylném dojmu, že předek, tedy
třída Presenter, vlastně žádné své závislosti nemá, jelikož si
programátor v konstruktoru předává jen ty své. Ale jde víceméně
o banalitu.
Horší je, že konstruktor potomka je volán nad objektem, který ještě
není plně inicializován, tj. nejsou mu předány závislosti rodiče.
Zkrátka se volá ještě před voláním inject(). Pokud by uživatel
v konstruktoru použil funkci, která nějakou takovou závislost vyžaduje
(například getUser()), výsledkem bude zavádějící chybová hláška Fatal
Error: Call to a member function xyz on a non-object nebo nějaká podobná.
Což není dobře.
Kdyby se uživatel zeptal, jak má tedy situaci vyřešit a v konstruktoru
plné inicializace docílit, odpovědí by bylo: nejde to, přesuň kód do
metody startup(). (Pokud nejsem srozumitelný, podívejte se na příklad
níže.) Tímto problémem řešení 3a netrpí.
ad 5) injektování přímo do proměnných s anotací @inject
by bylo vůbec nejšikovnější. Naprosté minimum režijního kódu, konec
problémů s dědičností. Má to ale svá úskalí: obsah public proměnných
nelze kontrolovat, private proměnné nejsou součástí veřejného API (jak je
plnit?) nicméně k tomuto tématu se ještě v nějakém dalším článku
vrátím.
Nakonec jsme se s Vaškem Purchartem domluvili na šalamounském řešení
4, kdy se všechny závislosti budou předávat konstruktorem s tím, že
závislosti třídy Presenter sbalíme do jediného
objektu PresenterDependencies, ten už se bude předávat snadno a při
změně závislostí Presenteru se nic nerozbije.
Pokud budu chtít do svého presenteru předat objekt ArticleFacade, bude to
vypadat takto:
class ArticlePresenter extends Presenter
{
function __construct(Nette\Application\UI\PresenterDepedencies $pd, ArticleFacade $af)
{
parent::__construct($pd);
$this->articleFacade = $af;
// nyní je objekt plně inicializován a lze volat třeba:
if (!$this->getUser()->isLoggedIn()) { ... }
}
}
Podstatné je, že všechny závislosti budou předány přímo
v konstruktoru, objekt bude ihned inicializován a nebudou se zavádět
žádné novoty v podobě metod inject. Jak Vašek psal, jde o ústupek
pohodlnosti v zájmu přehlednosti: „Většinou v Nette vyhrávala vždy
pohodlnost … mám za to, že v rámci plánovaných změn a refaktoringů by
Nette mělo tuhle houpačku mírně zhoupnout směrem k přehlednosti za cenu
absolutní pohodlnosti.“
Poznámka: aby bylo evidentní, že třída PresenterDependencies je jen
workaround nedostatku v návrhu jazyka a že vlastně nemá sémantický
význam v objektovém modelu, nevadilo by mi ji implementovat takto
triviálně:
class PresenterDependencies
{
private $services;
function __construct(Application\Application $application, ..., Security\User $user)
{
$this->services = func_get_args();
}
function get()
{
return $this->services;
}
}
abstract class Presenter
{
function __construct(PresenterDependencies $pd)
{
list($this->application, ..., $this->user) = $pd->get();
}
}
Konec dobrý, všechno dobré. Vašek připravil pull request a já si
uvědomil…
Slepá ulička
…že tohle řešení je vlastně nejhorší ze všech. Proč?
Udělal jsem si výčet faktorů, které chci u jednotlivých řešení
sledovat (pořadí nesouvisí s důležitostí):
- pohodlnost
- intuitivnost
- blbuvzdornost
- moment inicializace
- rozšiřitelnost (dědičnost)
ad pohodlnost: Povinné předání objektu PresenterDependencies je
zdánlivá drobnost, ale troufám si tvrdit, že tohle by byl důvod, proč
mnozí DI nepoužijí a raději si ArticleFacade vytáhnou ze service locatoru.
Uvedený type hint je nejdelší název třídy z celého frameworku a psát ho
je za trest.
Představil jsem si sám sebe, jak tohle ukazuju na školení a připadal
bych si jako kokot. Jako bych školil nějaký Zend. Musel bych se ptát sám
sebe, proč to neděláme nějak lépe.
ad intuitivnost: Honza položil otázkou, co se stane, pokud člověk začne
věc řešit intuitivně. Dopadne to špatně. Nenapadne ho, že existuje
nějaké PresenterDepedencies a že je ho potřeba předat. Nepředá ho,
Presenter nezíská závislosti a bude v poloinicializovaném stavu.
V nečekané chvíli vyskočí nějaký divoký nicneříkající
Fatal Error.
S tím velmi úzce souvisí otázka blbuvzdornosti. I když budu vědět,
že musím volat parent::__construct()
(vždycky volejte!), mohu na
to snadno zapomenout a bude problém. V případě presenteru by bylo možné
(a vhodné) implementovat kontrolu, zda byl volán, stejně jako je tomu
u metody startup(), ale problém je, že řešení 4 jde této chybě
naproti.
Programátor také může udělat chybu v type hintu (všimli jste si, že
jsem ji v příkladu udělal?) nebo jej neuvede vůbec. Sice jsem se snažil
poladit chybové hlášky DI kontejneru, ale ty ve své obecnosti nikdy nemohou
být dokonalé a programátor bude stejně zmaten.
Pod momentem inicializace myslím okamžik, kdy je objekt plně
inicializován. PresenterDependencies ho dokázal dostat do konstruktoru, jiné
řešení tohle neumí. Nicméně výhodu v případě presenterů relativizuje
fakt, že uživatelé jsou stejně zvyklí úvodní kód dávat do metody
startup(), kde už objekt plně inicializován je.
Pátý a dosud opomíjený faktor hodnotí, jak se řešení vypořádá
s existencí více úrovní dědičnosti. Což je v případě Nette obvyklé,
tj. vytvořit si nějaký abstraktní BasePresenter a teprve od něj dědit. Co
když bude mít BasePresenter také nějaké závislosti? Řešení
4 předpokládá, že bychom je také uváděli v konstruktoru třídy
BasePresenter a znovu opakovali v jeho potomcích. Otravnost by se tím
zvyšovala.
Ven ze slepé uličky
Udělal jsem si tabulku, která u každého řešení uvádí, jak vyhovuje
jednotlivým faktorům. Jasným vítězem se ukázalo být 3c. To konstruktor
vůbec nepoužívá a každá třída si své závislosti předá metodou
(nejlépe finální) s názvem začínajícím na inject
. (Viz implementace.)
PresenterFactory, která má za úkol vytvářet instance presenterů,
postupuje tak, že po vytvoření objektu zavolá všechny jeho metody inject
v pořadí od třídy Presenter k potomkům. Tedy programátor si v každé
třídě vytvoří metodu nazvanou inject()
nebo podobně, a v ní
uvede jednotlivé závislosti. Nemusí předávat nic navíc, minimalizuje se
možnost chyby. Z hlediska pohodlnosti, rozšiřitelnosti a blbuvzdornosti je
tak učiněno za dost.
Typický příklad předávání závislostí do presenterů bude v Nette
vypadat takto:
class BasePresenter extend Presenter
{
function injectBase(User $user, ...)
{
$this->user = $user;
...
}
}
class ArticlePresenter extends BasePresenter
{
function inject(ArticleFacade $af)
{
$this->articleFacade = $af;
}
}
A co z hlediska intuitivnosti? Co když uživatel použije pro předání
závislostí konstruktor, protože o žádných metodách inject() neví?
Nevadí, fungovat to bude. Pochopitelně v konstruktoru nebude stále možné
volat např. metodu getUser(), ale tuto daň rád zaplatím.
Celá věc by šla zjednodušit injektováním přímo do
proměnných, třeba časem k tomu najedeme klíč. Do té doby se mi 3c
jeví nejschůdnější.
Ladíte v prohlížeči PHP aplikaci a potřebujete ji v jednom místě
snadno zastavit a přehledně zobrazit všechny důležité informace jako:
- právě volanou funkci
- argumenty, které ji byly předané
- lokální proměnné
- call stack
A občas by se hodily i globální proměnné $_SERVER, $_COOKIES, HTTP
hlavičky, apod.
Jak na to?
Tohle umí Laděnka neboli Tracy,
dříve Nette\Diagnostics\Debugger. Pokud používáte Nette Framework, Laděnku už máte, protože je
jeho nedílnou součástí.
Laděnku nejprve aktivujeme. Pro uživatele samostatné verze stačí
zavolat:
ndebug();
(Uživatelé Nette Framework, aktivujte
Debugger::$strictMode = true;
a zavolejte
Debugger::enable()
.)
Nyní v místě, kde chceme program přerušit a zobrazit všechny
požadované informace, stačí napsat $stop()
:
public function authenticate(array $credentials)
{
list($username, $password) = $credentials;
$row = $this->users->where('username', $username)->fetch();
$stop(); // tady se program přeruší
...
Debugger vám přehledně ukáže místo, kde k přerušení došlo:
Všimněte si, že hodnotu lokální proměnné lze zobrazit tím, že na ni
najedete myší. Cool!
Můžete si nastavit systém tak, aby při kliknutí na jméno souboru jej otevřel
ve vašem oblíbeném editoru:
K dispozici je přehled všech proměnných, které byly funkci předány
(pod záložkou Callstack):
A přehledně vypsané všechny lokální proměnné, složitější
struktury můžete kliknutím rozbalit:
Samozřejmostí je celý call stack, opět s rozklikávacím pohledem do
zdrojového kódu nebo předávaných proměnných. A také lze klikem na
jméno souboru jej otevřít v editoru:
Níže najdete všechny informace o HTTP požadavku, počínaje hlavičkami
přes cookies k GET/POST proměnným:
A nechybí ani pole $_SERVER
, konfigurační direktivy,
konstanty atd:
Dokáže tohle váš framework?
Pokud ne, seznamte ho s Laděnkou.
Ne každému a ne na všechno se vždy a univerzálně hodí
velké frameworky!
Nadpis jsem si vypůjčil z článku
Manifest miniaturního PHP, pod nějž bych se klidně elektronicky
podepsal, mít elektronický podpis. Sice argument s počítáním řádků je
nefér a diskutabilní, ale rozumím, co se autor snažil říct. Na Zdrojáku
jsem pod něj napsal komentář, který jsem se nakonec rozhodl zvěčnit i tu
na blogu:
Docela často dělám jednoduché weby, které celé napíšu v
„notepadu“, a chci, aby kód neměl víc řádků, než je naprosto
nezbytně nutné a nahrávat kvůli 20kB webu i se styly na hosting
hafo-megabajtový framework nepřichází v úvahu už vůbec.
Ale přitom i v těch jednoduchých webech chci použít věci, co jsou
vyřešené v Nette, a nechci se vzdát navyklého pohodlí. Jsem líný
programátor. Z toho důvodu se dá Nette
Framework používat jako micro framework.
Chtělo by to příklad. Zrovna včera jsem si redesignoval https://davidgrudl.com a dal k dispozici
zdrojové kódy (odkryjte rožek vlevo nahoře), čistě pro inspiraci
ostatním, jak takový microsite řeším. Celý PHP kód webu je tvořen
jedním souborem index.php, který je, věřím, srozumitelný, byť pro
neznalého asi méně. Zbytek jsou šablony. A framework nahraný
v minifikované podobě jediného souboru, což, společně s faktem, že má
cca dvojnásobnou velikost než jQuery, řeší psychologický blok „nechci
nahrávat celej framework“.
Nebo příklad blogu, který najdete přímo v distribuci. Jeho zdroják
tvoří taktéž pouze index.php, a to ještě s méně řádky, než
v předchozím případě. Vše ostatní jsou šablony, viz https://github.com/…ta/templates.
Asi bych měl napsat, proč vlastně na pidiwebech framework používám. Tak
především si dnes neumím představit, že bych něco programoval bez Laděnky, ta mi pak na ostrém serveru
bude logovat chyby (ačkoliv u statického webu asi nebudou). Především ale
využiju šablonovací systém Latte,
protože už od 2 stránek chci mít oddělený layout a obsah, vyhovuje mi
stručná syntaxe Latte a spoléhám na automatické escapování. Využiji
i routování, protože prostý
požadavek mít URL adresy bez koncovek .php
dokáže
v mod_rewrite nastavit (správně!) jen
bůh.
V prvním zmíněném webu se ještě používá kešování pro twitter feedy, na
blogu je zase využit databázový
layer. A taky SEO vychytávka Nette, která automaticky předchází
známé chybě, kdy se při stránkování pohybem vpřed a vzad dostaneme na
tutéž stránku, jen jí v URL bude navíc strašit page=1
.
A taky mi Nette zajistí, že se při chybě nikdy neobrazí
programátorské chybové hlášky PHP, ale uživateli srozumitelná stránka. A ještě
autoloading – už jej
vnímám jako takovou samozřejmost, že bych na něj úplně zapomněl.
Samozřejmě někdy ještě přidám kontaktní formulář a nechám ho odesílat emailem. Teď si teprve
uvědomuji, že vlastně využívám 90 % frameworku.
Takhle tvořím quick'n'dirty weby a takhle mě to baví 
Znáte ty nářky vývojářů, že jejich klient nemá jasnou
představu a neustále mění zadání zakázky? To pláčou nad vlastní
neschopností. Když je slyším, nejraději bych popřál nebohému klientovi
lepšího dodavatele.
Klient nemá jasné zadání, protože není odborník na webdesign.
Zajímalo by mě, kolik webdesignérů se vyzná v podnikání svého klienta
tak dobře, že by dokázalo vytvořit precizní zadání, kdyby se karty
obrátily.
Pokud klient zadání průběžně mění, znamená to, že ho projekt
zajímá a baví, že nad ním neustále přemýšlí. Je pak větší
pravděpodobnost, že vznikne něco skutečně užitečného. A především:
bude poptávat další a další práci.
Pokud si vývojář tyto věci uvědomí, pak pochopí, že je to právě on,
kdo musí přizpůsobit svůj styl práce. Třeba zjednodušit přidání kolonky PSČ na web, ač
to v původním zadání nebylo.
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.
Nová struktura pro 5.2
Zde jsou důležité vlastně jen tyto změny:
Debug
→ Debugger
, String
→
Strings
, ArrayTools
→ Arrays
,
MultiRouter
→ RouteList
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!
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í.