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.
Pojďte do toho a staňte se partnerem
Nette!
Používá vaše firma Nette?
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ě.
Používáte Nette soukromě?
Stojíme také o vaši podporu. Přihlaste se k pravidelným donations přes
PayPal. Vaše jméno bude vidět na webu Nette.
Kolik peněz je potřeba?
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.
Viz také Freelo.cz:
14 důvodů, proč podpořit Nette
How to mock classes that are defined as final or some of their
methods are final?
Mocking means replacing the original object with its testing imitation that
does not perform any functionality and just looks like the original object. And
pretending the behavior we need to test.
For example, instead of a PDO with methods like query() etc., we create a
mock that pretends working with the database, and instead verifies that the
correct SQL statements are called, etc. More e.g. in the Mockery
documentation.
And in order to be able to pass mock to methods that use PDO
type hint, it is necessary for the mock class to inherit from the PDO. And that
can be a stumbling block. If the PDO or method query() were final, it would not
be possible.
Is there any solution? The first option is not to use the final keyword at
all. This, of course, does not help with the third-party code that it uses, but
mainly detracts from the important element of the object design. For example,
there is dogma that every class should be either final or abstract.
The second and very handy option is to use BypassFinals, which removes
finals from source code on-the-fly and allows mocking of final methods and
classes.
Install it using Composer:
composer require dg/bypass-finals --dev
And just call at the beginning of the test:
require __DIR__ . '/vendor/autoload.php';
DG\BypassFinals::enable();
Thats all. Incredibly black magic 
BypassFinals requires PHP version 5.6 and supports PHP up to 7.2. It can be
used together with any test tool such as PHPUnit or Mockery.
This functionality is directly implemented in the „Nette Tester“: https://tester.nette.org version 2.0 and
can be enabled this way:
require __DIR__ . '/vendor/autoload.php';
Tester\Environment::bypassFinals();
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:
require __DIR__ . '/vendor/autoload.php';
Tester\Environment::bypassFinals();
A je to. Je za tím ukrytá neskutečně černá magie ?
Pokud nepoužíváte Nette Tester, ale třeba PHPUnit, nebudete ochuzeni,
stačí si nainstalovat BypassFinals:
composer require dg/bypass-finals --dev
A na začátku skriptu zavoláte:
require __DIR__ . '/vendor/autoload.php';
DG\BypassFinals::enable();
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ší.
Jak se zbavit upozorňování PhpStormu, NetBeans nebo dalších IDE na
„undefined fields“ a aktivovat napovídání u objektu
$this->template
v presenterech?
Tedy jak změnit tento pohled s podtrženým abc
a prázdným
napovídacím okénkem:
Na tento?
Jednoduše. Stačí do presenteru (například BasePresenter) doplnit tuto
anotaci:
/**
* @property-read \Nette\Bridges\ApplicationLatte\Template|\stdClass $template
*/
abstract class BasePresenter extends Nette\Application\UI\Presenter
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.
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.
Čtěte dokumentaci
Všechny migrační návody najdete v dokumentaci. Pokud
byste ve firmě potřebovali s aktualizací pomoci, jsem vám k dispozici.
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
Dvojici ob_get_contents()
a ob_end_clean()
lze nahradit jedinou funkcí ob_get_clean()
,
z jejíhož názvu se sice vytratilo end
, ale skutečně output
buffering i vypíná:
$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.
What you can't learn from the documentation + patch for
security hole + advice on how to speed up server response.
…pokračování
PHP ssh2 thread safe binaries for Microsoft Windows:
Zmodernizoval jsem kód knihoven Dibi a Texy,
třídy přenesl do jmenných prostorů a využil syntaxe PHP 5.4. Zároveň
jsem doplnil mechanismus, aby knihovny fungovaly i s existujícím kódem,
který používá původní názvy tříd.
Výsledkem je Texy 2.8 (release notes) a Dibi
3.0 (release
notes).
Dibi mělo nést původně označení 2.4, protože krom vnitřního
refactoringu jsem nechtěl přidávat nebo měnit jakoukoliv funkčnost, jako
u Texy 2.8, ale nakonec jsem pár vychytávek přidal a výsledkem je právě
verze 3.0:
- nové výjimky
Dibi\ConstraintViolationException
,
ForeignKeyConstraintViolationException
,
NotNullConstraintViolationException
a UniqueConstraintViolationException
- MySQL: sloupec TIME se převádí na objekty DateInterval namísto DateTime
(BC break)
- SqlsrvDriver: doplněna podpora pro LIMIT & OFFSET
- vylepšen Dibi\Fluent při použití limit & offset
Zmizely ovladače pro SQLite 2 a MsSqlDriver, které nejsou od PHP
5.3 podporované, a MsSql2005Driver se nyní jmenuje SqlsrvDriver (funguje
i starý název). Statická třída dibi
zůstává mimo jmenné
prostory. Pokud si píšete vlastní ovladač, došlo ke změně v rozhraní
u metod escape()
& unescape()
(viz).
Minimální požadovaná verze PHP je 5.4.4, obě knihovny jsou plně
funkční i pod PHP 7. Minifikovaná verze je ve formě archívu PHAR.
Dibi postupně pokrývám testy. Jelikož nepoužívám MS SQL Server, je
tento driver víceméně v rukou komunity. Pokud jej používáte, zkuste
prosím zjistit, proč neprocházejí testy používané
pro jiné servery a co je potřeba změnit.
Velmi pozvolna vzniká i Texy 3.0, ze kterého zmizí dnes už překonané
funkce, jako je třeba podpora jiného kódování než UTF-8, jiného formátu
než HTML5 atd.