Způsob, jak se vyvíjejí aplikace v PHP, se v posledních
5 letech dramaticky proměnil. Nejprve jsme opouštěli čisté PHP a učili se
používat frameworky, později přišel Composer a s ním instalace knihoven
z příkazové řádky a nyní nastává konec frameworků, jak je známe.
Monolitické frameworky se postupně rozpadají do samostatných (decoupled)
komponent. A to přináší řadu výhod. Zatímco dříve bylo použití jen
jedné části frameworku obtížné až nemožné, dnes si prostě
nainstalujete jeho komponentu. Vývojový cyklus jednotlivých komponent může
mít různé tempo. Mají vlastní repozitáře, issue trackery, mohou mít
vlastní vývojářské týmy.
Komponenty můžete aktualizovat na nové verze průběžně, bez čekání,
než vyjde další verze celého frameworku. Nebo naopak se můžete rozhodnout
určitou komponentu neaktualizovat, třeba kvůli BC breaku.
Význam slova framework se tak posouvá, o verzích už takřka nelze
hovořit. Místo frameworku XYZ ve verzi 2.3.1 používáte sadu komponent
v různých verzích, které spolu fungují.
Rozdělení frameworku na komponenty je dost složité. Nette to trvalo
2 roky a hotovo bylo loni. Naprostou nutností bylo prosazení se Composeru a
také důsledné používání dependency injection. Nette dnes tvoří přes
20 samostatných repozitářů a v tom původním zbyla jen jediná
třída.
Všechny významné frameworky, jako Symfony, Zend, Laravel nebo CakePHP,
jsou rozčleněné do komponent, byť k dotažení chybí ještě jeden krok:
rozdělení do samostatných repozitářů (namísto náhražky v podobě Git
subtree split). Zend slibuje, že s tím přijde ve verzi 2.5, uvidíme, co
Symfony.
Komponování Nette
Dlouhým úvodem jsem se vás snažil přivést na myšlenku, že dívat se
na Nette jako na framework v nějaké konkrétní verzi je překonané. Že
šikovnější je k němu přistupovat jako k sadě komponent.
Tj. namísto uvádění závislosti na nette/nette uvádět
závislosti na konkrétních komponentách. Tak to nyní dělá
i Sandbox. Jako základ budoucí aplikace může posloužit i Nette Web Project, což je
úplně minimalistická obdoba Sandboxu. Stáhněte jej pomocí
composer create-project nette/web-project
a vyhoďte z composer.json komponenty, které nepotřebujete.
Zrychlíte tak operace Composeru.
Rychleji se k vám dostanou i bugfixy. Po opravení chyby lze hned
u příslušné komponenty tagnout novou verzi, zatímco cyklus uvolňování
verzí celého frameworku je mnohem pomalejší.
Pokud tvoříte doplňky pro Nette, tak vůbec neváhejte a hned nahraďte
závislost na nette/nette výčtem skutečně požadovaných
komponent.
Samozřejmě i nadále budou vycházet nové verze frameworku jako doposud,
bude fungovat require nette/nette a pro verzi 2.3 budou vycházet
i distribuce v archivech ZIP. Ale jejich význam bude pomalu upadat.
Nette se v anketě Best
PHP Framework for 2015 pořádané magazínem SitePoint umístilo na
úžasném 3. místě. Moc děkuji všem za hlasy, tak skvělý výsledek
jsem opravdu nečekal.
Těší mě na tom, že uživatelé jsou s Nette nejspíš spokojení, jinak
by asi hlasy neposílali. A také samozřejmě fakt, že Nette tím na sebe
upozornilo ve světě, kde se o něm kvůli jazykové bariéře
tolik neví.
Na výsledcích je zajímavé i to, že si člověk uvědomí, jak velké
množství PHP frameworků se reálně používá, že existují i další
populární „lokální frameworky“ a že je tu stále dost těch, kteří
žádný framework nepoužívají.
Jednou z nejzajímavějších částí Nette, kterou
vychvalují i uživatelé jiných frameworků, je Dependency Injection Container (dále
Nette DI). Podívejte se, jak snadno jej můžete použít kdekoliv,
i mimo Nette.
Mějme aplikaci pro rozesílání newsletterů. Kód jednotlivých tříd
jsem zjednodušil na dřeň. Máme tu objekt představující email:
class Mail
{
public $subject;
public $message;
}
Někoho, kdo ho umí odeslat:
interface Mailer
{
function send(Mail $mail, $to);
}
Přidáme podporu pro logování:
interface Logger
{
function log($message);
}
A nakonec třídu, která rozesílání newsletterů zajišťuje:
class NewsletterManager
{
private $mailer;
private $logger;
function __construct(Mailer $mailer, Logger $logger)
{
$this->mailer = $mailer;
$this->logger = $logger;
}
function distribute(array $recipients)
{
$mail = new Mail;
...
foreach ($recipients as $recipient) {
$this->mailer->send($mail, $recipient);
}
$this->logger->log(...);
}
}
Kód respektuje Dependency Injection, tj. že každá třída pracuje
pouze s proměnnými, které jsme jí předali. Také máme možnost si
Mailer i Logger implementovat po svém,
třeba takto:
class SendMailMailer implements Mailer
{
function send(Mail $mail, $to)
{
mail($to, $mail->subject, $mail->message);
}
}
class FileLogger implements Logger
{
private $file;
function __construct($file)
{
$this->file = $file;
}
function log($message)
{
file_put_contents($this->file, $message . "\n", FILE_APPEND);
}
}
DI kontejner je nejvyšší architekt, který umí stvořit
jednotlivé objekty (v terminologii DI označované jako služby) a poskládat
a nakonfigurovat je přesně podle naší potřeby.
Kontejner pro naši aplikaci by mohl vypadat třeba takto:
class Container
{
private $logger;
private $mailer;
function getLogger()
{
if (!$this->logger) {
$this->logger = new FileLogger('log.txt');
}
return $this->logger;
}
function getMailer()
{
if (!$this->mailer) {
$this->mailer = new SendMailMailer;
}
return $this->mailer;
}
function createNewsletterManager()
{
return new NewsletterManager($this->getMailer(), $this->getLogger());
}
}
Implementace vypadá takto, aby:
se jednotlivé služby vytvářely, až když je potřeba (lazy)
dvojí volání createNewsletterManager využívalo stále
stejný objekt loggeru a maileru
Vytvoříme instanci Container, necháme ji vyrobit managera a
můžeme se pustit do spamování uživatelů newslettery:
$container = new Container;
$manager = $container->createNewsletterManager();
$manager->distribute(...);
Podstatné na Dependency Injection je, že žádná třída nemá závislost
na kontejneru. Tudíž jej můžeme klidně nahradit za jiný. Třeba za
kontejner, který nám vygeneruje Nette DI.
Nette DI
Nette DI je totiž generátor kontejnerů. Instruujeme ho (zpravidla) pomocí
konfiguračních souborů a třeba tato konfigurace vygeneruje cca totéž, jako
byla třída Container:
Ale ještě na chvíli zpět ke ContainerLoader. Uvedený zápis
je podřízen jediné věci: rychlosti. Kontejner se vygeneruje jednou, jeho
kód se zapíše do cache (adresář __DIR__ . '/temp') a při
dalších požadavcích se už jen odsud načítá. Proto je načítání
konfigurace umístěno do closure v metodě
$loader->load().
Během vývoje je užitečné aktivovat auto-refresh mód, kdy se kontejner
automaticky přegeneruje, pokud dojde ke změně jakékoliv třídy nebo
konfiguračního souboru. Stačí v konstruktoru ContainerLoader
uvést jako druhý argument true.
Jak vidíte, použití Nette DI rozhodně není limitované na aplikace
psané v Nette, můžete jej pomocí pouhých 3 řádků kódu nasadit
kdekoliv. Zkuste si s ním pohrát, celý příklad je dostupný na GitHubu.
V Nette je k dispozici nástroj LinkGenerator
pro vytváření odkazů bez nutnosti použití presenterů a přitom stejně
pohodlně. Jak ho použít?
Ukážeme si to na příkladu třídy, která odesílá emaily. Kód emailu
může vypadat nějak takto: (všechny příklady jsou pro Nette
2.4 nebo vyšší)
<title>Subject of message</title>
<p>Hello {$name} <a n:href="Homepage:">click here</a></p>
Třídě předám
LinkGenerator a ta si jej uloží do proměnné
$this->linkGenerator.
class MailSender
{
/** @var Nette\Application\LinkGenerator */
private $linkGenerator;
function __construct(Nette\Application\LinkGenerator $generator)
{
$this->linkGenerator = $generator;
}
Samotné odesílání emailů bude provádět metoda sendEmail,
která si vytvoří objekt Latte:
function sendEmail()
{
$latte = new Latte\Engine;
$latte->setTempDirectory(...);
...
}
U Latte je vhodné nastavit temp directory , aby se šablona s každým
emailem nemusela znovu kompilovat, nicméně pokud jich posíláte jen několik
denně, nebo třeba hodně, ale v jednom requestu (tj. pomocí jednoho objektu
$latte), není to nutné, kompilace je blesková. Kde cestu k temp
složce vzít? Můžeme si ji opět předat, ale chytřejší řešení je
místo toho si předat objekt (tzv. továrničku), který dovede
nakonfigurované Latte vyrobit:
/** @var Nette\Bridges\ApplicationLatte\ILatteFactory */
private $latteFactory;
// $latteFactory opět předáme přes konstruktor
function sendEmail()
{
$latte = $this->latteFactory->create();
// nainstalujeme do $latte makra {link} a n:href
Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
$params = [ // proměnné do šablony
'name' => $order->getName(),
...
];
// a vygenerujeme HTML email
$html = $latte->renderToString(__DIR__ . '/email.latte', $params);
// a odešleme jej, viz dále
}
Tady ještě jednou přeruším, protože musím zmínit ještě jednu
alternativu, a to nechat si vygenerovat přímo známý objekt
$template, který bude obsahovat například proměnné
$basePath apod, už s nakonfigurovaným Latte. Vyměním tedy
latteFactory za templateFactory:
/** @var Nette\Application\UI\ITemplateFactory */
private $templateFactory;
// $templateFactory si opět předáme přes konstruktor
function sendEmail()
{
$template = $this->templateFactory->createTemplate();
$template->name = $order->getName();
$template->setFile(__DIR__ . '/email.latte');
}
Jde o obdobu použití $this->createTemplate() uvnitř
presenteru či komponenty.
...
$mail = new Nette\Mail\Message;
$mail->addTo($order->email);
$mail->setHtmlBody($html); // nebo setHtmlBody($template)
$this->mailer->send($mail); // $mailer si opět předáme konstruktorem
}
A teď zbývá poslední krok! Zapojit do toho LinkGenerator, ke kterému se
konečně dostávám. Je to snadné:
O té chvíle můžete používat makro {link} nebo
n:href.
LinkGenerator generuje všechny URL absolutní, tedy včetně
http://example.com. Taktéž na moduly pohlíží absolutně,
tudíž v odkazech neuvádějte na začátku dvojtečku ani //,
není to potřeba a vlastně tomu ani nebude rozumět.
Pokud v routeru používáte relativní cesty, což je velmi časné,
generátor bere doménu z aktuálního HTTP requestu. Ale v CLI žádný HTTP
request pochopitelně neexistuje. Lze jej však podstrčit třeba
v config.neon:
Jak zprovoznit CSS Flexbox na
iOS zařízeních aneb pár poznámek pro sebe, až zase příště budu zoufat,
proč to neflexí, a tápat v paměti, jak jsem to minule vyřešil.
Prefixy
Safari stále ještě, i ve verzi 8.1, vyžaduje pro Flexible Box prefixy.
Jsi opět překvapený, že? Takže tam hezky doplň
display: -webkit-flex nebo
-webkit-flex-wrap: wrap atd.
Řešíš, jak zajistit minimální mezeru mezi prvky v natahovacím
kontejneru s justify-content: space-between? Vždycky nad tím
dlouze dumáš, googlíš to a nikdy jsi nic nevygooglil.
Mezera má být natahovací, ale nesmí jít pod určité minimum. A krajní
prvky musí přiléhat ke kraji.
Můžeš prvkům nastavit margin-right: x a kontejneru
margin-right: -x, ale to trošku rozhodí layout a na mobilu půjde
stránku horizontálně posouvat. Třeba ti někdo poradí něco
lepšího… Řešením je obalit kontejner do prvku s
overflow: hidden.
Nette ve verzi 2.3 bude case sensitive. Proč a co to
konkrétně znamená?
Case sensitivity, anglicky „citlivost na velikost písmen“, znamená, že
například Homepage a homepage jsou dva naprosto
rozdílné řetězce. PHP je citlivé na velikost písmen u proměnných,
ignoruje ji u tříd a metod. JavaScript, C#, Ruby nebo XML jsou citlivé
u všech identifikátorů, naopak HTML je case insensitive. Souborový systém
na Linuxu je case sensitive, zatímco na Windows a Macu nikoliv. Atd.
Začínal jsem na jazycích, které nebyly na velikost písmen citlivé, a
připadalo mi šílené, že některé jiné jsou. „To si jako u každého
identifikátoru musím pamatovat, které písmenko je malé a které velké?“.
Tohle byla lichá obava, funguje to stejně jako v přirozeném jazyce, prostě
stačí znát, že vlastní jména se píší s velkým počátečním
písmenem, nikoliv to, že jeden konkrétní člověk se jmenuje
Franta a ne franta.
Nicméně mám rád pravidlo „buď velkorysý v tom, co přijímáš, a
striktní v tom, co odesíláš“ a protože striktní lpění na citlivosti
může být až
komické (Nabla je automat), Nette bylo vůči velikosti písmen
tolerantní.
Konkrétně šlo o
jména presenterů
některé parametry v cestě URL
názvy tříd u autoloadingu a DI kontejneru
Být tolerantní v prostředí, které je na velikost písmen citlivé, ba
co hůř, které je citlivé v závislosti na operačním systému apod,
přináší řadu úskalí. Například URL lišící se jen velikostí písmen
mohou mít zcela odlišný obsah, atd.
A přitom vlastně jen tolerujete, že někdo píše jak motejlek.
Proto od verze 2.3 bude Nette na velikost písmen citlivé.
A protože se Nette vždy snaží o co nejbezproblémovější přechod,
bude chyby ve velikosti písmen detekovat a upozorní na ně.
Občas dostávám otázky, proč Nette nemá přesný
harmonogram vydávání verzí, kde by bylo uvedeno, kdy přesně vyjde
příští verze a jak dlouho bude podporovaná. Pokusím se vysvětlit, jak to
v Nette funguje.
Podle mého je ideální vydávat ročně jednu až dvě nové větší
verze. Rychlejší a zejména pomalejší vydávání způsobuje různé
komplikace, a ony 1–2 verze ročně se ukázaly jako ideál.
Zároveň přibližně každých 6 týdnů vydávám opravné setinkové
verze. Někdy je interval kratší, protože se objeví chyba, kterou je třeba
opravit co nejdřív, jindy je delší, protože k vydání verze
není důvod.
Podporu pro každou verzi se snažím držet dlouho:
Nette 0.9 vyšlo 8/2009, poslední 0.9.7 vyšla 1/2012 (2,5 roku)
Nette 2.0 vyšlo 2/2012, poslední 2.0.18 vyšla 12/2014 (2,5 roku)
Nette 2.1 vyšlo 12/2013, aktuální 2.1.9 uvádí, že
podpora bude trvat do 1/2016 (2,1 roku)
Podpora tedy vždy trvá minimálně 2 roky. Pravdou je, že tato
informace nebyla explicitně na webu zmíněna, proto jsem ji doplnil na
stránku download.
Související otázkou je i podpora konkrétní verze PHP. Před nějakou
dobou jsem sliboval,
že PHP 5.3 bude podporované ještě pár let, a skutečně nadcházející
Nette 2.3 (vyjde asi na přelomu února a března) je stále s PHP
5.3 kompatibilní, tedy minimálně dva roky ještě bude 5.3 podporované.
(Nicméně téměř jistě příští verze Nette už 5.3 opustí.)
Zpátky k harmonogramu vydávání nových verzí. Předvídatelné a
pravidelné vydání nových verzí je z mého pohledu ideální, na rozdíl od
kalendáře s přesnými daty, kdy která verze v budoucnu vyjde. Koneckonců
ho téměř žádný jiný podobný open source projekt nemá.
Samozřejmě chápu, že je prima pocit vědět, kdy která verze vyjde,
také bych rád věděl, kdy třeba vyjde PHP 7.0, ale zároveň rozumím, že
nikdo z PHP mi přesné datum nedá.
Vydávání nové verze je poměrně náročný proces, do kterého zasahuje
hodně faktorů, a tím hlavním je být s výsledkem spokojený a stát si za
ním. Kdyby měl do toho ještě zasahovat faktor pevného data, tak to bude jen
na úkor něčeho jiného. A to nechci.
Dovedu si představit typy projektů, kde naopak přesný harmonogram je
výhodou, ale co funguje jinde, není nutně vhodné i pro Nette.
Doplnění: aby nedošlo k nedorozumění, je samozřejmě potřebné,
aby se tým vývojářů domluvil na harmonogramu vydání příští verze,
buď mezi sebou interně, nebo veřejně jako v případě PHP timeline, což je při
koordinaci velkého počtu vývojářů nutnost. Článek se týkal něčeho
jiného, tedy veřejných příslibů, kdy přesně v následujících letech
vyjde která verze.)
Stylish
je doplněk do Chrome, který umožňuje přidat webovým stránkám vlastní
CSS styly.
(Lze ho nainstalovat i do nové Opery, nejprve si přidejte Download
Chrome extension a pak už ho přímo nainstalujete z Chrome webstore.)
Stylish jsem si přidal kvůli GitHubu, který mě štve příliš širokými
tabulátory a hlavně nezalamováním řádků s textem, bez čehož se podobné commity
vůbec nedají číst.
Jsou tomu 3 roky, kdy vyšlo Nette 2.0.0. Šlo o přelomovou
verzi. Uzavřela pár let trvající vývoj a přinesla novinky, bez kterých si
dnes vůbec nelze vývoj v Nette představit.
Shodou okolností v té době byly vydány i přelomové dvojkové verze
významných frameworků Zend a Symfony. Nedá mi to nepřipomenout, že na
rozdíl od nich Nette nehodilo uživatele svých předchozích verzí přes
palubu, tj. neudělalo mezi verzemi tlustou čáru, ale naopak se snažilo
o zachování kompatibility, jak to jen bylo možné. Uživatelé také
například dostali nástroj, který jim ve zdrojových kódech nahradil
původní názvy tříd za nové atd.
PHP 5.2
Řada 2.0 stále držela podporu PHP 5.2, včetně verze PHP 5.2.0, což
bylo skutečně bolestivé. Šlo totiž o jednu z méně povedených verzí
PHP, jenže Debian ji měl předinstalovanou a konzervativní správci ji
odmítali aktualizovat.
Zajímavostí je, že Nette bylo již od
roku 2010 psáno čistě v PHP 5.3 se všemi jeho vymoženostmi, jako
jsou jmenné prostory nebo anonymní funkce. (Dvě) verze pro PHP 5.2 byly
vytvářeny strojově převodníkem. Ten nejen zaměňoval názvy tříd na
varianty bez jmenných prostorů, ale poradil si i s přepisem anonymních
funkcí a řadou dalších odlišností, jako například nemožností použít
func_get_args() jako parametr funkce atd.
Co zpětně vidím jako nejdůležitější přínos Nette 2.0? Dependency
Injection. Ale slovy klasika:
To není jednoduchá věc to Dependency Injection. Není. To je věc, ve
které se ne každý dost dobře vyzná.
DI nahradilo do té doby používaný objektový Service Locator a jeho
statickou variantu, třídu Environment. Čímž naprosto převrátilo způsoby,
jak aplikace navrhovat. Přineslo kvalitativní posun na novou úroveň. Proto
také přepsat aplikaci používající Environment na Dependency Injection je
nesmírně náročné, protože to vlastně znamená ji navrhnout znovu
a lépe.
End of Life
První den roku 2014 vyšlo Nette 2.0.14. Ano, tak hezky to vyšlo 🙂
Tím byl ukončen vývoj řady 2.0 a série vstoupila do roční fáze
critical issues only, kdy byly opravovány jen závažné chyby. Dnes
tato fáze končí. Před pár dny vyšlo Nette 2.0.18,
definitivně poslední verze této řady a také poslední verze pro
PHP 5.2.
Tak sbohem a šáteček!
(Do fáze critical issues only nyní vstupuje řada 2.1. Po
dobu roku 2015 budou opravovány jen závažné chyby.)
Tři roky slýchávám otázku: „Budeš mít školení Nette
i pro pokročilé?“ – „Budu,“ odpovídal jsem, ale zdaleka netušil,
že přípravy zaberou tolik času. Konečně je to tady!
Pokročilé školení Mistrovství
v Nette volně navazuje na základní kurz Vývoj
aplikací v Nette a je určeno všem, kteří už framework používají a
chtějí proniknout do podstaty jeho fungování. Vědět vše o Dependency
Injection a co jim přinese psaní tzv. rozšíření. Jak tvořit vlastní
makra do Latte. Jak správně navrhovat komponenty. Nebo vytvářet vlastní
formulářové prvky. A tak dále.
Příprava trvala tak dlouho hlavně kvůli mé permanentní nespokojenosti
s frameworkem 😉 Nechci školit oblasti, dokud mi nepřipadají perfektní.
Ano, je to asi hloupost, ale jelikož vždycky na školeních otevřeně
říkám i to, co se mi na Nette nelíbí, tak ten dobrý vnitřní pocit je
tuze důležitý.
S verzí Nette 2.2 jsem konečně začal být spokojený.
V říjnu jsem vypsal beta-verzi kurzu, na kterou se přihlásilo 3×
více lidí, než jaká byla kapacita. Mezi nimi možná i ti vaši konkurenti
🙂 Udělal jsem proto testovací školení tři, odladil pořadí
i náročnost témat a všem účastníkům děkuji za feedback.