Co přinesou příští verze Nette Frameworku a jaký je plán
pro další vývoj?
Nette je tvořeno řadou knihoven, z nichž některé patří mezi světovou
špičku: Latte je nejbezpečnější
šablonovací systém, Tracy je mnohými
považován za nejpřívětivější debugovací nástroj, Dependency Injection
Container patří mezi ty nejpohodlněji použitelné. Spousta konceptů vznikla
šťastnou rukou a fungují v prakticky nezměněné podobě už 10 let,
například formuláře nebo komponentový systém presenterů.
Nicméně ve všech oblastech je hodně příležitostí co vylepšovat a
inovovat. A nápadů je spousta.
Nette spouští nový
crowdfundingový program, jehož cílem je získat finanční prostředky
pro vývoj frameworku. Hlavní změnou je, že místo jednorázových
příspěvků je zaměřen na pravidelnou měsíční podporu. Ta může
přicházet jak od jednotlivců, tak od firem, kterým nabízí možnost
zviditelnit se na webu Nette a inzerovat na
fóru nebo přímo v dokumentaci, tedy na místech s nejlepším zásahem
cílové skupiny programátorů.
Každý, kdo staví na Nette, má zájem, aby se framework aktivně vyvíjel.
Aby podporoval nové verze PHP. Aby se opravovaly chyby. Aby přicházel s dalšími novinkami, které usnadní práci
nebo ušetří čas a peníze. Aby framework měl skvělou dokumentaci a
existoval kolem něj užitečný obsah, ať už ve formě článků, návodů
nebo videí.
Řada částí Nette představuje světovou špičku a chceme, aby tomu tak
bylo nadále.
Bez adekvátního financování se nic z toho nedá zajistit. Přitom abyste
se mohli spolehnout, že vyjdou další verze, stačí docela málo: abyste jej
každý měsíc podpořili byť jen malou finanční částkou.
Pokud pracujete ve firmě, které Nette vydělává peníze, vysvětlete
prosím svému šéfovi, že je dobrý nápad se stát partnerem a zajistit tak
zdravé fungování projektu, na který spoléháte. Zvýšíte prioritu
řešení vašich issues a zároveň zviditelníte svoji společnost
v komunitě a přilákáte k sobě vývojáře.
Partnerství totiž přichází s exkluzivními výhodami. Například
s uvedením vašeho loga na webu Nette, možností vkládat pracovní
nabídky,
inzerovat na fóru (ukázka)
nebo v dokumentaci (ukázka),
tedy na místech se zcela nejlepším zásahem do skupiny Nette
vývojářů.
Partnerům vystavujeme faktury, aby si mohli podporu dát do nákladů, a to
buď měsíčně, čtvrtletně, půlročně nebo ročně.
V tuto chvíli máme stanoveny tři cíle, kterých chceme dosáhnout.
Prvním cílem je 64.000 Kč měsíčně, které zajistí, že se vývoji
bude věnovat jeden programátor na půl úvazku. Další vývoj Nette tak bude
pokračovat, nicméně polovičním tempem než dosud, což není úplně
ideální. Při dosažení hranice 128.000 Kč získá framework full-time
vývojáře a nové verze mohou přicházet každý rok.
Náklady jsou tak nízké proto, že nepotřebujeme kanceláře a hlavně
pracujeme na něčem, co nás baví.
Při dosažení třetí mety 196.000 Kč měsíčně budeme moci přizvat
další spolupracovníky a vylepšovat tak web, dokumentaci, vytvářet nový
obsah a nechat jej překládat do angličtiny. A tak oslovit zahraniční
komunitu. Čtvrtá meta dává možnost zapojit druhého programátora, což by
vývoj a správu issues značně zrychlilo.
Jakmile se dosáhne tohoto milníku, připravíme další cíle, které by
počítaly s více programátory, mohly by vznikat nové užitečné nástroje
a knihovny. Pak třeba vznikne i kniha o Nette.
Už je to šílených dvanáct let, co jsem na tomto blogu
představil knihovnu Dibi. Dnes se dočkala
čtvrté verze. Využívající všech předností moderního PHP 7.1.
Vývoj a testování verze 4.0 trvalo 11 měsíců a čítá 114 komitů,
zapojilo se do něj několik autorů, kterým děkuji.
A co je nového? Dibi 4 běží v přísném režimu, tedy s
declare(strict_types=1). Je plně typovaná, tedy parametry a
návratové hodnoty metod mají nativní typehinty. Což si vyžádalo jednu
drobnou změnu: metody fetch() nebo fetchSingle()
v případě, že už v tabulce není další řádek, vracejí nově
null na místo false, protože návratové hodnoty
mohou být nullable,
nikoliv falseable. Doplněná byla podpora pro JSON (automaticky
dekóduje tyto sloupce), kontroluje, zda s modifikátorem %i nebo
%f nepoužijete řetězec, co není číslo, přibyl
Dibi\Expression a Connection::expression() (příklad), interface
IConnection a spousta dalších drobností.
Protože Composer se dnes považuje za standard, jak instalovat balíčky,
archív ZIP i s minifikovanou verzí zmizel v propadlišti dějin.
Změnou je, že metody Connection::query() a
Fluent::execute() nevrací v případě DML příkazů počet ovlivněných
řádek, ale objekt Dibi\Result. Počet řádek zjistíte z něj
($res->getRowCount()) nebo jako dříve
($connection->getAffectedRows()).
Dále objekt Dibi\DateTime je nyní potomkem DateTimeImmutable .
Má v sobě implementovaný magický mechanismus, který by měl odhalit, pokud
někde v kódu stavíte na tom, že je mutable, a došlo by tak k chybě.
Pak jsem dal pryč několik historických reliktů nebo zbytečností,
kompletní přehled najdete v changelogu. Ač ten
seznam může vypadat dlouze, v praxi byste krom výše zmíněného neměli na
žádný BC break narazit.
A ještě pro úplnost: Dibi od verze 3.1 podporuje mikrosekundy, což
může ve specifickém případu vést k BC breaku (viz
vlákno) a od verze 3.2 podporuje jen třídy s namespaces (tedy krom
třídy dibi).
Co bude dál?
Určitě zajímavé by bylo do Dibi doplnit podporu pro nativní bindování
parametrů, třeba pro upload binárních souborů je to nutnost. A s tím úzce
souvisí i prepared statements. Občas zaznívají žádosti o vylepšení
fluent interface, volání uložených procedur atd.
Zcela na rovinu říkám, že budoucnost stojí zejména na tom, jestli budu
mít za Dibi nějaké příspěvky. Takže pokud máte Dibi rádi, nastavte prosím měsíční
donation a svět bude nadále krásný 😁
Jak mockovat třídy, které jsou definované jako final nebo
některé z jejich metod jsou final?
Mockování znamená nahrazení původního objektu za jeho testovací
imitaci, která neprovádí žádnou funkci a jen se tváří jako původní
objekt. A předstírá chování, které potřebujeme kvůli testování.
Takže například místo objektu PDO s metodami jako query() apod.
vytvoříme jeho mock, který práci s databází jen předstírá, a místo
toho ověřuje, že se volají ty správné SQL příkazy atd. Více třeba
v dokumentaci
Mockery.
A aby bylo možné mock předávat metodám, které mají type hint
PDO, je potřeba, aby i třída mocku dědila od PDO. A to může
být kámen úrazu. Pokud by totiž třída PDO nebo metoda query() byla final,
už by to nebylo možné.
Existuje nějaké řešení? První možnost je final vůbec
nepoužívat. To ovšem nepomůže s kódem třetích stran, který final
používá, ale hlavně se tím ochuzujeme o důležitý prvek objektového
návrhu. Existuje dogma, že každá třída by měla být buď final, nebo
abstract.
Druhou a velmi šikovnou možností je použít Nette Tester, který od verze 2.0 disponuje
vychytávkou, která odstraňuje z kódu klíčové slovo final
on-the-fly. Stačí na začátku testu zavolat:
Názvoslovný oříšek: jak souhrnně označovat třídy a rozhraní? Jak
třeba nazvat proměnnou, která může obsahovat jak název třídy, tak
rozhraní? Co zvolit místo $class?
Dá se tomu říkat type ($type), nicméně to je zase
příliš obecné, protože typem je i řetězec nebo pole. Z pohledu jazyka
jím může být i něco komplikovanějšího, třeba ?array.
Navíc je sporné, co je v případě objektu jeho typ: je jím název třídy,
nebo je to object?
Nicméně souhrnné označení pro třídy a rozhraní skutečně existuje:
je jím slovo třída.
Cože?
Z pohledu deklarace je interface hodně ořezaná třída. Může obsahovat
jen veřejné abstraktní metody. Což také implikuje nemožnost vytvářet
objekty. Rozhraní jsou tedy podmnožinou tříd. A pokud je něco
podmnožinou, tak to můžeme označovat názvem nadmnožiny. Člověk je savec,
stejně jako rozhraní je třída.
Nicméně je tady ještě pohled užití. Třída může dědit jen od
jedné třídy, ale může implementovat vícero rozhraní. Nicméně tohle je
omezení týkající se tříd, samotné rozhraní za to nemůže. Obdobně:
třída nemůže dědit od final třídy, ale přitom final třídu pořád
vnímáme jako třídu. A také pokud třída může implementovat víc
rozhraní (tj. tříd, viz 1.), stále je vnímejme jako třídy.
A co traity? Ty sem vůbec nepatří, z hlediska OOP jednoduše
neexistují.
Tedy problém se společným pojmenováním tříd a rozhraní je vyřešen.
Říkejme jim prostě třídy.
classes + interfaces = classes
No jo, ale vznikl tady problém nový. Jak říkat třídám, které nejsou
rozhraní? Tedy jejich doplňku. Tomu, co se ještě na začátku článku
nazývalo třídy. Nerozhraní? Nebo implementations? 🙂
To je ještě větší oříšek. To je pořádný ořech. Víte co, raději
zapomeňme na to, že rozhraní jsou také třídy, a tvařme se opět, že
každý OOP identifikátor je buď třída, nebo rozhraní. Bude to
snazší.
Pozn.: v nejnovějších verzích Nette 2.4 až je tato anotace přímo v kódu.
Třída šablony
Třída stdClass v anotaci je workaround pro PhpStorm, který
jinak všechny proměnné považuje za nedefinované. Nicméně zajímavější
je si vytvořit třídu se seznamem skutečných proměnných, které šablona
má, včetně jejich typů. Může vypadat třeba takto:
class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
/** @var string */
public $lang;
/** @var int */
public $page;
/** @var string[] */
public $menu;
/** @var Model\Page */
public $article;
}
a potom ji v anotaci konkrétního presenteru uvedeme namísto
stdClass:
/**
* @property-read ArticleTemplate $template
*/
final class ArticlePresenter extends Nette\Application\UI\Presenter
Od této chvíle je napovídání perfektní:
Aktualizace: Latte
plugin už podporuje napovídání přímo v šablonách. Stačí přidat
{templateType App\Presenters\ArticleTemplate} na začátek
šablony.
Web Nette prochází redesignem
a první vlaštovkou je zbrusu nová horní lišta.
Na ní podobně jako ve vysílání ČT 24 nebo CNN běží tipy na
zajímavé články, knihovny, videa, události atd. Díky ní by vám nemělo
uniknout nic zajímavého, co se kolem Nette děje.
Potřebujete aktualizovat projekt běžící na Nette 2.0 na
nejnovější verzi? Tady je pár tipů, jak na to.
Stoupejte po jednotlivých
verzích
Tj. nejprve aktualizujte na Nette 2.1, poté 2.2, a tak dále. Důvod je ten,
že pokud se v Nette nějaká vlastnost změní nebo odstraní, děje se tak
v postupných krocích trvajících i několik let. Nejprve je vlastnost jako
deprecated jen označena v kódu (silently deprecated), v další velké verzi
emituje hlášku E_USER_DEPRECATED, ale funkčnost je zachována, a
teprve ve třetí verzi je odstraněna.
Nette se snaží, aby ony hlášky byly maximálně srozumitelné a návodné
(např.
Syntax {!$var} is deprecated, use {$var|noescape} on line 123),
abyste mohli zastaralé věci snadno nahradit.
Pokud byste přeskočili několik verzí, mohli byste přijít o tuto
důležitou fázi.
Začínejte bez
E_USER_DEPRECATED
Před nasazováním nové verze je vhodné nejprve vypnout hlášení chyb
E_USER_DEPRECATED:
$configurator->enableDebugger();
error_reporting(~E_USER_DEPRECATED); // všimněte si ~
Nyní můžete vyzkoušet, zda vše funguje jak má, bez upozorňování na
zapovězené věci. Pokud vše funguje, hlášky zase povolte a upravte podle
nich kód.
Za pár týdnů vyjde nová verze Nette 2.4, nejnabušenější
Nette všech dob. Postupně budu přidávat články, kde se dozvíte o všech
novinkách, ale ještě předtím…
…by bylo skvělé, kdybyste otestovali, jestli pod ním běží vaše
aplikace. Na všech svých webech už mám testovací 2.4 nasazenou a
pochopitelně jsem přitom odhalil řadu chybiček, které bylo potřeba
opravit. Tak prosím ověřte, jak jsou na tom vaše weby, než vyjde
stable.
Verze 2.4 by měla být na 99 % kompatibilní s verzí 2.3. Zároveň
upozorňuje, pokud používáte konstrukce, se kterými se už nepočítá do
Nette 3.0. Může se stát, že něco z toho je pro vás důležité a chtěli
byste funkčnost zachovat. Stačí o tom dát vědět. Ale samozřejmě co
nejdříve.
Podrobné vysvětlení všech případů, kdy Nette generuje upozornění, a
několika nekompatibilit mezi 2.3 a 2.4 najdete v tomto
vláknu na fóru. A velmi stručný přehled novinek můžete zkouknout ve
videu z poslední
Poslední soboty.
A co se ani v dokumentaci nedočtete, včetně záplaty na
bezpečnostní díru a rady, jak zrychlit odezvu serveru a naopak ji
nezbrzdit.
Output buffering umožňuje, aby výstup PHP skriptu (především funkcí
echo) nebyl okamžitě odeslán do prohlížeče nebo terminálu,
ale byl uchováván v paměti (tj. bufferu). Což se hodí k celé
řadě věcí.
Zabránění vypisování na výstup:
ob_start(); // zapne output buffering
$foo->bar(); // veškerý výstup jde pouze do bufferu
ob_end_clean(); // buffer smaže a ukončí buffering
Zachytávání výstupu do proměnné:
ob_start(); // zapne output buffering
$foo->render(); // výstup jde pouze do bufferu
$output = ob_get_contents(); // obsah bufferu uloží do proměnné
ob_end_clean(); // buffer smaže a ukončí buffering
$output = ob_get_clean(); // obsah bufferu uloží do proměnné a vypne buffering
V uvedených příkladech se obsah bufferu na výstup vůbec nedostal. Pokud
jej naopak na výstup poslat chci, namísto ob_end_clean() jej
ukončím funkcí ob_end_flush()
. Pro současné získání obsahu bufferu, odeslání na výstup a ukončení
bufferování existuje opět zkratka (i včetně chybějícího
end v názvu): ob_get_flush().
Buffer lze kdykoliv vyprázdnit i bez nutnosti jej ukončit, a to pomocí ob_clean()
(smaže jej) a nebo ob_flush()
(pošle jej na výstup):
ob_start(); // zapne output buffering
$foo->bar(); // veškerý výstup jde pouze do bufferu
ob_clean(); // smažu obsah bufferu, ale buffering zůstává aktivní
$foo->render(); // výstup jde stále do bufferu
ob_flush(); // buffer posílám na výstup
$none = ob_get_contents(); // obsah bufferu je nyní prázdný řetězec
ob_end_clean(); // vypne output buffering
Do bufferu se posílá i výstup zapisovaný na php://output,
naopak buffery lze obejít zápisem na php://stdout (nebo do
STDOUT), což je k dispozici pouze pod CLI, tedy při spouštění
skriptů z příkazové řádky.
Zanoření
Buffery je možné zanořovat, takže zatímco je jeden buffer aktivní,
dalším voláním ob_start()
se aktivuje buffer nový. Tedy ob_end_flush() a
ob_flush() neposílají obsah bufferu na výstup, ale do
nadřazeného bufferu. A teprve když žádný nadřazený není, posílá se
obsah na skutečný výstup, tj. do prohlížeče nebo terminálu.
Proto je důležité buffering ukončit, a to i v případě, že
v průběhu nastane výjimka:
ob_start();
try {
$foo->render();
} finally { // finally existuje od PHP 5.5
ob_end_clean(); // nebo ob_end_flush()
}
Velikost bufferu
Buffer může také zrychlit
generování stránky tím, že se do prohlížeče nebude odesílat
každé jednotlivé echo, ale až větší objem dat (například
4kB). Stačí na začátku skriptu zavolat:
ob_start(null, 4096);
Jakmile velikost bufferu překročí 4096 bajtů (tzv.
chunk size), automaticky se provede flush, tj. buffer
se vyprázdní a odešle ven. Téhož se dá dosáhnout i nastavením direktivy
output_buffering.
V CLI režimu se ignoruje.
Ale pozor, spuštění bufferingu bez uvedení velikosti, tedy
prostým ob_start(), způsobí, že se stránka nebude neodesílat
průběžně, ale až se vykreslí celá, takže server bude naopak působit
velmi líně!
HTTP hlavičky
Output buffering nemá žádný vliv na odesílání HTTP hlaviček, ty se
zpracovávají jinou cestou. Nicméně díky bufferingu je možné odeslat
hlavičky i poté, co se vypsal nějaký výstup, jelikož se stále drží
v bufferu. Ovšem jde o vedlejší efekt, na který neradno spoléhat,
protože není jistota, kdy výstup překročí velikost bufferu a
odešle se.
Bezpečnostní díra
Při ukončení skriptu se všechny neukončené buffery vypíší na
výstup. Což lze považovat za nepříjemnou bezpečnostní díru, pokud si
například v bufferu připravujete citlivá data, která nejsou určená pro
výstup a dojde přitom k chybě. Řešením je použít vlastní handler:
ob_start(function () { return ''; });
Handlery
Na output buffering lze navázat vlastní handler, tj. funkci, která obsah
paměti zpracuje před odesláním ven:
ob_start(
function ($buffer, $phase) { return strtoupper($buffer); }
);
echo 'Ahoj';
ob_end_flush(); // na výstup se dostane AHOJ
I funkce ob_clean() nebo ob_end_clean() vyvolají
handler, ale výstup zahodí a ven neposílají. Přičemž handler může
zjistit, která funkce je volána a reagovat na to. Používá se k tomu druhý
parametr $phase, což je bitová maska (od PHP 5.4):
PHP_OUTPUT_HANDLER_START při otevření bufferu
PHP_OUTPUT_HANDLER_FINAL při ukončení bufferu
PHP_OUTPUT_HANDLER_FLUSH při volání ob_flush()
(ale nikoliv ob_end_flush() nebo ob_get_flush())
PHP_OUTPUT_HANDLER_CLEAN při volání ob_clean(),
ob_end_clean() a ob_get_clean()
PHP_OUTPUT_HANDLER_WRITE při automatickém
flush
Fáze start, final a flush (resp. clean) mohou klidně nastat současně,
rozliší se pomocí binárního operátoru &:
if ($phase & PHP_OUTPUT_HANDLER_START) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FLUSH) { ... }
elseif ($phase & PHP_OUTPUT_HANDLER_CLEAN) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FINAL) { ... }
Fáze PHP_OUTPUT_HANDLER_WRITE nastává jen tehdy, pokud má
buffer velikost (chunk size) a ta byla překročena. Jedná se tedy
o zmíněný automatický flush. Jen pozor, konstanta
PHP_OUTPUT_HANDLER_WRITE má hodnotu 0, proto nelze použít
bitový test, ale:
if ($phase === PHP_OUTPUT_HANDLER_WRITE) { .... }
Handler nemusí podporovat všechny operace. Při aktivaci funkcí
ob_start() lze jako třetí parametr uvést bitovou masku
podporovaných operací:
PHP_OUTPUT_HANDLER_CLEANABLE – lze volat funkce
ob_clean() a související
PHP_OUTPUT_HANDLER_FLUSHABLE – lze volat funkci
ob_flush()
PHP_OUTPUT_HANDLER_REMOVABLE – buffer lze ukončit
PHP_OUTPUT_HANDLER_STDFLAGS – je kombinací všech tří
flagů, výchozí chování
Tohle se týká i bufferingu bez vlastního handleru. Například pokud chci
zachytávat výstupu do proměnné, nenastavím flag
PHP_OUTPUT_HANDLER_FLUSHABLE a buffer tak nebude možné (třeba
omylem) poslat na výstup funkcí ob_flush(). Nicméně lze tak
učinit pomocí ob_end_flush() nebo ob_get_flush(),
takže to poněkud ztrácí smysl.
Obdobně by měla absence flagu PHP_OUTPUT_HANDLER_CLEANABLE
zamezit mazání bufferu, ale opět to nefunguje.
A nakonec absence PHP_OUTPUT_HANDLER_REMOVABLE činní buffer
uživatelsky neodstranitelný, vypne se až při ukončení skriptu. Příkladem
handleru, který je vhodné takto nastavit, je ob_gzhandler,
který komprimuje výstup a tedy snižuje objem a zvyšuje rychlost datového
přenosu. Jakmile se tento buffer otevře, odešle HTTP hlavičku
Content-Encoding: gzip a veškerý další výstup musí být
komprimovaný. Odstranění bufferu by rozbilo stránku.
Správné použití je tedy:
ob_start(
'ob_gzhandler',
16000, // bez chunk size by server data neodesílal průběžně
PHP_OUTPUT_HANDLER_FLUSHABLE // ale ne removable nebo cleanable
);
Komprimaci výstupu můžete aktivovat také direktivou zlib.output_compression,
která zapne buffering s jiným handlerem (netuším, v čem konkrétně se
liší), bohužel chybí příznak, že má být neodstranitelný. Protože je
vhodné komprimovat přenos všech textových souborů, nejen v PHP
generovaných stánek, je lepší kompresi aktivovat přímo na straně HTTP
serveru.