<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
	<title>phpFashion</title>
	<link>https://phpfashion.com/cs/</link>
	<language>cs</language>
	<category>blog</category>
	<docs>http://backend.userland.com/rss</docs>
	<image>
		<url>/assets/img/logo.gif?v=1539821622</url>
		<title>phpFashion</title>
		<link>https://phpfashion.com/cs/</link>
	</image>
	<atom:link rel="self" href="https://phpfashion.com/cs/feed/rss" type="application/rss+xml" />

	<item>
		<title>Duck typing je mrtvý, Dejve!</title>
		<link>https://phpfashion.com/cs/duck-typing-je-mrtvy-dejve</link>
		<description>Jsou okamžiky, kdy se v technologiích ukáže pravda tak
jasně, že ji nejde přehlédnout. Když celý svět nezávisle na sobě dojde
ke stejnému závěru. Když čísla řeknou nahlas to, co jste roky cítili
v kódu.

Duck typing je mrtvý. Typy vyhrály. Ve všech jazycích, ve všech
komunitách, na všech frontách. A já mám z toho obrovskou radost, protože
tahle cesta, cesta silných typů, byla vždycky ta správná.



Proroctví

Monkey patching, duck typing, odmítání dependency injection – to
všechno vyrůstá ze stejného kořene. Z přesvědčení, že explicitní
struktura je zbytečná. Že kód nepotřebuje říkat, co očekává a co
vrací. Že volnost je důležitější než předvídatelnost. A že kdo chce
typy, DI a jasné kontrakty, ten prostě nepochopil krásu dynamických
jazyků.

V roce 2007 jsem napsal článek Ruby on Rails?
Děkuji, nechci, kde jsem kritizoval monkey patching – zvyk v Ruby
libovolně předefinovávat existující třídy za běhu programu. Komunita mi
vysvětlila, že jsem zkostnatělý a nerozumím modernímu programování.

V roce 2012 přišel článek Objeví Rails
Dependency Injection?, kde jsem popsal, jak Rails odmítají dependency
injection s argumentem, že „Ruby je tak dobrý jazyk, že DI vůbec
nepotřebuje“. Model Post fungoval zároveň jako entita
i repozitář, závislosti se skrývaly za statickými voláními a celý
systém držel pohromadě jen díky konvencím a naději. Předpověděl jsem,
že Rails jednou DI objeví a bude to velké téma.

Je rok 2026.

Stále neobjevili ☹

JavaScript je mrtvý

Podívejte se na výsledky ankety State of JavaScript 2025.
40 procent vývojářů píše výhradně v TypeScriptu. A kolik jich píše
výhradně v čistém JavaScriptu, tedy bez jakýchkoliv typů?

Šest procent.

To je méně než lidí, kteří věří, že Země je placka. TypeScript se
v roce 2025 stal nejpoužívanějším
jazykem na GitHubu, předběhl Python i samotný JavaScript. Za jediný rok
přibyl milion nových přispěvatelů, nárůst o 66 %.

A co víc: samotná TC39, komise zodpovědná za vývoj JavaScriptu, pracuje
na návrhu,
který přidá typové anotace přímo do jazyka. Víte proč? Protože
„statické typování“ bylo v anketě State of JS nejžádanější
chybějící feature pět let po sobě. Každý rok. 2020, 2021, 2022, 2023,
2024. Programátoři chtěli typy tak zoufale, že se jim to vtisklo do
samotné specifikace jazyka.

JavaScript bez typů je mrtvý jazyk. Jen o tom ještě neví.

Kachna přiznala barvu

A Python? Python, posvátná země duck typingu? Vlast, kde se generace
programátorů učily, že „pokud to chodí jako kachna a kváká jako kachna,
tak je to kachna a víc nepotřebujete vědět“?

86 %
Python vývojářů dnes pravidelně používá typové anotace. Guido van
Rossum, tatíček Pythonu, ten samý člověk, který do jazyka desítky let
žádné typy nedával, prohlásil typové anotace za zásadní přínos pro
produktivitu velkých projektů. A pak šel pracovat na mypy, nástroji pro
statickou typovou kontrolu.

FastAPI, nejpopulárnější Python web framework, je na typech doslova
postavený – bez typových anotací ani nenastartuje. Pydantic,
knihovna pro validaci dat, na které stojí půlka AI ekosystému, je na typech
závislá existenčně.

Kachna si nechala přidat typové anotace.

A Ruby? Ruby typový ekosystém je rozštěpený na dva nekompatibilní
systémy – Sorbet od Stripe a oficiální RBS. Ani jeden nedosáhl masového
přijetí. DHH, tvůrce Ruby on Rails, stojí na barikádě s transparentem
„Typy ne!“, odmítá je, odmítá DI, odmítá v podstatě všechno, co
softwarové inženýrství za posledních dvacet let považuje za osvědčenou
praxi. Na RubyKaigi 2025 se oba tábory konečně pokusily najít společnou
řeč a propojit Sorbet s RBS. Zda to k něčemu povede, to se
teprve ukáže.

Všechny dynamické jazyky šly stejným směrem. K typům. Jedny rychleji,
druhé pomaleji. Ale všechny.

My jsme to dělali od začátku

PHP má runtime typy od roku 2004, kdy vyšla verze 5. Ale cesta k plným
typům nebyla přímočará. V roce 2015 se hlasovalo o skalárních typech
pro PHP 7 a málem neprošly. Hlasování skončilo 108 ku 48 – jen těsně
nad požadovanou dvoutřetinovou většinou. Stačily čtyři hlasy jinak a PHP
by skalární typy nemělo. Říkalo se tomu „The Great Scalar Type War“ a
bylo to jedno z nejdramatičtějších hlasování v historii PHP. Naštěstí
rozum zvítězil.

V Nette jsme tuhle cestu razili dávno předtím, než to bylo trendy. Nette
Framework měl plnou dependency injection od verze 2.0 v roce 2012. Ne jako
volitelný doplněk, ale jako základ celé architektury. V době, kdy DHH
prohlašoval DI za zbytečný Java pattern, my jsme kolem DI vybudovali
kompletní ekosystém a napsali
dokumentaci, která dodnes patří k nejsrozumitelnějším výkladům
dependency injection v jakémkoliv jazyce. A edukovali jsme v tom celou
českou PHP komunitu.

Nette 3.0, vydaný v dubnu 2019, se stal prvním full-stack PHP frameworkem
na světě s kompletním typovým systémem. Všechny soubory
declare(strict_types=1). Všechny parametry otypované. Všechny
návratové hodnoty. Všechny vlastnosti.

Symfony 5.0 přidal plné typy o sedm měsíců později. Laravel?

Laravel nemá strict types ani v roce 2026. A často nemá ani nativní
typy samotné.

A pak tu máme PHPStan od Ondřeje Mirtese – český nástroj, který má
dnes 344 milionů stažení a díky němuž je typová kontrola v PHP
přísnější než v leckterém staticky typovaném jazyce. V poslední verzi
PHPStan 2.0 přibyl level 10, který detekuje i implicitně typované mixed
hodnoty.

Proč to zabila AI

Víte, co definitivně pohřbilo duck typing? Ne programátoři. Ne
frameworky. Umělá inteligence.

Studie
z roku 2025 zjistila, že 94 % chyb v kompilaci kódu generovaného
jazykovými modely jsou chyby typové kontroly. Čtyřiadevadesát procent.
Typový systém je záchranná síť pro kód, který jste nenapsali sami.

Když dnes sedím u Claude Code a pracuji na PHP kódu s plnými typy a
PHPStanem, AI agent rozumí mému kódu. Ví, co funkce přijímá. Ví, co
vrací. Ví, jaké má objekt vlastnosti. Ví, co smí a co ne.
V duck-typovaném kódu by házel kostkou.

Duck typing byl vždycky prasárna. Ale aspoň prasárna, která v malém
měřítku nějak fungovala. V éře, kdy AI agent generuje stovky řádků
kódu denně, je to prasárna, která nefunguje ani trochu.



Takže shrnu: TypeScript pohřbil vanilla JavaScript. Python si přidal typy
a 86 % vývojářů je používá. Ruby se v tom topí. A PHP? PHP má plné
runtime typy, PHPStan se 344 miliony stažení a Nette, které tohle dělalo
dřív, než to bylo cool.

Normálně bych teď zakončil sebeironickým odstavcem o tom, jak nikdo
nemá rád chlapíka, který říká „já to říkal“. Ale víte co?

Já to říkal.
</description>
		<content:encoded><![CDATA[
		<p class="perex">Jsou okamžiky, kdy se v technologiích ukáže pravda tak
jasně, že ji nejde přehlédnout. Když celý svět nezávisle na sobě dojde
ke stejnému závěru. Když čísla řeknou nahlas to, co jste roky cítili
v kódu.</p>

<p>Duck typing je mrtvý. Typy vyhrály. Ve všech jazycích, ve všech
komunitách, na všech frontách. A já mám z toho obrovskou radost, protože
tahle cesta, cesta silných typů, byla vždycky ta správná.</p>

<figure><img loading="lazy" src="/media/1b209a2782.webp" alt="" width="1400"
height="933"></figure>

<h2 id="toc-proroctvi">Proroctví</h2>

<p>Monkey patching, duck typing, odmítání dependency injection – to
všechno vyrůstá ze stejného kořene. Z přesvědčení, že explicitní
struktura je zbytečná. Že kód nepotřebuje říkat, co očekává a co
vrací. Že volnost je důležitější než předvídatelnost. A že kdo chce
typy, DI a jasné kontrakty, ten prostě nepochopil krásu dynamických
jazyků.</p>

<p>V roce 2007 jsem napsal článek <a
href="https://phpfashion.com/cs/ruby-on-rails-dekuji-nechci">Ruby on Rails?
Děkuji, nechci</a>, kde jsem kritizoval monkey patching – zvyk v Ruby
libovolně předefinovávat existující třídy za běhu programu. Komunita mi
vysvětlila, že jsem zkostnatělý a nerozumím modernímu programování.</p>

<p>V roce 2012 přišel článek <a
href="https://phpfashion.com/cs/objevi-rails-dependency-injection">Objeví Rails
Dependency Injection?</a>, kde jsem popsal, jak Rails odmítají dependency
injection s argumentem, že „Ruby je tak dobrý jazyk, že DI vůbec
nepotřebuje“. Model <code>Post</code> fungoval zároveň jako entita
i repozitář, závislosti se skrývaly za statickými voláními a celý
systém držel pohromadě jen díky konvencím a naději. Předpověděl jsem,
že Rails jednou DI objeví a bude to velké téma.</p>

<p>Je rok 2026.</p>

<p>Stále neobjevili ☹</p>

<h2 id="toc-javascript-je-mrtvy">JavaScript je mrtvý</h2>

<p>Podívejte se na výsledky ankety <a
href="https://2025.stateofjs.com/en-US/usage/">State of JavaScript 2025</a>.
40 procent vývojářů píše výhradně v TypeScriptu. A kolik jich píše
výhradně v čistém JavaScriptu, tedy bez jakýchkoliv typů?</p>

<p>Šest procent.</p>

<p>To je méně než lidí, kteří věří, že Země je placka. TypeScript se
v roce 2025 stal <a
href="https://github.blog/ai-and-ml/llms/why-ai-is-pushing-developers-toward-typed-languages/">nejpoužívanějším
jazykem na GitHubu</a>, předběhl Python i samotný JavaScript. Za jediný rok
přibyl milion nových přispěvatelů, nárůst o 66 %.</p>

<p>A co víc: samotná TC39, komise zodpovědná za vývoj JavaScriptu, pracuje
na <a href="https://github.com/tc39/proposal-type-annotations">návrhu</a>,
který přidá typové anotace přímo do jazyka. Víte proč? Protože
„statické typování“ bylo v anketě State of JS nejžádanější
chybějící feature pět let po sobě. Každý rok. 2020, 2021, 2022, 2023,
2024. Programátoři chtěli typy tak zoufale, že se jim to vtisklo do
samotné specifikace jazyka.</p>

<p>JavaScript bez typů je mrtvý jazyk. Jen o tom ještě neví.</p>

<h2 id="toc-kachna-priznala-barvu">Kachna přiznala barvu</h2>

<p>A Python? Python, posvátná země duck typingu? Vlast, kde se generace
programátorů učily, že „pokud to chodí jako kachna a kváká jako kachna,
tak je to kachna a víc nepotřebujete vědět“?</p>

<p><a
href="https://engineering.fb.com/2025/12/22/developer-tools/python-typing-survey-2025-code-quality-flexibility-typing-adoption/">86 %
Python vývojářů</a> dnes pravidelně používá typové anotace. Guido van
Rossum, tatíček Pythonu, ten samý člověk, který do jazyka desítky let
žádné typy nedával, prohlásil typové anotace za zásadní přínos pro
produktivitu velkých projektů. A pak šel pracovat na mypy, nástroji pro
statickou typovou kontrolu.</p>

<p>FastAPI, nejpopulárnější Python web framework, je na typech doslova
<i>postavený</i> – bez typových anotací ani nenastartuje. Pydantic,
knihovna pro validaci dat, na které stojí půlka AI ekosystému, je na typech
závislá existenčně.</p>

<p>Kachna si nechala přidat typové anotace.</p>

<p>A Ruby? Ruby typový ekosystém je rozštěpený na dva nekompatibilní
systémy – Sorbet od Stripe a oficiální RBS. Ani jeden nedosáhl masového
přijetí. DHH, tvůrce Ruby on Rails, stojí na barikádě s transparentem
„Typy ne!“, odmítá je, odmítá DI, odmítá v podstatě všechno, co
softwarové inženýrství za posledních dvacet let považuje za osvědčenou
praxi. Na RubyKaigi 2025 se oba tábory konečně pokusily najít společnou
řeč a propojit Sorbet s RBS. Zda to k něčemu povede, to se
teprve ukáže.</p>

<p>Všechny dynamické jazyky šly stejným směrem. K typům. Jedny rychleji,
druhé pomaleji. Ale všechny.</p>

<h2 id="toc-my-jsme-to-delali-od-zacatku">My jsme to dělali od začátku</h2>

<p>PHP má runtime typy od roku 2004, kdy vyšla verze 5. Ale cesta k plným
typům nebyla přímočará. V roce 2015 se hlasovalo o skalárních typech
pro PHP 7 a málem neprošly. Hlasování skončilo 108 ku 48 – jen těsně
nad požadovanou dvoutřetinovou většinou. Stačily čtyři hlasy jinak a PHP
by skalární typy nemělo. Říkalo se tomu „The Great Scalar Type War“ a
bylo to jedno z nejdramatičtějších hlasování v historii PHP. Naštěstí
rozum zvítězil.</p>

<p>V Nette jsme tuhle cestu razili dávno předtím, než to bylo trendy. Nette
Framework měl plnou dependency injection od verze 2.0 v roce 2012. Ne jako
volitelný doplněk, ale jako základ celé architektury. V době, kdy DHH
prohlašoval DI za zbytečný Java pattern, my jsme kolem DI vybudovali
kompletní ekosystém a <a
href="https://doc.nette.org/cs/dependency-injection/introduction">napsali
dokumentaci</a>, která dodnes patří k nejsrozumitelnějším výkladům
dependency injection v jakémkoliv jazyce. A edukovali jsme v tom celou
českou PHP komunitu.</p>

<p>Nette 3.0, vydaný v dubnu 2019, se stal prvním full-stack PHP frameworkem
na světě s kompletním typovým systémem. Všechny soubory
<code>declare(strict_types=1)</code>. Všechny parametry otypované. Všechny
návratové hodnoty. Všechny vlastnosti.</p>

<p>Symfony 5.0 přidal plné typy o sedm měsíců později. Laravel?</p>

<p>Laravel nemá strict types ani v roce 2026. A často nemá ani nativní
typy samotné.</p>

<p>A pak tu máme PHPStan od Ondřeje Mirtese – český nástroj, který má
dnes 344 milionů stažení a díky němuž je typová kontrola v PHP
přísnější než v leckterém staticky typovaném jazyce. V poslední verzi
PHPStan 2.0 přibyl level 10, který detekuje i implicitně typované mixed
hodnoty.</p>

<h2 id="toc-proc-to-zabila-ai">Proč to zabila AI</h2>

<p>Víte, co definitivně pohřbilo duck typing? Ne programátoři. Ne
frameworky. Umělá inteligence.</p>

<p><a
href="https://github.blog/ai-and-ml/llms/why-ai-is-pushing-developers-toward-typed-languages/">Studie
z roku 2025</a> zjistila, že 94 % chyb v kompilaci kódu generovaného
jazykovými modely jsou chyby typové kontroly. Čtyřiadevadesát procent.
Typový systém je záchranná síť pro kód, který jste nenapsali sami.</p>

<p>Když dnes sedím u Claude Code a pracuji na PHP kódu s plnými typy a
PHPStanem, AI agent rozumí mému kódu. Ví, co funkce přijímá. Ví, co
vrací. Ví, jaké má objekt vlastnosti. Ví, co smí a co ne.
V duck-typovaném kódu by házel kostkou.</p>

<p>Duck typing byl vždycky prasárna. Ale aspoň prasárna, která v malém
měřítku nějak fungovala. V éře, kdy AI agent generuje stovky řádků
kódu denně, je to prasárna, která nefunguje ani trochu.</p>

<hr>

<p>Takže shrnu: TypeScript pohřbil vanilla JavaScript. Python si přidal typy
a 86 % vývojářů je používá. Ruby se v tom topí. A PHP? PHP má plné
runtime typy, PHPStan se 344 miliony stažení a Nette, které tohle dělalo
dřív, než to bylo cool.</p>

<p>Normálně bych teď zakončil sebeironickým odstavcem o tom, jak nikdo
nemá rád chlapíka, který říká „já to říkal“. Ale víte co?</p>

<p>Já to říkal.</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/duck-typing-je-mrtvy-dejve#comments</comments>
		<pubDate>Mon, 06 Apr 2026 23:58:00 +0200</pubDate>
		<guid isPermaLink="false">item2491@http://phpfashion.com</guid>
		<enclosure url="https://phpfashion.com/media/1b209a2782.webp" length="83832" type="image/webp" />
	</item>
	<item>
		<title>Jak si opravou pro PHPStan zadělat na bug</title>
		<link>https://phpfashion.com/cs/phpstan-jak-si-zadelat-na-bug</link>
		<description>PHPStan hlásí chybu, vy ji opravíte. Jenže tou opravou jste
kód paradoxně zhoršili. Jak je to možné?



Pamatuju si, jak jsem jednou projížděl výstup PHPStanu a systematicky
opravoval hlášku za hláškou. Cítil jsem se produktivně. Kód je čistší,
typy sedí, zelená všude. O měsíc později jsem si lámal hlavu, proč mi
funkce vrací prázdný string, když by neměla. Detektivka na hodinu, přitom
viník byl jasný: já a moje „oprava“.

Nejdřív důležitá věc: PHPStan dělá
přesně to, co má. Upozorní vás, že funkce může vrátit null
nebo false, a donutí vás se nad tím zamyslet. To je skvělé.
Problém je až vaše reakce.

Nevinný příklad

Mějme funkci, která z textu odstraní nadbytečné mezery:

function normalizeSpaces(string $s): string
{
	return preg_replace(&apos;#\s+#&apos;, &apos; &apos;, $s);
}


PHPStan zahlásí: Function preg_replace returns string|null but function
should return string. No jasně, preg_replace může vrátit
null, pokud dojde k chybě v regexu. Tak to opravíme, ne?

function normalizeSpaces(string $s): string
{
	return (string) preg_replace(&apos;#\s+#&apos;, &apos; &apos;, $s);
}


PHPStan je spokojený. Commit, push, hotovo.

Jenže.

Co jste vlastně udělali

Ten původní kód byl ve skutečnosti lepší. Pokud by
preg_replace někdy vrátil null, třeba kvůli …
(ne, nenapadá mě proč), PHP by vyhodilo TypeError. Fatální chyba.
Tracy by se rozsvítila, v logu by se to objevilo, prostě byste se o tom
dozvěděli. (Jo aha! Kvůli tomuto může vrátit null!)

Po vaší „opravě“ se null tiše přetypuje na prázdný
string. Funkce vrátí &quot;&quot;, aplikace jede dál a vy nemáte
tušení, že se něco pokazilo. Data se poškodí bez jakéhokoli
varování.

Gratuluju, právě jste kód zhoršili 🙂

A preg_replace není ojedinělý případ. Spousta PHP funkcí
vrací false nebo null pro situace, které při
normálním použití prakticky nenastanou: json_encode,
ob_get_contents, getcwd, gzcompress,
celá řada Intl funkcí. Pokaždé, když sáhnete po přetypování, zastavte
se a položte si otázku: nezahazujete tím informaci o
(nepravděpodobné) chybě?

Správné řešení

Pokud chcete uspokojit PHPStan a zároveň zachovat původní chování,
použijte throw expression:

function normalizeSpaces(string $s): string
{
	return preg_replace(&apos;#\s+#&apos;, &apos; &apos;, $s)
		?? throw new \LogicException(&apos;preg_replace failed&apos;);
}


Tím říkáte: „Vím, že teoreticky může nastat chyba. Pokud nastane,
chci o tom vědět.“ V podstatě jste explicitně zapsali to, co tam bylo
implicitně předtím, fatálku při selhání. PHPStan spokojený, kód
nezhoršený.

Ale moment. Ono to jde i jednodušeji. Tyhle bagatelní chyby můžete
v PHPStanu prostě ignorovat, přidat je do ignoreErrors v
phpstan.neon nebo anotací @phpstan-ignore. A je to
naprosto legitimní. Vždyť to původní chování, kdy PHP vyhodí TypeError,
je v podstatě to, co chcete. Proč byste kvůli tomu měnili kód?

Ještě lepší je problém vůbec nemít. Proto vznikají wrappery,
kupříkladu Nette\Utils\Strings::replace() obaluje
preg_replace a při chybě vyhodí výjimku. Podobně
Nette\Utils\Json::encode() místo json_encode.
Použijete jednu funkci a problém zmizí, žádný null, žádný
false, nic k řešení.

Další možnost je vyřešit to na úrovni PHPStanu rozšířením, které
u vybraných funkcí odstraní false nebo null
z návratového typu. Například nette/phpstan-rules tohle
dělá pro desítky
PHP funkcí. U regexových funkcí navíc kontroluje, zda je pattern
konstantní řetězec, a pokud ano, null z typu odstraní,
protože chyba v regexu nenastane.

Je to samozřejmě opinionated přístup. A to mi na PHPStanu přesně
vyhovuje: je přísný, a můžu si ho přizpůsobit rozšířením
podle svého.



Tichá chyba je horší než hlasitá. A (string) je ten
nejtišší způsob, jak ji vyrobit.
</description>
		<content:encoded><![CDATA[
		<p class="perex">PHPStan hlásí chybu, vy ji opravíte. Jenže tou opravou jste
kód paradoxně zhoršili. Jak je to možné?</p>

<figure><img loading="lazy" src="/media/f782a4e49e.webp" alt="" width="1400"
height="933"></figure>

<p>Pamatuju si, jak jsem jednou projížděl výstup PHPStanu a systematicky
opravoval hlášku za hláškou. Cítil jsem se produktivně. Kód je čistší,
typy sedí, zelená všude. O měsíc později jsem si lámal hlavu, proč mi
funkce vrací prázdný string, když by neměla. Detektivka na hodinu, přitom
viník byl jasný: já a moje „oprava“.</p>

<p>Nejdřív důležitá věc: <a href="https://phpstan.org">PHPStan</a> dělá
přesně to, co má. Upozorní vás, že funkce může vrátit <code>null</code>
nebo <code>false</code>, a donutí vás se nad tím zamyslet. To je skvělé.
Problém je až vaše reakce.</p>

<h2 id="toc-nevinny-priklad">Nevinný příklad</h2>

<p>Mějme funkci, která z textu odstraní nadbytečné mezery:</p>

<pre
class="language-php"><code>function normalizeSpaces(string $s): string
{
	return preg_replace(&#039;#\s+#&#039;, &#039; &#039;, $s);
}
</code></pre>

<p>PHPStan zahlásí: <em>Function preg_replace returns string|null but function
should return string.</em> No jasně, <code>preg_replace</code> může vrátit
<code>null</code>, pokud dojde k chybě v regexu. Tak to opravíme, ne?</p>

<pre
class="language-php"><code>function normalizeSpaces(string $s): string
{
	return (string) preg_replace(&#039;#\s+#&#039;, &#039; &#039;, $s);
}
</code></pre>

<p>PHPStan je spokojený. Commit, push, hotovo.</p>

<p>Jenže.</p>

<h2 id="toc-co-jste-vlastne-udelali">Co jste vlastně udělali</h2>

<p>Ten původní kód byl <b>ve skutečnosti lepší</b>. Pokud by
<code>preg_replace</code> někdy vrátil <code>null</code>, třeba kvůli …
<em>(ne, nenapadá mě proč)</em>, PHP by vyhodilo TypeError. Fatální chyba.
Tracy by se rozsvítila, v logu by se to objevilo, prostě byste se o tom
dozvěděli. <em>(Jo aha! Kvůli <b>tomuto</b> může vrátit null!)</em></p>

<p>Po vaší „opravě“ se <code>null</code> tiše přetypuje na prázdný
string. Funkce vrátí <code>""</code>, aplikace jede dál a vy nemáte
tušení, že se něco pokazilo. Data se poškodí bez jakéhokoli
varování.</p>

<p>Gratuluju, právě jste kód zhoršili 🙂</p>

<p>A <code>preg_replace</code> není ojedinělý případ. Spousta PHP funkcí
vrací <code>false</code> nebo <code>null</code> pro situace, které při
normálním použití prakticky nenastanou: <code>json_encode</code>,
<code>ob_get_contents</code>, <code>getcwd</code>, <code>gzcompress</code>,
celá řada Intl funkcí. Pokaždé, když sáhnete po přetypování, zastavte
se a položte si otázku: nezahazujete tím informaci o
(nepravděpodobné) chybě?</p>

<h2 id="toc-spravne-reseni">Správné řešení</h2>

<p>Pokud chcete uspokojit PHPStan a zároveň zachovat původní chování,
použijte <code>throw</code> expression:</p>

<pre
class="language-php"><code>function normalizeSpaces(string $s): string
{
	return preg_replace(&#039;#\s+#&#039;, &#039; &#039;, $s)
		?? throw new \LogicException(&#039;preg_replace failed&#039;);
}
</code></pre>

<p>Tím říkáte: „Vím, že teoreticky může nastat chyba. Pokud nastane,
chci o tom vědět.“ V podstatě jste explicitně zapsali to, co tam bylo
implicitně předtím, fatálku při selhání. PHPStan spokojený, kód
nezhoršený.</p>

<p>Ale moment. Ono to jde i jednodušeji. Tyhle bagatelní chyby můžete
v PHPStanu prostě ignorovat, přidat je do <code>ignoreErrors</code> v
<code>phpstan.neon</code> nebo anotací <code>@phpstan-ignore</code>. A je to
naprosto legitimní. Vždyť to původní chování, kdy PHP vyhodí TypeError,
je v podstatě to, co chcete. Proč byste kvůli tomu měnili kód?</p>

<p>Ještě lepší je problém vůbec nemít. Proto vznikají wrappery,
kupříkladu <code>Nette\Utils\Strings::replace()</code> obaluje
<code>preg_replace</code> a při chybě vyhodí výjimku. Podobně
<code>Nette\Utils\Json::encode()</code> místo <code>json_encode</code>.
Použijete jednu funkci a problém zmizí, žádný <code>null</code>, žádný
<code>false</code>, nic k řešení.</p>

<p>Další možnost je vyřešit to na úrovni PHPStanu rozšířením, které
u vybraných funkcí odstraní <code>false</code> nebo <code>null</code>
z návratového typu. Například <a
href="https://github.com/nette/phpstan-rules">nette/phpstan-rules</a> tohle
dělá pro <a
href="https://github.com/nette/phpstan-rules/blob/master/extension-php.neon">desítky
PHP funkcí</a>. U regexových funkcí navíc kontroluje, zda je pattern
konstantní řetězec, a pokud ano, <code>null</code> z typu odstraní,
protože chyba v regexu <i>nenastane</i>.</p>

<p>Je to samozřejmě opinionated přístup. A to mi na PHPStanu přesně
vyhovuje: je přísný, a můžu si ho přizpůsobit rozšířením
podle svého.</p>

<hr>

<p>Tichá chyba je horší než hlasitá. A <code>(string)</code> je ten
nejtišší způsob, jak ji vyrobit.</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/phpstan-jak-si-zadelat-na-bug#comments</comments>
		<pubDate>Sat, 21 Feb 2026 15:29:00 +0100</pubDate>
		<guid isPermaLink="false">item2444@http://phpfashion.com</guid>
		<enclosure url="https://phpfashion.com/media/b5572c439e.webp" length="38352" type="image/webp" />
	</item>
	<item>
		<title>Velký přehled porovnávání v PHP je tu!</title>
		<link>https://phpfashion.com/cs/velky-prehled-porovnavani-v-php</link>
		<description>Už žádné psaní testovacích skriptů, když si nejste
stoprocentně jistí. Už žádné zdlouhavé listování v dokumentaci.
Konečně je tu tabulka pravdy PHP. Připravil jsem pro vás definitivní PHP
Comparison Cheat Sheet. Je to mapa pro území, kde neplatí
===.

Protože PHP 8 v tomto ohledu přepsalo pravidla, tabulky jsou dvě:

👉 Tabulka pro PHP
8.x (Současnost, kterou musíte znát)
👉 Tabulka
pro PHP 7.x (Pro legacy warriors a archeology)



Všichni jsme se naučili používat ===, abychom měli klidné
spaní. Je to naše jistota. Jenže co ve chvíli, kdy nepotřebujete vědět,
jestli jsou hodnoty totožné, ale která je větší nebo menší? Tady
veškerá jistota končí. Pro operátory &lt;, &gt;,
&lt;= a &gt;= totiž žádná „strict“ verze
neexistuje. PHP v tu chvíli přebírá otěže a spouští type juggling.
Víte s jistotou, jak se zachová porovnání čísla a řetězce? Nebo
null a false?

Stačí se podívat do tabulky a okamžitě vidíte, jak se k sobě typy
chovají, když je PHP nutí do interakce. Začíná kompletním přehledem
všech operátorů včetně spaceship (&lt;=&gt;).

Odhalíte tiché chyby
dřív, než nastanou

Uveďme si dva příklady, které vás mohou stát hodiny ladění. Různé
PHP funkce používají různé strategie porovnávání. Třeba funkce
sort() má jako výchozí nastavení SORT_REGULAR.

Jak se zachová u řetězců, které vypadají jako čísla, například
&quot;042&quot; a &quot; 42&quot;? Jak je seřadí?


	V tabulce si najdu sekci SORT_REGULAR

	Podívám se na průsečík těchto hodnot.

	Vidím symbol =

	Co to znamená: PHP je v tomto režimu považuje za identické. Výsledné
	pořadí těchto prvků po seřazení bude náhodné (nedefinované). Pokud na
	pořadí záleží, máme problém.


A co array_unique()? „Nezkanibalizuje“ mi potichu data,
když se v poli potká &quot;042&quot; a &quot; 42&quot;? Nemusíte nic
zkoušet.


	Funkce array_unique() má jako výchozí nastavení SORT_STRING

	Podívám se na průsečík těchto hodnot.

	Vidím symbol &lt;

	Co to znamená: Dopadne do dobře, hodnoty se liší (protože se vše
	převede na string)


Díky tabulce nemusíte hádat. Okamžitě vidíte, kdy musíte přepnout
flag, aby aplikace dělala přesně to, co chcete.

(A ano, žádný flag pro striktní porovnávání bez type juggling v PHP
neexistuje 😤)

Fajnšmekroviny: DateTime a
Closures

Aneb co nejspíš nevíte o porovnávání objektů v PHP.

Vezměte si takový DateTime. Mnoho vývojářů má zafixováno, že
objekty se porovnávat nedají, a tak data zoufale převádí na timestampy nebo
formátované stringy typu &apos;Y-m-d H:i:s&apos;, jen aby zjistili, co
nastalo dřív. Zbytečně! Třídy DateTime a
DateTimeImmutable mají implementovanou logiku pro běžné
porovnávací operátory. Můžete se ptát na větší/menší stejně
přirozeně jako u čísel. Žádné helpery, žádné formátování, čistá
syntaxe. Proto si to zasloužilo vlastní sekci DateTime
v tabulce.

Ještě větší zábava začíná u rovnosti. Zatímco === je
u objektů nekompromisní a zajímá ho, jestli držíte v ruce identickou
instanci, operátor == je u data mnohem pragmatičtější a
porovnává časovou hodnotu. Díky tomu můžete porovnat dva různé objekty,
a pokud ukazují stejný čas, PHP řekne „ano, to se rovná“. A co
víc – funguje to i křížem mezi DateTime a
DateTimeImmutable!

A třešnička na dortu? Closures. I anonymní funkce jsou objekty. Kdy
jsou dvě closures rovny? Podívejte se do tabulky!
</description>
		<content:encoded><![CDATA[
		<p class="perex">Už žádné psaní testovacích skriptů, když si nejste
stoprocentně jistí. Už žádné zdlouhavé listování v dokumentaci.
Konečně je tu tabulka pravdy PHP. Připravil jsem pro vás definitivní <b>PHP
Comparison Cheat Sheet</b>. Je to mapa pro území, kde neplatí
<code>===</code>.</p>

<p>Protože PHP 8 v tomto ohledu přepsalo pravidla, tabulky jsou dvě:</p>

<p>👉 <b><a
href="https://phpfashion.com/attachments/php-comparison/8.x/">Tabulka pro PHP
8.x</a></b> (Současnost, kterou musíte znát)<br>
👉 <b><a href="https://phpfashion.com/attachments/php-comparison/7.x/">Tabulka
pro PHP 7.x</a></b> (Pro legacy warriors a archeology)</p>

<figure><img loading="lazy" src="/media/fb22c7f238.webp" alt="" width="1400"
height="933"></figure>

<p>Všichni jsme se naučili používat <code>===</code>, abychom měli klidné
spaní. Je to naše jistota. Jenže co ve chvíli, kdy nepotřebujete vědět,
jestli jsou hodnoty totožné, ale která je větší nebo menší? Tady
veškerá jistota končí. Pro operátory <code>&lt;</code>, <code>&gt;</code>,
<code>&lt;=</code> a <code>&gt;=</code> totiž žádná „strict“ verze
neexistuje. PHP v tu chvíli přebírá otěže a spouští type juggling.
Víte s jistotou, jak se zachová porovnání čísla a řetězce? Nebo
<code>null</code> a <code>false</code>?</p>

<p>Stačí se podívat do tabulky a okamžitě vidíte, jak se k sobě typy
chovají, když je PHP nutí do interakce. Začíná kompletním přehledem
všech operátorů včetně spaceship (<code>&lt;=&gt;</code>).</p>

<h2 id="toc-odhalite-tiche-chyby-driv-nez-nastanou">Odhalíte tiché chyby
dřív, než nastanou</h2>

<p>Uveďme si dva příklady, které vás mohou stát hodiny ladění. Různé
PHP funkce používají různé strategie porovnávání. Třeba funkce
<code>sort()</code> má jako výchozí nastavení <code>SORT_REGULAR</code>.</p>

<p>Jak se zachová u řetězců, které vypadají jako čísla, například
<code>"042"</code> a <code>" 42"</code>? Jak je seřadí?</p>

<ul>
	<li>V tabulce si najdu sekci <a
	href="https://phpfashion.com/attachments/php-comparison/8.x/#SORT_REGULAR">SORT_REGULAR</a></li>

	<li>Podívám se na průsečík těchto hodnot.</li>

	<li>Vidím symbol <b><code>=</code></b></li>

	<li>Co to znamená: PHP je v tomto režimu považuje za identické. Výsledné
	pořadí těchto prvků po seřazení bude náhodné (nedefinované). Pokud na
	pořadí záleží, máme problém.</li>
</ul>

<p>A co <code>array_unique()</code>? „Nezkanibalizuje“ mi potichu data,
když se v poli potká <code>"042"</code> a <code>" 42"</code>? Nemusíte nic
zkoušet.</p>

<ul>
	<li>Funkce <code>array_unique()</code> má jako výchozí nastavení <a
	href="https://phpfashion.com/attachments/php-comparison/8.x/#SORT_STRING">SORT_STRING</a></li>

	<li>Podívám se na průsečík těchto hodnot.</li>

	<li>Vidím symbol <b><code>&lt;</code></b></li>

	<li>Co to znamená: Dopadne do dobře, hodnoty se liší (protože se vše
	převede na string)</li>
</ul>

<p>Díky tabulce nemusíte hádat. Okamžitě vidíte, kdy musíte přepnout
flag, aby aplikace dělala přesně to, co chcete.</p>

<p>(A ano, žádný flag pro striktní porovnávání bez type juggling v PHP
neexistuje 😤)</p>

<h2 id="toc-fajnsmekroviny-datetime-a-closures">Fajnšmekroviny: DateTime a
Closures</h2>

<p>Aneb co nejspíš nevíte o porovnávání objektů v PHP.</p>

<p>Vezměte si takový <b>DateTime</b>. Mnoho vývojářů má zafixováno, že
objekty se porovnávat nedají, a tak data zoufale převádí na timestampy nebo
formátované stringy typu <code>'Y-m-d H:i:s'</code>, jen aby zjistili, co
nastalo dřív. Zbytečně! Třídy <code>DateTime</code> a
<code>DateTimeImmutable</code> mají implementovanou logiku pro běžné
porovnávací operátory. Můžete se ptát na větší/menší stejně
přirozeně jako u čísel. Žádné helpery, žádné formátování, čistá
syntaxe. Proto si to zasloužilo vlastní sekci <a
href="https://phpfashion.com/attachments/php-comparison/8.x/#datetime">DateTime</a>
v tabulce.</p>

<p>Ještě větší zábava začíná u rovnosti. Zatímco <code>===</code> je
u objektů nekompromisní a zajímá ho, jestli držíte v ruce identickou
instanci, operátor <code>==</code> je u data mnohem pragmatičtější a
porovnává časovou hodnotu. Díky tomu můžete porovnat dva různé objekty,
a pokud ukazují stejný čas, PHP řekne „ano, to se rovná“. A co
víc – funguje to i křížem mezi <code>DateTime</code> a
<code>DateTimeImmutable</code>!</p>

<p>A třešnička na dortu? Closures. I anonymní funkce jsou objekty. Kdy
jsou dvě closures rovny? Podívejte se do <a
href="https://phpfashion.com/attachments/php-comparison/8.x/#closures">tabulky</a>!</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/velky-prehled-porovnavani-v-php#comments</comments>
		<pubDate>Fri, 26 Dec 2025 20:37:00 +0100</pubDate>
		<guid isPermaLink="false">item2438@http://phpfashion.com</guid>
		<enclosure url="https://phpfashion.com/media/fb22c7f238.webp" length="84458" type="image/webp" />
	</item>
	<item>
		<title>100 minut je méně než 50? Paradoxy PHP při změně času</title>
		<link>https://phpfashion.com/cs/100-minut-je-mene-nez-50-paradoxy-php-pri-zmene-casu</link>
		<description>„Kdy se sejdeme?“ – „Zítra ve tři.“ „Kdy je ta
schůzka?“ – „Příští měsíc.“ Pro běžný život jsou takové
údaje o čase zcela postačující. Jenže zkuste totéž v programování a
rychle zjistíte, že jste vstoupili do bludiště plného nástrah a
neočekávaných překvapení.

Čas v programování je jako šelma, která vypadá krotce, dokud na ni
nešlápnete. A jednou z nejmocnějších lstí této šelmy je letní čas a
jeho zákeřné přechody. Systém, který měl údajně ušetřit svíčky,
dnes způsobuje programátorům bezesné noci (pravděpodobně kolem 2:30 ráno,
kdy najednou zjistí, že jejich servery dělají podivné věci).

Vydejme se na průzkum temných zákoutí přechodů na letní čas a zpět,
jak je PHP (ne)zvládá a jak jsem se pokusil napravit toto šílenství v Nette Utils. Připravte se na momenty,
kdy 1 + 1 ≠ 2 a kdy přidání delšího času vám paradoxně vrátí
dřívější hodinu. Tohle by nevymyslel ani Einstein.



Nejprve si prosvištíme
některá slovíčka

Než se ponoříme do problematiky, vysvětleme si několik
klíčových pojmů:


	UTC (Coordinated Universal Time) – koordinovaný světový čas,
	základní časový standard, od kterého se odvozují všechny ostatní
	časové zóny. Je to v podstatě „nulový bod“ pro měření času na
	celém světě.

	Časový posun (offset) – kolik hodin je potřeba přičíst nebo
	odečíst od UTC, abychom dostali místní čas. Označuje se jako UTC+X
	nebo UTC-X.

	CET (Central European Time) – středoevropský čas, který
	používáme v zimě. Má posun UTC+1, což znamená, že když je v UTC
	poledne, u nás je 13:00.

	CEST (Central European Summer Time) – středoevropský letní
	čas, který používáme v létě. Má posun UTC+2, takže když je v UTC
	poledne, u nás je 14:00.

	ČEST – komunistický pozdrav, něco, co patří doufám už pouze
	do starý časů

	Letní čas – systém, kdy v určité části roku (obvykle
	v létě) posuneme hodiny o hodinu dopředu, abychom lépe využili denní
	světlo.


Ten okamžik trval celý
světelný rok

Pojďme si sekundu po sekundě rozebrat, jak probíhá přechod na letní
čas a zpátky. Jako příklad si vezměme nedávnou změnu času v České
republice v neděli 30. března 2025:


	V 01:59:58 středoevropského času (CET) je vše normální.

	V 01:59:59 CET je stále vše normální.

	V další sekundě NEnastane 02:00:00 CET. Místo toho se hodiny
	magickým skokem posunou vpřed.

	Následuje 03:00:00 středoevropského letního času (CEST).


Celá hodina mezi 02:00:00 a 02:59:59 v tento den lokálně
„neexistuje“. Pokud jste měli mít ve 2:30 ráno důležitý telefonát,
máte smůlu.

Podobně, při přechodu zpět na standardní čas (někdy označovaný jako
„zimní“) na podzim (např. 26. října 2025), nastane opačná
situace:


	V 02:59:59 letního času (CEST) je vše normální.

	V další sekundě nenastane 03:00:00 CEST. Hodiny se
	vrátí zpět.

	Následuje 02:00:00 standardního středoevropského
	času (CET).


V tomto případě hodina mezi 02:00:00 a 02:59:59 nastane dvakrát.
Poprvé v letním čase (CEST) a podruhé ve standardním čase (CET). Jak
rozlišíme, kterou 2:30 myslíme? Právě pomocí označení času (CET/CEST),
posunu od UTC (+01:00 / +02:00) nebo prostě slovem „letního“ /
„zimního“ času.

Časové zóny: Co
vlastně označuje Europe/Prague?

Když v PHP (nebo jinde) použijeme identifikátor časové zóny jako
Europe/Prague, není to jen informace o aktuálním posunu od UTC.
Je to odkaz na záznam v IANA Time Zone Database, která obsahuje
komplexní historii a budoucí pravidla pro danou geografickou oblast:


	Standardní posun od UTC (kolik hodin se přičítá nebo odečítá
	od UTC).

	Pravidla pro letní čas (kdy začíná, kdy končí).

	Historické změny v posunech nebo pravidlech letního času (ty se mohou
	měnit rozhodnutím vlád).


Existují stovky takových zón (America/New_York,
Asia/Tokyo, Australia/Sydney). Některé oblasti
letní čas vůbec nepoužívají (např. většina Afriky a Asie, nebo oblasti
kolem rovníku) a mají po celý rok stejný posun od UTC (např.
Etc/UTC nebo Africa/Nairobi).

Absolutní čas: UTC a Timestamp

Abychom se vyhnuli zmatkům s lokálními časy a letním časem, existují
absolutní časové reference:


	UTC: Jak jsme si již řekli, je to základní časový standard,
	který nemá letní čas. Všechny lokální časy jsou definovány jako posun
	od UTC. UTC je v podstatě „čistý“ čas, ke kterému si pak každá
	časová zóna přidá svůj posun.

	Unix Timestamp: Počet sekund, které uplynuly od začátku Unixové
	éry (1. ledna 1970, 00:00:00 UTC), nepočítaje přestupné sekundy. Timestamp
	je také absolutní a nezávislý na časové zóně nebo letním čase.


Právě převod mezi absolutním časem (UTC/timestamp) a lokálním časem
v konkrétní zóně je místo, kde vstupují do hry pravidla
letního času.

PHP DateTime: Když se hodiny
přetočí

Když v PHP pracujete s objektem DateTime nebo
DateTimeImmutable, vždy má přiřazenou časovou zónu. Pokud ji
explicitně neuvedete, použije se výchozí zóna nastavená v PHP
(konfigurací nebo pomocí date_default_timezone_set()).

Co se stane, když se pokusíte vytvořit čas, který kvůli letnímu času
neexistuje, nebo čas, který existuje dvakrát?

Neexistující čas (jarní skok):

// Pokus vytvořit čas v &quot;díře&quot; 30. března 2025
$dt = new DateTime(&apos;2025-03-30 02:30:00&apos;, new DateTimeZone(&apos;Europe/Prague&apos;));
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-30 03:30:00 CEST (+02:00)


PHP typicky „normalizuje“ tento neplatný čas tím, že ho posune
vpřed o hodinu na první platný čas po skoku. Takže 02:30 se
stane 03:30.

Nejednoznačný čas (podzimní návrat):

// Pokus vytvořit čas v &quot;překryvu&quot; 26. října 2025
$dt = new DateTime(&apos;2025-10-26 02:30:00&apos;, new DateTimeZone(&apos;Europe/Prague&apos;));
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-10-26 02:30:00 CET (+01:00)


PHP zde standardně zvolí druhý výskyt toho času. Proč druhý a
ne první? Protože PHP považuje standardní čas (CET) za výchozí,
základní stav a letní čas (CEST) pouze za dočasnou úpravu, a proto při
nejednoznačnosti dává přednost standardnímu času.

Relativní časové
výrazy a jejich záludnosti

Teď se dostáváme k opravdu záludné části. PHP umožňuje pracovat
s relativními časovými výrazy – tedy řetězci jako
+30 minutes, -1 hour nebo 1 day 2 hours.
Tyto výrazy můžeme použít dvěma způsoby:


	Přímo v konstruktoru new DateTime(&apos;+50 minutes&apos;)

	V metodě $date-&gt;modify(&apos;+50 minutes&apos;)


Mimochodem, Nette tyto relativní časové výrazy odjakživa „tlačí“,
protože jsou srozumitelné a přehledné. Určitě je znáte například
z konfigurace „expiration“ u session nebo v dalších částech
frameworku.

Intuitivně bychom čekali, že když k času přičteme delší dobu,
výsledný čas bude pozdější. S relativními časovými výrazy to ale
během jarního přechodu nemusí platit! A tento problém se projevuje jak
při použití v konstruktoru DateTime, tak v metodě
modify().

Představte si, že je právě půl druhé ráno, těsně před jarním
skokem. V tu chvíli se ve vaší aplikací může odehrávat něco velmi
bizarního, čeho si většina z nás ovšem nevšimne, protože buď
spokojeně chrupeme v posteli, nebo ještě spokojeněji vykládáme moudra
v hospodě. Jenže v serverovnách po celém světě kód tiše
běží dál…

// pro všechny další příklady nastavíme výchozí časovou zónu
date_default_timezone_set(&apos;Europe/Prague&apos;);

// Je právě 2025-03-30 01:30:00 a vytvoříme DateTime s relativním časem +50 minut
$dt50 = new DateTime(&apos;+50 minutes&apos;);
echo $dt50-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-30 03:20:00 CEST (+02:00)


„No počkat, 1:30 plus 50 minut je přece 2:20. Proč to ukazuje 3:20?“
Jak už jsme si říkali, hodina mezi 2:00 a 3:00 neexistuje. Takže 2:20 je
neplatný čas, který PHP opraví tak, že ho posune o hodinu dál. Tedy na
3:20 v letním čase.

A co když k tomu stejnému výchozímu času přičteme delší
interval – řekněme 100 minut?

$dt100 = new DateTime(&apos;+100 minutes&apos;);
echo $dt100-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-30 03:10:00 CEST (+02:00)


Vidíte to! Ano, čtete správně. Po přidání 100 minut jsme dostali čas
(03:10), který je dřívější než čas po přidání
50 minut (03:20).


	+50 minutes: 01:30 + 50 min = 02:20. Tento
	čas neexistuje, protože je v té „ztracené hodině“. PHP ho normalizuje
	posunem o hodinu vpřed na 03:20 CEST.

	+100 minutes:
	01:30 + 100 min (1h 40m) = 03:10. Tento čas už existuje. PHP ho
	tedy použije tak, jak je.


Metoda modify() a konstruktor s relativními řetězci v PHP
mají tendenci provádět aritmetiku nejprve na úrovni „hodinkového času“
a až potom řešit neplatné časy vzniklé skokem na letní čas.
Výsledkem je naprosto neintuitivní chování, které většina knihoven pro
práci s časem v jiných jazycích nedělá. Ty typicky interpretují
+X minut jako přidání přesné doby trvání (X *
60 sekund) k absolutnímu časovému okamžiku.

DateInterval: Další vrstva
komplikací

Příběh se ještě komplikuje třídou DateInterval. Ta byla
vytvořena speciálně pro práci s časovými intervaly a mohla by nabízet
řešení našeho problému. Jenže ouha…

K vytvoření instance DateInterval musíte použít formát
podle normy ISO 8601. Upřímně, rozuměli byste na první pohled, co znamená
PT100M? Ne? Já taky ne. Je to „Period of Time, 100 Minutes“
(doba trvání 100 minut). Standardizované, ale rozhodně ne na první
pohled jasné.

Přesto, pokud tento podivný zápis překousneme, funguje najednou všechno
správně!

$dt = new DateTime(&apos;2025-03-30 01:30:00&apos;);
$dt-&gt;add(new DateInterval(&apos;PT100M&apos;)); // 100 Minutes - ten báječný ISO 8601 formát
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-30 04:10:00 CEST (+02:00) - hurá, funguje správně!


Skvělé! Tady se to opravdu chová tak, jak bychom čekali – přidá
přesně 100 minut k absolutnímu času. To by mohlo být naše řešení…
ale co ten podivný formát?

PHP vývojáři si byli vědomi, že PT100M není zrovna
uživatelsky přívětivé, a tak přidali metodu
DateInterval::createFromDateString(), která rozumí těm
příjemným textovým výrazům jako 100 minutes:

$dt = new DateTime(&apos;2025-03-30 01:30:00&apos;);
$dt-&gt;add(DateInterval::createFromDateString(&apos;100 minutes&apos;));
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-30 03:10:00 CEST (+02:00) - au, zase špatně!


A jsme zase tam, kde jsme byli! Stejný problém jako s
modify(). Co se to děje?

Ve skutečnosti máme co do činění s jakousi „dvojí tváří“
třídy DateInterval. Záleží na tom, jakým způsobem ji
vytvoříme:


	Když použijeme konstruktor s ISO 8601 formátem
	new DateInterval(&apos;PT100M&apos;), vytvoří se skutečná doba
	trvání, která se přičítá k absolutnímu času.

	Když použijeme createFromDateString(&apos;100 minutes&apos;), vytvoří
	se spíše jakýsi kalendářní interval, který se chová podobně
	jako modify() – nejprve provede „hodinkovou“ aritmetiku a
	pak až řeší problémy s neplatnými časy.


Takže není DateInterval jako DateInterval. Je to
úplně jiná tvář stejně pojmenovaného objektu podle toho, jak ho
vytvoříme.

Jedna možnost řešení: Útěk
do UTC

Jedním ze způsobů, jak se těmto problémům vyhnout, je provádět
veškerou časovou aritmetiku v UTC, kde žádný letní čas neexistuje, a až
finální výsledek převést do požadované lokální zóny:

$dt = new DateTime(&apos;2025-03-30 01:30:00&apos;);
$dt-&gt;setTimezone(new DateTimeZone(&apos;UTC&apos;)); // Převeď do UTC
$dt-&gt;modify(&apos;+100 minutes&apos;);               // Proveď operaci v UTC
$dt-&gt;setTimezone(new DateTimeZone(&apos;Europe/Prague&apos;)); // Převeď zpět
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Správný výstup: 2025-03-30 04:10:00 CEST (+02:00)


Hurá! Nebo ne? Tento trik může být naopak kontraproduktivní, když
přičítáme celé dny nebo jiné kalendářní jednotky. Nejprve si
ověříme, že když přičteme 1 den k času před jarním skokem, dostaneme
očekávaný výsledek:

$dt = new DateTime(&apos;2025-03-30 01:30:00&apos;); // Před skokem (CET +01:00)
$dt-&gt;modify(&apos;+1 day&apos;);
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-31 01:30:00 CEST (+02:00)


Vidíme, že při přičtení jednoho dne zůstává stejná „hodinková“
hodnota (01:30), ale mění se časová zóna z CET na CEST.

Ale co se stane, když použijeme náš UTC trik?

$dt = new DateTime(&apos;2025-03-30 01:30:00&apos;); // Před skokem (CET +01:00)
$dt-&gt;setTimezone(new DateTimeZone(&apos;UTC&apos;)); // Převede na UTC
$dt-&gt;modify(&apos;+1 day&apos;);
$dt-&gt;setTimezone(new DateTimeZone(&apos;Europe/Prague&apos;)); // Převede zpět do lokální zóny
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-31 02:30:00 CEST (+02:00) - o hodinu více!


Ups! Hodina se nám posunula z 1:30 na 2:30. Proč?


	Původní čas (01:30 CET) jsme převedli do UTC (00:30 UTC)

	Přičetli jsme den v UTC (00:30 UTC následující den)

	Ale následující den už platí v Praze letní čas (CEST), který má
	posun +2 hodiny od UTC

	Takže když převedeme zpět 00:30 UTC, dostaneme 02:30 CEST


Tento „útěk do UTC“ tedy může způsobit, že kalendářní operace se
nebudou chovat intuitivně z pohledu lokálního času. Co je tedy vlastně
správné chování? To záleží na vašich potřebách – někdy chcete
zachovat absolutní časový interval (jako 24 hodin), jindy chcete zachovat
kalendářní význam (jako „stejný čas následující den“).

Řešení v Nette Utils

Protože práce s časem, časovými zónami a letním časem je notoricky
složitá, rozhodl jsem se do Nette
Utils přidat opravu problematického chování PHP. Konkrétně do třídy
Nette\Utils\DateTime, a to opravu jak konstruktoru, tak metody
modify(). Jen váhám, zda nejde o BC break – k tomu se
vrátím v závěru článku.

$dt = new Nette\Utils\DateTime(&apos;2025-03-30 01:30:00&apos;);
$dt-&gt;modify(&apos;+100 minutes&apos;);
echo $dt-&gt;format(&apos;Y-m-d H:i:s T (P)&apos;);
// Výstup: 2025-03-30 04:10:00 CEST (+02:00) - SPRÁVNĚ!


S Nette\Utils\DateTime je výsledek pro
+100 minutes vždy pozdější než pro +50 minutes,
i když je půl druhé ráno!

Kdy je 1 + 1 ≠ 2? Když
pracujeme s časem!

Implementace v Nette Utils řeší i složitější případy, kdy
kombinujeme přičítání dnů a hodin. Tady se dostáváme k opravdu
zajímavému problému: existují totiž dva možné výklady relativního
výrazu jako „+1 day +1 hour“. A tyto dvě interpretace dávají při
přechodu na letní čas různé výsledky! Pojďme si to ukázat na
příkladu:

První interpretace:

$dt = new \DateTime(&apos;2025-03-30 01:30:00&apos;); // CET

$dt1 = clone $dt;
$dt1-&gt;modify(&apos;+1 day&apos;); // Nejprve přičtu den: 2025-03-31 01:30:00 CEST
$dt1-&gt;modify(&apos;+1 hour&apos;); // Pak přičtu hodinu: 2025-03-31 02:30:00 CEST


Druhá interpretace:

$dt2 = clone $dt;
$dt2-&gt;modify(&apos;+1 hour&apos;); // Nejprve hodinu: 2025-03-30 03:30:00 CEST
$dt2-&gt;modify(&apos;+1 day&apos;);         // Pak den: 2025-03-31 03:30:00 CEST


Rozdíl je celá hodina! Jak vidíte, pořadí operací zde hraje
zásadní roli.

V Nette\Utils\DateTime jsem zvolil první interpretaci jako
výchozí chování, protože je intuitivnější. Chceme-li přičíst „1 den
a 1 hodinu“, obvykle tím myslíme „stejný čas následující den plus
hodina“. A co je nejlepší? Je jedno, v jakém pořadí jednotky
zapíšete. Ať už použijete +1 day +1 hour nebo
+1 hour +1 day, výsledek bude vždy stejný.

Tato konzistence dělá práci s časovými výrazy mnohem
předvídatelnější a bezpečnější.

Čas je těžký, netrapte se
sami

Práce s časem v PHP může být zrádná, zvláště kolem přechodů na
letní čas. Relativní časové výrazy a dokonce i některé způsoby
použití DateInterval mohou vést k neintuitivním
výsledkům.

Pokud potřebujete spolehlivou manipulaci s časem:


	Používejte Nette\Utils\DateTime, který opravuje
	problematické chování.

	Nebo provádějte časovou aritmetiku v UTC zóně a až pak převádějte
	zpět do lokální zóny.

	Vždy testujte chování vašeho kódu během přechodů na
	letní čas.


Teď jen váhám, jestli oprava chování DateTime v Nette Utils nebude BC
break. Upřímně si nemyslím, že by kdokoliv vědomě spoléhal na zrádné
současné chování při přechodech na letní čas. Tak bych to asi zařadil
do Nette Utils 4.1.

Čas je těžké téma ve všech programovacích jazycích, ne jen v PHP.
Kam se na to hrabe invalidace keše.
</description>
		<content:encoded><![CDATA[
		<p>„Kdy se sejdeme?“ – „Zítra ve tři.“ „Kdy je ta
schůzka?“ – „Příští měsíc.“ Pro běžný život jsou takové
údaje o čase zcela postačující. Jenže zkuste totéž v programování a
rychle zjistíte, že jste vstoupili do bludiště plného nástrah a
neočekávaných překvapení.</p>

<p>Čas v programování je jako šelma, která vypadá krotce, dokud na ni
nešlápnete. A jednou z nejmocnějších lstí této šelmy je letní čas a
jeho zákeřné přechody. Systém, který měl údajně ušetřit svíčky,
dnes způsobuje programátorům bezesné noci (pravděpodobně kolem 2:30 ráno,
kdy najednou zjistí, že jejich servery dělají podivné věci).</p>

<p>Vydejme se na průzkum temných zákoutí přechodů na letní čas a zpět,
jak je PHP (ne)zvládá a jak jsem se pokusil napravit toto šílenství v <a
href="https://doc.nette.org/cs/utils">Nette Utils</a>. Připravte se na momenty,
kdy 1 + 1 ≠ 2 a kdy přidání delšího času vám paradoxně vrátí
dřívější hodinu. Tohle by nevymyslel ani Einstein.</p>

<figure><img loading="lazy" src="/media/7bf0994f8f.webp" alt="" width="1400"
height="933"></figure>

<h2 id="toc-nejprve-si-prosvistime-nektera-slovicka">Nejprve si prosvištíme
některá slovíčka</h2>

<p>Než se ponoříme do problematiky, vysvětleme si několik
klíčových pojmů:</p>

<ul>
	<li><b>UTC</b> (Coordinated Universal Time) – koordinovaný světový čas,
	základní časový standard, od kterého se odvozují všechny ostatní
	časové zóny. Je to v podstatě „nulový bod“ pro měření času na
	celém světě.</li>

	<li><b>Časový posun (offset)</b> – kolik hodin je potřeba přičíst nebo
	odečíst od UTC, abychom dostali místní čas. Označuje se jako UTC+X
	nebo UTC-X.</li>

	<li><b>CET</b> (Central European Time) – středoevropský čas, který
	používáme v zimě. Má posun UTC+1, což znamená, že když je v UTC
	poledne, u nás je 13:00.</li>

	<li><b>CEST</b> (Central European Summer Time) – středoevropský letní
	čas, který používáme v létě. Má posun UTC+2, takže když je v UTC
	poledne, u nás je 14:00.</li>

	<li><b>ČEST</b> – komunistický pozdrav, něco, co patří doufám už pouze
	do starý časů</li>

	<li><b>Letní čas</b> – systém, kdy v určité části roku (obvykle
	v létě) posuneme hodiny o hodinu dopředu, abychom lépe využili denní
	světlo.</li>
</ul>

<h2 id="toc-ten-okamzik-trval-cely-svetelny-rok">Ten okamžik trval celý
světelný rok</h2>

<p>Pojďme si sekundu po sekundě rozebrat, jak probíhá přechod na letní
čas a zpátky. Jako příklad si vezměme nedávnou změnu času v České
republice v neděli 30. března 2025:</p>
<!--more-->
<ul>
	<li>V 01:59:58 středoevropského času (CET) je vše normální.</li>

	<li>V 01:59:59 CET je stále vše normální.</li>

	<li>V další sekundě <b>NEnastane 02:00:00 CET.</b> Místo toho se hodiny
	magickým skokem posunou vpřed.</li>

	<li><b>Následuje 03:00:00 středoevropského letního času</b> (CEST).</li>
</ul>

<p>Celá hodina mezi 02:00:00 a 02:59:59 v tento den lokálně
„neexistuje“. Pokud jste měli mít ve 2:30 ráno důležitý telefonát,
máte smůlu.</p>

<p>Podobně, při přechodu zpět na standardní čas (někdy označovaný jako
„zimní“) na podzim (např. 26. října 2025), nastane opačná
situace:</p>

<ul>
	<li>V 02:59:59 letního času (CEST) je vše normální.</li>

	<li>V další sekundě <b>nenastane 03:00:00 CEST</b>. Hodiny se
	vrátí zpět.</li>

	<li><b>Následuje 02:00:00 standardního středoevropského
	času</b> (CET).</li>
</ul>

<p>V tomto případě hodina mezi 02:00:00 a 02:59:59 nastane <b>dvakrát</b>.
Poprvé v letním čase (CEST) a podruhé ve standardním čase (CET). Jak
rozlišíme, kterou 2:30 myslíme? Právě pomocí označení času (CET/CEST),
posunu od UTC (+01:00 / +02:00) nebo prostě slovem „letního“ /
„zimního“ času.</p>

<h2 id="toc-casove-zony-co-vlastne-oznacuje-europe-prague">Časové zóny: Co
vlastně označuje Europe/Prague?</h2>

<p>Když v PHP (nebo jinde) použijeme identifikátor časové zóny jako
<code>Europe/Prague</code>, není to jen informace o aktuálním posunu od UTC.
Je to odkaz na záznam v <b>IANA Time Zone Database</b>, která obsahuje
komplexní historii a budoucí pravidla pro danou geografickou oblast:</p>

<ul>
	<li>Standardní posun od UTC (kolik hodin se přičítá nebo odečítá
	od UTC).</li>

	<li>Pravidla pro letní čas (kdy začíná, kdy končí).</li>

	<li>Historické změny v posunech nebo pravidlech letního času (ty se mohou
	měnit rozhodnutím vlád).</li>
</ul>

<p>Existují stovky takových zón (<code>America/New_York</code>,
<code>Asia/Tokyo</code>, <code>Australia/Sydney</code>). Některé oblasti
letní čas vůbec nepoužívají (např. většina Afriky a Asie, nebo oblasti
kolem rovníku) a mají po celý rok stejný posun od UTC (např.
<code>Etc/UTC</code> nebo <code>Africa/Nairobi</code>).</p>

<h2 id="toc-absolutni-cas-utc-a-timestamp">Absolutní čas: UTC a Timestamp</h2>

<p>Abychom se vyhnuli zmatkům s lokálními časy a letním časem, existují
absolutní časové reference:</p>

<ul>
	<li><b>UTC:</b> Jak jsme si již řekli, je to základní časový standard,
	který nemá letní čas. Všechny lokální časy jsou definovány jako posun
	od UTC. UTC je v podstatě „čistý“ čas, ke kterému si pak každá
	časová zóna přidá svůj posun.</li>

	<li><b>Unix Timestamp:</b> Počet sekund, které uplynuly od začátku Unixové
	éry (1. ledna 1970, 00:00:00 UTC), nepočítaje přestupné sekundy. Timestamp
	je také absolutní a nezávislý na časové zóně nebo letním čase.</li>
</ul>

<p>Právě převod mezi absolutním časem (UTC/timestamp) a lokálním časem
v konkrétní zóně je místo, kde vstupují do hry pravidla
letního času.</p>

<h2 id="toc-php-datetime-kdyz-se-hodiny-pretoci">PHP DateTime: Když se hodiny
přetočí</h2>

<p>Když v PHP pracujete s objektem <code>DateTime</code> nebo
<code>DateTimeImmutable</code>, vždy má přiřazenou časovou zónu. Pokud ji
explicitně neuvedete, použije se výchozí zóna nastavená v PHP
(konfigurací nebo pomocí <code>date_default_timezone_set()</code>).</p>

<p>Co se stane, když se pokusíte vytvořit čas, který kvůli letnímu času
neexistuje, nebo čas, který existuje dvakrát?</p>

<p><b>Neexistující čas (jarní skok):</b></p>

<pre
class="language-php"><code>// Pokus vytvořit čas v &quot;díře&quot; 30. března 2025
$dt = new DateTime(&#039;2025-03-30 02:30:00&#039;, new DateTimeZone(&#039;Europe/Prague&#039;));
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-30 03:30:00 CEST (+02:00)
</code></pre>

<p>PHP typicky „normalizuje“ tento neplatný čas tím, že ho <b>posune
vpřed</b> o hodinu na první platný čas po skoku. Takže 02:30 se
stane 03:30.</p>

<p><b>Nejednoznačný čas (podzimní návrat):</b></p>

<pre
class="language-php"><code>// Pokus vytvořit čas v &quot;překryvu&quot; 26. října 2025
$dt = new DateTime(&#039;2025-10-26 02:30:00&#039;, new DateTimeZone(&#039;Europe/Prague&#039;));
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-10-26 02:30:00 CET (+01:00)
</code></pre>

<p>PHP zde standardně zvolí <b>druhý výskyt</b> toho času. Proč druhý a
ne první? Protože PHP považuje standardní čas (CET) za výchozí,
základní stav a letní čas (CEST) pouze za dočasnou úpravu, a proto při
nejednoznačnosti dává přednost standardnímu času.</p>

<h2 id="toc-relativni-casove-vyrazy-a-jejich-zaludnosti">Relativní časové
výrazy a jejich záludnosti</h2>

<p>Teď se dostáváme k opravdu záludné části. PHP umožňuje pracovat
s relativními časovými výrazy – tedy řetězci jako
<code>+30 minutes</code>, <code>-1 hour</code> nebo <code>1 day 2 hours</code>.
Tyto výrazy můžeme použít dvěma způsoby:</p>

<ol>
	<li>Přímo v konstruktoru <code>new DateTime('+50 minutes')</code></li>

	<li>V metodě <code>$date-&gt;modify('+50 minutes')</code></li>
</ol>

<p>Mimochodem, Nette tyto relativní časové výrazy odjakživa „tlačí“,
protože jsou srozumitelné a přehledné. Určitě je znáte například
z konfigurace „expiration“ u session nebo v dalších částech
frameworku.</p>

<p>Intuitivně bychom čekali, že když k času přičteme delší dobu,
výsledný čas bude pozdější. S relativními časovými výrazy to ale
během jarního přechodu nemusí platit! A tento problém se projevuje jak
při použití v konstruktoru <code>DateTime</code>, tak v metodě
<code>modify()</code>.</p>

<p>Představte si, že je právě půl druhé ráno, těsně před jarním
skokem. V tu chvíli se ve vaší aplikací může odehrávat něco velmi
bizarního, čeho si většina z nás ovšem nevšimne, protože buď
spokojeně chrupeme v posteli, nebo ještě spokojeněji vykládáme moudra
v hospodě. Jenže v serverovnách po celém světě kód tiše
běží dál…</p>

<pre
class="language-php"><code>// pro všechny další příklady nastavíme výchozí časovou zónu
date_default_timezone_set(&#039;Europe/Prague&#039;);

// Je právě 2025-03-30 01:30:00 a vytvoříme DateTime s relativním časem +50 minut
$dt50 = new DateTime(&#039;+50 minutes&#039;);
echo $dt50-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-30 03:20:00 CEST (+02:00)
</code></pre>

<p>„No počkat, 1:30 plus 50 minut je přece 2:20. Proč to ukazuje 3:20?“
Jak už jsme si říkali, hodina mezi 2:00 a 3:00 neexistuje. Takže 2:20 je
neplatný čas, který PHP opraví tak, že ho posune o hodinu dál. Tedy na
3:20 v letním čase.</p>

<p>A co když k tomu stejnému výchozímu času přičteme delší
interval – řekněme 100 minut?</p>

<pre
class="language-php"><code>$dt100 = new DateTime(&#039;+100 minutes&#039;);
echo $dt100-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-30 03:10:00 CEST (+02:00)
</code></pre>

<p>Vidíte to! Ano, čtete správně. Po přidání 100 minut jsme dostali čas
(<code>03:10</code>), který je <i>dřívější</i> než čas po přidání
50 minut (<code>03:20</code>).</p>

<ul>
	<li><b><code>+50 minutes</code></b>: <code>01:30 + 50 min = 02:20</code>. Tento
	čas neexistuje, protože je v té „ztracené hodině“. PHP ho normalizuje
	posunem o hodinu vpřed na <code>03:20 CEST</code>.</li>

	<li><b><code>+100 minutes</code></b>:
	<code>01:30 + 100 min (1h 40m) = 03:10</code>. Tento čas už existuje. PHP ho
	tedy použije tak, jak je.</li>
</ul>

<p>Metoda <code>modify()</code> a konstruktor s relativními řetězci v PHP
mají tendenci provádět aritmetiku nejprve na úrovni „hodinkového času“
a až <i>potom</i> řešit neplatné časy vzniklé skokem na letní čas.
Výsledkem je naprosto neintuitivní chování, které většina knihoven pro
práci s časem v jiných jazycích nedělá. Ty typicky interpretují
<code>+X minut</code> jako přidání <em>přesné doby trvání</em> (X *
60 sekund) k absolutnímu časovému okamžiku.</p>

<h2 id="toc-dateinterval-dalsi-vrstva-komplikaci">DateInterval: Další vrstva
komplikací</h2>

<p>Příběh se ještě komplikuje třídou <code>DateInterval</code>. Ta byla
vytvořena speciálně pro práci s časovými intervaly a mohla by nabízet
řešení našeho problému. Jenže ouha…</p>

<p>K vytvoření instance <code>DateInterval</code> musíte použít formát
podle normy ISO 8601. Upřímně, rozuměli byste na první pohled, co znamená
<code>PT100M</code>? Ne? Já taky ne. Je to „Period of Time, 100 Minutes“
(doba trvání 100 minut). Standardizované, ale rozhodně ne na první
pohled jasné.</p>

<p>Přesto, pokud tento podivný zápis překousneme, funguje najednou všechno
správně!</p>

<pre
class="language-php"><code>$dt = new DateTime(&#039;2025-03-30 01:30:00&#039;);
$dt-&gt;add(new DateInterval(&#039;PT100M&#039;)); // 100 Minutes - ten báječný ISO 8601 formát
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-30 04:10:00 CEST (+02:00) - hurá, funguje správně!
</code></pre>

<p>Skvělé! Tady se to opravdu chová tak, jak bychom čekali – přidá
přesně 100 minut k absolutnímu času. To by mohlo být naše řešení…
ale co ten podivný formát?</p>

<p>PHP vývojáři si byli vědomi, že <code>PT100M</code> není zrovna
uživatelsky přívětivé, a tak přidali metodu
<code>DateInterval::createFromDateString()</code>, která rozumí těm
příjemným textovým výrazům jako <code>100 minutes</code>:</p>

<pre
class="language-php"><code>$dt = new DateTime(&#039;2025-03-30 01:30:00&#039;);
$dt-&gt;add(DateInterval::createFromDateString(&#039;100 minutes&#039;));
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-30 03:10:00 CEST (+02:00) - au, zase špatně!
</code></pre>

<p>A jsme zase tam, kde jsme byli! Stejný problém jako s
<code>modify()</code>. Co se to děje?</p>

<p>Ve skutečnosti máme co do činění s jakousi „dvojí tváří“
třídy <code>DateInterval</code>. Záleží na tom, jakým způsobem ji
vytvoříme:</p>

<ol>
	<li>Když použijeme konstruktor s ISO 8601 formátem
	<code>new DateInterval('PT100M')</code>, vytvoří se skutečná <em>doba
	trvání</em>, která se přičítá k absolutnímu času.</li>

	<li>Když použijeme <code>createFromDateString('100 minutes')</code>, vytvoří
	se spíše jakýsi <em>kalendářní interval</em>, který se chová podobně
	jako <code>modify()</code> – nejprve provede „hodinkovou“ aritmetiku a
	pak až řeší problémy s neplatnými časy.</li>
</ol>

<p>Takže není <code>DateInterval</code> jako <code>DateInterval</code>. Je to
úplně jiná tvář stejně pojmenovaného objektu podle toho, jak ho
vytvoříme.</p>

<h2 id="toc-jedna-moznost-reseni-utek-do-utc">Jedna možnost řešení: Útěk
do UTC</h2>

<p>Jedním ze způsobů, jak se těmto problémům vyhnout, je provádět
veškerou časovou aritmetiku v UTC, kde žádný letní čas neexistuje, a až
finální výsledek převést do požadované lokální zóny:</p>

<pre
class="language-php"><code>$dt = new DateTime(&#039;2025-03-30 01:30:00&#039;);
$dt-&gt;setTimezone(new DateTimeZone(&#039;UTC&#039;)); // Převeď do UTC
$dt-&gt;modify(&#039;+100 minutes&#039;);               // Proveď operaci v UTC
$dt-&gt;setTimezone(new DateTimeZone(&#039;Europe/Prague&#039;)); // Převeď zpět
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Správný výstup: 2025-03-30 04:10:00 CEST (+02:00)
</code></pre>

<p>Hurá! Nebo ne? Tento trik může být naopak kontraproduktivní, když
přičítáme celé dny nebo jiné kalendářní jednotky. Nejprve si
ověříme, že když přičteme 1 den k času před jarním skokem, dostaneme
očekávaný výsledek:</p>

<pre
class="language-php"><code>$dt = new DateTime(&#039;2025-03-30 01:30:00&#039;); // Před skokem (CET +01:00)
$dt-&gt;modify(&#039;+1 day&#039;);
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-31 01:30:00 CEST (+02:00)
</code></pre>

<p>Vidíme, že při přičtení jednoho dne zůstává stejná „hodinková“
hodnota (01:30), ale mění se časová zóna z CET na CEST.</p>

<p>Ale co se stane, když použijeme náš UTC trik?</p>

<pre
class="language-php"><code>$dt = new DateTime(&#039;2025-03-30 01:30:00&#039;); // Před skokem (CET +01:00)
$dt-&gt;setTimezone(new DateTimeZone(&#039;UTC&#039;)); // Převede na UTC
$dt-&gt;modify(&#039;+1 day&#039;);
$dt-&gt;setTimezone(new DateTimeZone(&#039;Europe/Prague&#039;)); // Převede zpět do lokální zóny
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-31 02:30:00 CEST (+02:00) - o hodinu více!
</code></pre>

<p>Ups! Hodina se nám posunula z 1:30 na 2:30. Proč?</p>

<ol>
	<li>Původní čas (01:30 CET) jsme převedli do UTC (00:30 UTC)</li>

	<li>Přičetli jsme den v UTC (00:30 UTC následující den)</li>

	<li>Ale následující den už platí v Praze letní čas (CEST), který má
	posun +2 hodiny od UTC</li>

	<li>Takže když převedeme zpět 00:30 UTC, dostaneme 02:30 CEST</li>
</ol>

<p>Tento „útěk do UTC“ tedy může způsobit, že kalendářní operace se
nebudou chovat intuitivně z pohledu lokálního času. Co je tedy vlastně
správné chování? To záleží na vašich potřebách – někdy chcete
zachovat absolutní časový interval (jako 24 hodin), jindy chcete zachovat
kalendářní význam (jako „stejný čas následující den“).</p>

<h2 id="toc-reseni-v-nette-utils">Řešení v Nette Utils</h2>

<p>Protože práce s časem, časovými zónami a letním časem je notoricky
složitá, rozhodl jsem se do <a href="https://doc.nette.org/cs/utils">Nette
Utils</a> přidat opravu problematického chování PHP. Konkrétně do třídy
<code>Nette\Utils\DateTime</code>, a to opravu jak konstruktoru, tak metody
<code>modify()</code>. Jen váhám, zda nejde o BC break – k tomu se
vrátím v závěru článku.</p>

<pre
class="language-php"><code>$dt = new Nette\Utils\DateTime(&#039;2025-03-30 01:30:00&#039;);
$dt-&gt;modify(&#039;+100 minutes&#039;);
echo $dt-&gt;format(&#039;Y-m-d H:i:s T (P)&#039;);
// Výstup: 2025-03-30 04:10:00 CEST (+02:00) - SPRÁVNĚ!
</code></pre>

<p>S <code>Nette\Utils\DateTime</code> je výsledek pro
<code>+100 minutes</code> vždy pozdější než pro <code>+50 minutes</code>,
i když je půl druhé ráno!</p>

<h2 id="toc-kdy-je-1-1-2-kdyz-pracujeme-s-casem">Kdy je 1 + 1 ≠ 2? Když
pracujeme s časem!</h2>

<p>Implementace v Nette Utils řeší i složitější případy, kdy
kombinujeme přičítání dnů a hodin. Tady se dostáváme k opravdu
zajímavému problému: existují totiž dva možné výklady relativního
výrazu jako „+1 day +1 hour“. A tyto dvě interpretace dávají při
přechodu na letní čas <b>různé výsledky</b>! Pojďme si to ukázat na
příkladu:</p>

<p>První interpretace:</p>

<pre
class="language-php"><code>$dt = new \DateTime(&#039;2025-03-30 01:30:00&#039;); // CET

$dt1 = clone $dt;
$dt1-&gt;modify(&#039;+1 day&#039;); // Nejprve přičtu den: 2025-03-31 01:30:00 CEST
$dt1-&gt;modify(&#039;+1 hour&#039;); // Pak přičtu hodinu: 2025-03-31 02:30:00 CEST
</code></pre>

<p>Druhá interpretace:</p>

<pre
class="language-php"><code>$dt2 = clone $dt;
$dt2-&gt;modify(&#039;+1 hour&#039;); // Nejprve hodinu: 2025-03-30 03:30:00 CEST
$dt2-&gt;modify(&#039;+1 day&#039;);         // Pak den: 2025-03-31 03:30:00 CEST
</code></pre>

<p>Rozdíl je celá hodina! Jak vidíte, pořadí operací zde hraje
zásadní roli.</p>

<p>V <code>Nette\Utils\DateTime</code> jsem zvolil první interpretaci jako
výchozí chování, protože je intuitivnější. Chceme-li přičíst „1 den
a 1 hodinu“, obvykle tím myslíme „stejný čas následující den plus
hodina“. A co je nejlepší? Je jedno, v jakém pořadí jednotky
zapíšete. Ať už použijete <code>+1 day +1 hour</code> nebo
<code>+1 hour +1 day</code>, výsledek bude vždy stejný.</p>

<p>Tato konzistence dělá práci s časovými výrazy mnohem
předvídatelnější a bezpečnější.</p>

<h2 id="toc-cas-je-tezky-netrapte-se-sami">Čas je těžký, netrapte se
sami</h2>

<p>Práce s časem v PHP může být zrádná, zvláště kolem přechodů na
letní čas. Relativní časové výrazy a dokonce i některé způsoby
použití <code>DateInterval</code> mohou vést k neintuitivním
výsledkům.</p>

<p>Pokud potřebujete spolehlivou manipulaci s časem:</p>

<ol>
	<li>Používejte <code>Nette\Utils\DateTime</code>, který opravuje
	problematické chování.</li>

	<li>Nebo provádějte časovou aritmetiku v UTC zóně a až pak převádějte
	zpět do lokální zóny.</li>

	<li>Vždy testujte chování vašeho kódu během přechodů na
	letní čas.</li>
</ol>

<p>Teď jen váhám, jestli oprava chování DateTime v Nette Utils nebude BC
break. Upřímně si nemyslím, že by kdokoliv vědomě spoléhal na zrádné
současné chování při přechodech na letní čas. Tak bych to asi zařadil
do Nette Utils 4.1.</p>

<p>Čas je těžké téma ve všech programovacích jazycích, ne jen v PHP.
Kam se na to hrabe invalidace keše.</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/100-minut-je-mene-nez-50-paradoxy-php-pri-zmene-casu#comments</comments>
		<pubDate>Fri, 04 Apr 2025 11:01:00 +0200</pubDate>
		<guid isPermaLink="false">item2390@http://phpfashion.com</guid>
		<enclosure url="https://phpfashion.com/media/7bf0994f8f.webp" length="103172" type="image/webp" />
	</item>
	<item>
		<title>Var, Let, Const: Přestaňte si komplikovat život v JavaScriptu</title>
		<link>https://phpfashion.com/cs/var-let-const-v-javascriptu</link>
		<description>JavaScript nabízí tři způsoby, jak deklarovat proměnné:
var, let a const. Pro mnoho
programátorů není úplně jasné, kdy kterou z nich použít, většina
tutoriálů a linterů vás nutí používat je špatně. Pojďme si ukázat,
jak psát čistší a srozumitelnější kód bez zbytečných pravidel, která
nám ve skutečnosti nepomáhají.

Začněme tím
nejnebezpečnějším

JavaScript má jednu zákeřnou vlastnost: pouhým opomenutím deklarace
proměnné můžete nevědomky používat globální proměnnou. Stačí
zapomenout na var, let nebo const:

function calculatePrice(amount) {
	price = amount * 100;    // Opomenutí! Chybí &apos;let&apos;
	return price;            // Používáme globální proměnnou &apos;price&apos;
}

function processOrder() {
	price = 0;               // Používáme tu samou globální proměnnou!
	// ... nějaký kód volající calculatePrice()
	return price;            // Vracíme úplně jinou hodnotu, než čekáme
}


Tohle je noční můra každého vývojáře – kód funguje zdánlivě
správně, dokud nezačne někde jinde v aplikaci něco záhadně selhávat.
Debugování takových chyb může zabrat hodiny, protože globální proměnná
může být přepsána kdekoliv v aplikaci.

Proto je naprosto zásadní vždy deklarovat proměnné pomocí
let nebo const.

Zapomeňte na var

Klíčové slovo var je v JavaScriptu od jeho počátku v roce
1995 a nese s sebou pár problematických vlastností, které byly v době
vzniku jazyka považovány za features, ale časem se ukázaly jako zdroj mnoha
chyb. Po dvaceti letech vývoje jazyka se autoři JavaScriptu rozhodli tyto
problémy řešit – ne opravou var (kvůli zachování zpětné
kompatibility), ale představením nového klíčového slova let
v ES2015.

Na internetu najdete spoustu článků rozebírajících problémy
var do nejmenších detailů. Ale víte co? Není potřeba se
v tom babrat. Berme var prostě jako překonaný archaismus a
pojďme se soustředit na moderní JavaScript.

Kdy použít let

let je moderní způsob deklarace proměnných
v JavaScriptu.

Příjemné je, že proměnná existuje vždy pouze uvnitř bloku kódu (tedy
mezi složenými závorkami), kde byla definována. To dělá kód
předvídatelnější a bezpečnější.

if (someCondition) {
	let temp = calculateSomething();
	// temp je dostupná jen zde
}
// temp už zde neexistuje


V případě cyklů je deklarace přísně vzato umístěna před
složenými závorkami, ale nenechte si tím zmást, proměnná existuje jen
v cyklu:

for (let counter = 0; counter &lt; 10; counter++) {
	// Proměnná counter existuje jen v cyklu
}
// counter už zde nejsou dostupné


Kdy použít const

const slouží k deklarování konstant. Typicky jde
o důležité hodnoty na úrovni modulu nebo aplikace, které se nikdy
nemají měnit:

const PI = 3.14159;
const API_URL = &apos;https://api.example.com&apos;;
const MAX_RETRY_ATTEMPTS = 3;


Je ale důležité pochopit jeden klíčový detail: const pouze
zabraňuje přiřazení nové hodnoty do proměnné – neřeší, co se
děje s hodnotou samotnou. Tento rozdíl se projevuje zejména u objektů a
polí (pole je ostatně také objekt) – const z nich nedělá
immutable objekty, tj. nezabraňuje změnám uvnitř objektu:

const CONFIG = {
	url: &apos;https://api.example.com&apos;,
	timeout: 5000
};

CONFIG.url = &apos;https://api2.example.com&apos;;  // Toto funguje!
CONFIG = { url: &apos;https://api2.example.com&apos; };  // Toto vyhodí TypeError!


Pokud potřebujete skutečně neměnný objekt, musíte jej nejprve
zmrazit.

Dilema let vs
const

Nyní se dostáváme k zajímavější otázce. Zatímco u var
vs let je situace jasná, použití const je
předmětem mnoha diskuzí v komunitě. Většina tutoriálů, style-guides a
linterů prosazuje pravidlo „používej const všude, kde
můžeš“. Takže použití const vídáme zcela běžně
v tělech funkcí nebo metod.

Pojďme si vysvětlit, proč je tato populární „best practice“ ve
skutečnosti anti-pattern, který dělá kód méně čitelný a zbytečně
svazující.

Přístup „pokud se proměnná v kódu nepřepisuje, měla by být
deklarována jako const“ se na první pohled jeví logický.
Proč by jinak bůh stvořil const? Čím víc „konstant“, tím
bezpečnější a předvídatelnější kód, že? A navíc rychlejší,
protože ho kompilátor může lépe optimalizovat.

Jenže celý tento přístup je ve skutečnosti nepochopení toho, k čemu
konstanty slouží. Jde především o komunikaci záměru – opravdu
chceme sdělit ostatním vývojářům, že do této proměnné se už nesmí
nic přiřadit, nebo do ní jen náhodou v současné implementaci nic
nepřiřazujeme?

// Skutečné konstanty - hodnoty, které jsou konstantní ze své podstaty
const PI = 3.14159;
const DAYS_IN_WEEK = 7;
const API_ENDPOINT = &apos;https://api.example.com&apos;;

// vs.

function processOrder(items) {
	// Toto NEJSOU konstanty, jen náhodou je nepřepisujeme
	const total = items.reduce((sum, item) =&gt; sum + item.price, 0);
	const tax = total * 0.21;
	const shipping = calculateShipping(total);
	return { total, tax, shipping };
}


V prvním případě máme hodnoty, které jsou konstantami ze své
podstaty – vyjadřují neměnné vlastnosti našeho systému nebo důležitá
konfigurační data. Když někde v kódu vidíme PI nebo
API_ENDPOINT, okamžitě chápeme, proč jsou tyto hodnoty
konstanty.

V druhém případě používáme const jen proto, že zrovna
teď náhodou hodnoty nepřepisujeme. Ale není to jejich podstatná
vlastnost – jsou to běžné proměnné, které bychom v příští
verzi funkce klidně mohli chtít změnit. A když to budeme chtít udělat,
const nám v tom bude zbytečně bránit.

V dobách, kdy byl JavaScript jeden velký globální kód, mělo smysl
snažit se zabezpečit proměnné proti přepsání. Ale dnes píšeme kód
v modulech a třídách. Dnes je běžné a správné, že scope je malá
funkce a v jejím rámci vůbec nemá smysl rozdíl mezi let a
const řešit.

Protože to vytváří naprosto zbytečnou kognitivní zátěž:


	Programátor musí při psaní přemýšlet: „Budu tuhle hodnotu měnit?
	Ne? Tak musím dát const…“

	Čtenáře to ruší! Vidí v kódu const a ptá se: „Proč
	je tohle konstanta? Je to nějaká důležitá hodnota? Má to nějaký
	význam?“

	Za měsíc potřebujeme hodnotu změnit a musíme řešit: „Můžu změnit
	const na let? Nespoléhá na to někdo?“


Používejte jednoduše let a tyto otázky nemusíte vůbec
neřešit.

Ještě horší je, když toto rozhodnutí dělá automaticky linter. Tedy
když linter „opraví“ proměnné na const, protože vidí jen jedno
přiřazení. Čtenář kódu pak zbytečně přemýšlí: „Proč tady musí
být tyto proměnné konstanty? Je to nějak důležité?“ A přitom to není
důležité – je to jen shoda okolností. Nepoužívejte v ESLint pravidlo
prefer-const!

Mimochodem, argument o optimalizaci je mýtus. Moderní JavaScript engine
(jako V8) dokáže snadno detekovat, zda je proměnná přepisována nebo ne,
bez ohledu na to, jestli byla deklarována pomocí let nebo
const. Takže používání const nepřináší
žádný výkonnostní benefit.

Implicitní konstanty

V JavaScriptu existuje několik konstrukcí, které implicitně vytvářejí
konstanty, aniž bychom museli použít klíčové slovo const:

// importované moduly
import { React } from &apos;react&apos;;
React = something; // TypeError: Assignment to constant variable

// funkce
function add(a, b) { return a + b; }
add = something; // TypeError: Assignment to constant variable

// třídy
class User {}
User = something; // TypeError: Assignment to constant variable


Je to logické – tyto konstrukce definují základní stavební bloky
našeho kódu a jejich přepsání by mohlo způsobit chaos v aplikaci. Proto
je JavaScript automaticky chrání proti přepsání, stejně jako kdyby byly
deklarovány pomocí const.

Konstanty ve třídách

Třídy byly do JavaScriptu přidány relativně nedávno (v ES2015) a
jejich funkcionalita teprve postupně dospívá. Například privátní členy
označené pomocí # přišly až v roce 2022. Na podporu
konstant ve třídách JavaScript stále čeká. Prozatím můžete používat
static, který ale není zdaleka to samé – označuje hodnotu
sdílenou mezi všemi instancemi třídy, nikoliv však neměnnou.

Závěr


	var nepoužívejte – je to přežitek

	const používejte pro skutečné konstanty na
	úrovni modulu

	Ve funkcích a metodách používejte let – je to
	čitelnější a jasnější

	Nenechte linter automaticky měnit let na
	const – není to o počtu přiřazení, ale o záměru

</description>
		<content:encoded><![CDATA[
		<p>JavaScript nabízí tři způsoby, jak deklarovat proměnné:
<code>var</code>, <code>let</code> a <code>const</code>. Pro mnoho
programátorů není úplně jasné, kdy kterou z nich použít, většina
tutoriálů a linterů vás nutí používat je špatně. Pojďme si ukázat,
jak psát čistší a srozumitelnější kód bez zbytečných pravidel, která
nám ve skutečnosti nepomáhají.</p>

<h2 id="toc-zacneme-tim-nejnebezpecnejsim">Začněme tím
nejnebezpečnějším</h2>

<p>JavaScript má jednu zákeřnou vlastnost: pouhým opomenutím deklarace
proměnné můžete nevědomky používat globální proměnnou. Stačí
zapomenout na <code>var</code>, <code>let</code> nebo <code>const</code>:</p>

<pre
class="language-javascript"><code>function calculatePrice(amount) {
	price = amount * 100;    // Opomenutí! Chybí &#039;let&#039;
	return price;            // Používáme globální proměnnou &#039;price&#039;
}

function processOrder() {
	price = 0;               // Používáme tu samou globální proměnnou!
	// ... nějaký kód volající calculatePrice()
	return price;            // Vracíme úplně jinou hodnotu, než čekáme
}
</code></pre>

<p>Tohle je noční můra každého vývojáře – kód funguje zdánlivě
správně, dokud nezačne někde jinde v aplikaci něco záhadně selhávat.
Debugování takových chyb může zabrat hodiny, protože globální proměnná
může být přepsána kdekoliv v aplikaci.</p>

<p>Proto je naprosto zásadní <b>vždy</b> deklarovat proměnné pomocí
<code>let</code> nebo <code>const</code>.</p>

<h2 id="toc-zapomente-na-var">Zapomeňte na <code>var</code></h2>

<p>Klíčové slovo <code>var</code> je v JavaScriptu od jeho počátku v roce
1995 a nese s sebou pár problematických vlastností, které byly v době
vzniku jazyka považovány za features, ale časem se ukázaly jako zdroj mnoha
chyb. Po dvaceti letech vývoje jazyka se autoři JavaScriptu rozhodli tyto
problémy řešit – ne opravou <code>var</code> (kvůli zachování zpětné
kompatibility), ale představením nového klíčového slova <code>let</code>
v ES2015.</p>

<p>Na internetu najdete spoustu článků rozebírajících problémy
<code>var</code> do nejmenších detailů. Ale víte co? Není potřeba se
v tom babrat. Berme <code>var</code> prostě jako překonaný archaismus a
pojďme se soustředit na moderní JavaScript.</p>

<h2 id="toc-kdy-pouzit-let">Kdy použít <code>let</code></h2>

<p><code>let</code> je moderní způsob deklarace proměnných
v JavaScriptu.</p>

<p>Příjemné je, že proměnná existuje vždy pouze uvnitř bloku kódu (tedy
mezi složenými závorkami), kde byla definována. To dělá kód
předvídatelnější a bezpečnější.</p>

<pre
class="language-javascript"><code>if (someCondition) {
	let temp = calculateSomething();
	// temp je dostupná jen zde
}
// temp už zde neexistuje
</code></pre>

<p>V případě cyklů je deklarace přísně vzato umístěna před
složenými závorkami, ale nenechte si tím zmást, proměnná existuje jen
v cyklu:</p>

<pre
class="language-js"><code>for (let counter = 0; counter &lt; 10; counter++) {
	// Proměnná counter existuje jen v cyklu
}
// counter už zde nejsou dostupné
</code></pre>

<h2 id="toc-kdy-pouzit-const">Kdy použít <code>const</code></h2>

<p><code>const</code> slouží k deklarování konstant. Typicky jde
o důležité hodnoty na úrovni modulu nebo aplikace, které se nikdy
nemají měnit:</p>

<pre
class="language-js"><code>const PI = 3.14159;
const API_URL = &#039;https://api.example.com&#039;;
const MAX_RETRY_ATTEMPTS = 3;
</code></pre>

<p>Je ale důležité pochopit jeden klíčový detail: <b>const pouze
zabraňuje přiřazení nové hodnoty do proměnné</b> – neřeší, co se
děje s hodnotou samotnou. Tento rozdíl se projevuje zejména u objektů a
polí (pole je ostatně také objekt) – const z nich <b>nedělá</b>
immutable objekty, tj. nezabraňuje změnám uvnitř objektu:</p>

<pre
class="language-js"><code>const CONFIG = {
	url: &#039;https://api.example.com&#039;,
	timeout: 5000
};

CONFIG.url = &#039;https://api2.example.com&#039;;  // Toto funguje!
CONFIG = { url: &#039;https://api2.example.com&#039; };  // Toto vyhodí TypeError!
</code></pre>

<p>Pokud potřebujete skutečně neměnný objekt, musíte jej <a
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze">nejprve
zmrazit</a>.</p>

<h2 id="toc-dilema-let-vs-const">Dilema <code>let</code> vs
<code>const</code></h2>

<p>Nyní se dostáváme k zajímavější otázce. Zatímco u <code>var</code>
vs <code>let</code> je situace jasná, použití <code>const</code> je
předmětem mnoha diskuzí v komunitě. Většina tutoriálů, style-guides a
linterů prosazuje pravidlo „používej <code>const</code> všude, kde
můžeš“. Takže použití <code>const</code> vídáme zcela běžně
v tělech funkcí nebo metod.</p>

<p>Pojďme si vysvětlit, proč je tato populární „best practice“ ve
skutečnosti anti-pattern, který dělá kód méně čitelný a zbytečně
svazující.</p>

<p>Přístup „pokud se proměnná v kódu nepřepisuje, měla by být
deklarována jako <code>const</code>“ se na první pohled jeví logický.
Proč by jinak bůh stvořil <code>const</code>? Čím víc „konstant“, tím
bezpečnější a předvídatelnější kód, že? A navíc rychlejší,
protože ho kompilátor může lépe optimalizovat.</p>

<p>Jenže celý tento přístup je ve skutečnosti nepochopení toho, k čemu
konstanty slouží. Jde především o <b>komunikaci záměru</b> – opravdu
chceme sdělit ostatním vývojářům, že do této proměnné se už nesmí
nic přiřadit, nebo do ní jen náhodou v současné implementaci nic
nepřiřazujeme?</p>

<pre
class="language-js"><code>// Skutečné konstanty - hodnoty, které jsou konstantní ze své podstaty
const PI = 3.14159;
const DAYS_IN_WEEK = 7;
const API_ENDPOINT = &#039;https://api.example.com&#039;;

// vs.

function processOrder(items) {
	// Toto NEJSOU konstanty, jen náhodou je nepřepisujeme
	const total = items.reduce((sum, item) =&gt; sum + item.price, 0);
	const tax = total * 0.21;
	const shipping = calculateShipping(total);
	return { total, tax, shipping };
}
</code></pre>

<p>V prvním případě máme hodnoty, které jsou konstantami ze své
podstaty – vyjadřují neměnné vlastnosti našeho systému nebo důležitá
konfigurační data. Když někde v kódu vidíme <code>PI</code> nebo
<code>API_ENDPOINT</code>, okamžitě chápeme, proč jsou tyto hodnoty
konstanty.</p>

<p>V druhém případě používáme <code>const</code> jen proto, že zrovna
teď náhodou hodnoty nepřepisujeme. Ale <b>není to jejich podstatná
vlastnost</b> – jsou to běžné proměnné, které bychom v příští
verzi funkce klidně mohli chtít změnit. A když to budeme chtít udělat,
<code>const</code> nám v tom bude zbytečně bránit.</p>

<p>V dobách, kdy byl JavaScript jeden velký globální kód, mělo smysl
snažit se zabezpečit proměnné proti přepsání. Ale dnes píšeme kód
v modulech a třídách. Dnes je běžné a správné, že scope je malá
funkce a v jejím rámci vůbec nemá smysl rozdíl mezi <code>let</code> a
<code>const</code> řešit.</p>

<p>Protože to vytváří naprosto zbytečnou kognitivní zátěž:</p>

<ol>
	<li>Programátor musí při psaní přemýšlet: „Budu tuhle hodnotu měnit?
	Ne? Tak musím dát const…“</li>

	<li>Čtenáře to ruší! Vidí v kódu <code>const</code> a ptá se: „Proč
	je tohle konstanta? Je to nějaká důležitá hodnota? Má to nějaký
	význam?“</li>

	<li>Za měsíc potřebujeme hodnotu změnit a musíme řešit: „Můžu změnit
	const na let? Nespoléhá na to někdo?“</li>
</ol>

<p><b>Používejte jednoduše <code>let</code> a tyto otázky nemusíte vůbec
neřešit.</b></p>

<p>Ještě horší je, když toto rozhodnutí dělá automaticky linter. Tedy
když linter „opraví“ proměnné na const, protože vidí jen jedno
přiřazení. Čtenář kódu pak zbytečně přemýšlí: „Proč tady musí
být tyto proměnné konstanty? Je to nějak důležité?“ A přitom to není
důležité – je to jen shoda okolností. Nepoužívejte v ESLint pravidlo
<a
href="https://eslint.org/docs/latest/rules/prefer-const"><code>prefer-const</code></a>!</p>

<p>Mimochodem, argument o optimalizaci je mýtus. Moderní JavaScript engine
(jako V8) dokáže snadno detekovat, zda je proměnná přepisována nebo ne,
bez ohledu na to, jestli byla deklarována pomocí <code>let</code> nebo
<code>const</code>. Takže používání <code>const</code> nepřináší
žádný výkonnostní benefit.</p>

<h2 id="toc-implicitni-konstanty">Implicitní konstanty</h2>

<p>V JavaScriptu existuje několik konstrukcí, které implicitně vytvářejí
konstanty, aniž bychom museli použít klíčové slovo <code>const</code>:</p>

<pre
class="language-js"><code>// importované moduly
import { React } from &#039;react&#039;;
React = something; // TypeError: Assignment to constant variable

// funkce
function add(a, b) { return a + b; }
add = something; // TypeError: Assignment to constant variable

// třídy
class User {}
User = something; // TypeError: Assignment to constant variable
</code></pre>

<p>Je to logické – tyto konstrukce definují základní stavební bloky
našeho kódu a jejich přepsání by mohlo způsobit chaos v aplikaci. Proto
je JavaScript automaticky chrání proti přepsání, stejně jako kdyby byly
deklarovány pomocí <code>const</code>.</p>

<h2 id="toc-konstanty-ve-tridach">Konstanty ve třídách</h2>

<p>Třídy byly do JavaScriptu přidány relativně nedávno (v ES2015) a
jejich funkcionalita teprve postupně dospívá. Například privátní členy
označené pomocí <code>#</code> přišly až v roce 2022. Na podporu
konstant ve třídách JavaScript stále čeká. Prozatím můžete používat
<code>static</code>, který ale není zdaleka to samé – označuje hodnotu
sdílenou mezi všemi instancemi třídy, nikoliv však neměnnou.</p>

<h2 id="toc-zaver">Závěr</h2>

<ol>
	<li><code>var</code> nepoužívejte – je to přežitek</li>

	<li><code>const</code> používejte pro skutečné konstanty na
	úrovni modulu</li>

	<li>Ve funkcích a metodách používejte <code>let</code> – je to
	čitelnější a jasnější</li>

	<li>Nenechte linter automaticky měnit <code>let</code> na
	<code>const</code> – není to o počtu přiřazení, ale o záměru</li>
</ol>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/var-let-const-v-javascriptu#comments</comments>
		<pubDate>Thu, 06 Feb 2025 23:54:51 +0100</pubDate>
		<guid isPermaLink="false">item2360@http://phpfashion.com</guid>
	</item>
	<item>
		<title>Jak vyřešit chaos s prázdnými řetězci a NULL hodnotami v MySQL?</title>
		<link>https://phpfashion.com/cs/jak-vyresit-chaos-s-prazdnymi-retezci-a-null-hodnotami-v-mysql</link>
		<description>Znáte to – vytvoříte dotaz WHERE street = &apos;&apos;, ale systém
nevrátí všechny záznamy, které byste čekali. Nebo vám nefunguje LEFT JOIN
tak, jak má. Důvodem je častý problém v databázích: nekonzistentní
používání prázdných řetězců a NULL hodnot. Pojďme si ukázat, jak
tento chaos vyřešit jednou provždy.

Kdy použít NULL a kdy
prázdný řetězec?

Teoreticky je rozdíl jasný: NULL znamená „hodnota není zadaná“,
zatímco prázdný řetězec znamená „hodnota je zadaná a je prázdná“.
Podívejme se na reálný příklad z e-shopu, kde máme tabulku objednávek.
Každá objednávka má povinnou dodací adresu a volitelnou fakturační adresu
pro případ, že zákazník chce fakturovat na jiné místo (typické
zatržítko „Fakturovat na jinou adresu“):

CREATE TABLE orders (
    id INT PRIMARY KEY,
    delivery_street VARCHAR(255) NOT NULL,
    delivery_city VARCHAR(255) NOT NULL,
    billing_street VARCHAR(255) NULL,
    billing_city VARCHAR(255) NULL
);


Pole billing_city a billing_street jsou nullable,
protože fakturační adresa nemusí být vyplněná. Ale je mezi nimi rozdíl.
Zatímco ulice může být legitimně prázdná (obce bez ulic), nebo nezadaná
(použije se dodací adresa), město musí být vždy vyplněné, pokud je
fakturační adresa použita. Buď tedy billing_city obsahuje
název města, nebo je NULL – v tomto případě se použije dodací
adresa.

Realita velkých databází

V praxi ale často dochází k tomu, že se v databázi začnou míchat
oba přístupy. Příčin může být několik:


	Změny v aplikační logice v průběhu času (např. přechod z jednoho
	ORM na jiné)

	Různé týmy nebo programátoři používající různé konvence

	Buggy migrace dat při slučování databází

	Legacy kód, který se chová jinak než nový

	Chyby v aplikaci, které občas propustí prázdný řetězec místo NULL
	nebo naopak


Tohle vede k situacím, kdy máme v databázi mix hodnot a musíme psát
složité podmínky:

SELECT * FROM tbl
WHERE foo = &apos;&apos; OR foo IS NULL;


Daleko horší je, že NULL se chová neintuitivně při porovnání:

SELECT * FROM tbl WHERE foo = &apos;&apos;; -- nezahrne NULL
SELECT * FROM tbl WHERE foo &lt;&gt; &apos;&apos;; -- taky nezahrne NULL

-- musíme použít
SELECT * FROM tbl WHERE foo IS NULL;
SELECT * FROM tbl WHERE foo &lt;=&gt; NULL;


Tato nekonzistence v chování porovnávacích operátorů je další
důvod, proč je výhodnější používat v databázi jen jeden způsob
reprezentace prázdné hodnoty.

Proč se vyhnout dvojímu
přístupu

Podobná situace jako v MySQL existuje i v JavaScriptu, kde máme
null a undefined. Po letech zkušeností mnoho
JavaScript vývojářů dospělo k závěru, že rozlišování mezi těmito
dvěma stavy přináší víc problémů než užitku a raději se rozhodli
používat pouze systémově nativní undefined.

V databázovém světě je situace podobná. Místo toho, abychom stále
řešili, jestli něco je prázdný řetězec nebo NULL, je často jednodušší
zvolit jeden přístup a toho se držet. Například databáze Oracle prázdné
řetězce a NULL hodnoty v podstatě ztotožňuje, čímž tento problém
elegantně obchází. Je to jedno z míst, kde se Oracle odchyluje od SQL
standardu, ale zároveň tím zjednodušuje práci s prázdnými/NULL
hodnotami.

Jak něčeho podobného dosáhnout v MySQL?

Co vlastně chceme vynutit?


	U povinných polí (NOT NULL) chceme vynutit, aby vždy
	obsahovala smysluplnou hodnotu. Tedy zabránit vložení prázdného řetězce
	(nebo řetězce obsahujícího pouze mezery)

	U volitelných polí (NULL) chceme zabránit ukládání
	prázdných řetězců. Když je pole volitelné, měl by být NULL
	jedinou reprezentací „nevyplněné hodnoty“. Míchání obou přístupů
	v jednom sloupci vede k problémům s dotazováním a JOIN operacemi, které
	jsme si ukázali výše.


Řešení v MySQL

V MySQL dávalo historicky smysl naopak používat výhradně prázdné
řetězce (&apos;&apos;) místo NULL hodnot. Byl to totiž jediný přístup, který šlo
vynutit pomocí NOT NULL constraintu. Pokud jsme chtěli
automaticky konzistentní databázi, byla to jediná cesta.

Existuje ale jeden důležitý případ, kdy tento přístup selže –
když potřebujeme nad sloupcem unikátní index. MySQL totiž považuje více
prázdných řetězců za stejné hodnoty, zatímco více NULL hodnot
za různé:

Nicméně od MySQL verze 8.0.16 můžeme použít CHECK constraint a mít
tak větší kontrolu nad tím, jaké hodnoty povolíme. Můžeme například
vynutit, že sloupec bude buď NULL, nebo bude obsahovat neprázdný
řetězec:

CREATE TABLE users (
    id INT PRIMARY KEY,

    -- Povinné pole - musí obsahovat nějaký neprázdný text
    email VARCHAR(255) NOT NULL UNIQUE
        CONSTRAINT email_not_empty      -- název pravidla
        CHECK (email != &apos;&apos;),

    -- Nepovinné pole - buď NULL nebo neprázdný text
    nickname VARCHAR(255)
        CONSTRAINT nickname_not_empty
        CHECK (nickname IS NULL OR nickname != &apos;&apos;)
);


Při vytváření CHECK constraintu je důležité dát mu smysluplný název
pomocí klíčového slova CONSTRAINT. Díky tomu dostaneme v případě
porušení pravidla srozumitelnou chybovou hlášku Check constraint
‚nickname_not_empty‘ is violated místo obecného oznámení
o porušení constraintu. To výrazně usnadňuje debugging a údržbu
aplikace.

Problém jsou nejen prázdné řetězce, ale i řetězce obsahující pouze
mezery. Řešení pomocí CHECK constraintu můžeme vylepšit použitím funkce
TRIM:

CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE
        CONSTRAINT email_not_empty
        CHECK (TRIM(email) != &apos;&apos;),
   ...
);


Nyní neprojdou ani tyto pokusy o obejití validace:

INSERT INTO users (email) VALUES (&apos;   &apos;);  -- samé mezery


Praktické řešení v Nette
Framework

Konzistentní přístup k prázdným hodnotám je potřeba řešit i na
úrovni aplikace. Pokud používáte Nette Framework, můžete využít elegantní
řešení pomocí metody setNullable():

$form = new Form;
$form-&gt;addText(&apos;billing_street&apos;)
    -&gt;setNullable(); // prázdný input se transformuje na NULL


Doporučení pro praxi


	Na začátku projektu se rozhodněte pro jeden přístup:
		
			Buď používejte pouze NULL pro chybějící hodnoty

			Nebo pouze prázdné řetězce pro prázdné/chybějící hodnoty
		
	

	Toto rozhodnutí zdokumentujte v dokumentaci projektu

	Používejte CHECK constrainty pro vynucení konzistence

	U existujících projektů:
		
			Proveďte audit současného stavu

			Připravte migrační skript pro sjednocení přístupu

			Nezapomeňte upravit aplikační logiku
		
	


Tímto přístupem se vyhnete mnoha problémům s porovnáváním,
indexováním a JOIN operacemi, které vznikají při míchání NULL a
prázdných řetězců. Vaše databáze bude konzistentnější a dotazy
jednodušší.
</description>
		<content:encoded><![CDATA[
		<p>Znáte to – vytvoříte dotaz <code>WHERE street = ''</code>, ale systém
nevrátí všechny záznamy, které byste čekali. Nebo vám nefunguje LEFT JOIN
tak, jak má. Důvodem je častý problém v databázích: nekonzistentní
používání prázdných řetězců a NULL hodnot. Pojďme si ukázat, jak
tento chaos vyřešit jednou provždy.</p>

<h2 id="toc-kdy-pouzit-null-a-kdy-prazdny-retezec">Kdy použít NULL a kdy
prázdný řetězec?</h2>

<p>Teoreticky je rozdíl jasný: NULL znamená „hodnota není zadaná“,
zatímco prázdný řetězec znamená „hodnota je zadaná a je prázdná“.
Podívejme se na reálný příklad z e-shopu, kde máme tabulku objednávek.
Každá objednávka má povinnou dodací adresu a volitelnou fakturační adresu
pro případ, že zákazník chce fakturovat na jiné místo (typické
zatržítko „Fakturovat na jinou adresu“):</p>

<pre
class="language-sql"><code>CREATE TABLE orders (
    id INT PRIMARY KEY,
    delivery_street VARCHAR(255) NOT NULL,
    delivery_city VARCHAR(255) NOT NULL,
    billing_street VARCHAR(255) NULL,
    billing_city VARCHAR(255) NULL
);
</code></pre>

<p>Pole <code>billing_city</code> a <code>billing_street</code> jsou nullable,
protože fakturační adresa nemusí být vyplněná. Ale je mezi nimi rozdíl.
Zatímco ulice může být legitimně prázdná (obce bez ulic), nebo nezadaná
(použije se dodací adresa), město musí být vždy vyplněné, pokud je
fakturační adresa použita. Buď tedy <code>billing_city</code> obsahuje
název města, nebo je NULL – v tomto případě se použije dodací
adresa.</p>

<h2 id="toc-realita-velkych-databazi">Realita velkých databází</h2>

<p>V praxi ale často dochází k tomu, že se v databázi začnou míchat
oba přístupy. Příčin může být několik:</p>

<ul>
	<li>Změny v aplikační logice v průběhu času (např. přechod z jednoho
	ORM na jiné)</li>

	<li>Různé týmy nebo programátoři používající různé konvence</li>

	<li>Buggy migrace dat při slučování databází</li>

	<li>Legacy kód, který se chová jinak než nový</li>

	<li>Chyby v aplikaci, které občas propustí prázdný řetězec místo NULL
	nebo naopak</li>
</ul>

<p>Tohle vede k situacím, kdy máme v databázi mix hodnot a musíme psát
složité podmínky:</p>

<pre
class="language-sql"><code>SELECT * FROM tbl
WHERE foo = &#039;&#039; OR foo IS NULL;
</code></pre>

<p>Daleko horší je, že NULL se chová neintuitivně při porovnání:</p>

<pre
class="language-sql"><code>SELECT * FROM tbl WHERE foo = &#039;&#039;; -- nezahrne NULL
SELECT * FROM tbl WHERE foo &lt;&gt; &#039;&#039;; -- taky nezahrne NULL

-- musíme použít
SELECT * FROM tbl WHERE foo IS NULL;
SELECT * FROM tbl WHERE foo &lt;=&gt; NULL;
</code></pre>

<p>Tato nekonzistence v chování porovnávacích operátorů je další
důvod, proč je výhodnější používat v databázi jen jeden způsob
reprezentace prázdné hodnoty.</p>

<h2 id="toc-proc-se-vyhnout-dvojimu-pristupu">Proč se vyhnout dvojímu
přístupu</h2>

<p>Podobná situace jako v MySQL existuje i v JavaScriptu, kde máme
<code>null</code> a <code>undefined</code>. Po letech zkušeností mnoho
JavaScript vývojářů dospělo k závěru, že rozlišování mezi těmito
dvěma stavy přináší víc problémů než užitku a raději se rozhodli
používat pouze systémově nativní <code>undefined</code>.</p>

<p>V databázovém světě je situace podobná. Místo toho, abychom stále
řešili, jestli něco je prázdný řetězec nebo NULL, je často jednodušší
zvolit jeden přístup a toho se držet. Například databáze Oracle prázdné
řetězce a NULL hodnoty v podstatě ztotožňuje, čímž tento problém
elegantně obchází. Je to jedno z míst, kde se Oracle odchyluje od SQL
standardu, ale zároveň tím zjednodušuje práci s prázdnými/NULL
hodnotami.</p>

<p>Jak něčeho podobného dosáhnout v MySQL?</p>

<h2 id="toc-co-vlastne-chceme-vynutit">Co vlastně chceme vynutit?</h2>

<ol>
	<li>U povinných polí (<code>NOT NULL</code>) chceme vynutit, aby vždy
	obsahovala smysluplnou hodnotu. Tedy zabránit vložení prázdného řetězce
	(nebo řetězce obsahujícího pouze mezery)</li>

	<li>U volitelných polí (<code>NULL</code>) chceme zabránit ukládání
	prázdných řetězců. Když je pole volitelné, měl by být <code>NULL</code>
	jedinou reprezentací „nevyplněné hodnoty“. Míchání obou přístupů
	v jednom sloupci vede k problémům s dotazováním a JOIN operacemi, které
	jsme si ukázali výše.</li>
</ol>

<h2 id="toc-reseni-v-mysql">Řešení v MySQL</h2>

<p>V MySQL dávalo historicky smysl naopak používat výhradně prázdné
řetězce ('') místo NULL hodnot. Byl to totiž jediný přístup, který šlo
vynutit pomocí <code>NOT NULL</code> constraintu. Pokud jsme chtěli
automaticky konzistentní databázi, byla to jediná cesta.</p>

<p>Existuje ale jeden důležitý případ, kdy tento přístup selže –
když potřebujeme nad sloupcem unikátní index. MySQL totiž považuje více
prázdných řetězců za stejné hodnoty, zatímco více NULL hodnot
za různé:</p>

<p>Nicméně od MySQL verze 8.0.16 můžeme použít CHECK constraint a mít
tak větší kontrolu nad tím, jaké hodnoty povolíme. Můžeme například
vynutit, že sloupec bude buď NULL, nebo bude obsahovat neprázdný
řetězec:</p>

<pre
class="language-sql"><code>CREATE TABLE users (
    id INT PRIMARY KEY,

    -- Povinné pole - musí obsahovat nějaký neprázdný text
    email VARCHAR(255) NOT NULL UNIQUE
        CONSTRAINT email_not_empty      -- název pravidla
        CHECK (email != &#039;&#039;),

    -- Nepovinné pole - buď NULL nebo neprázdný text
    nickname VARCHAR(255)
        CONSTRAINT nickname_not_empty
        CHECK (nickname IS NULL OR nickname != &#039;&#039;)
);
</code></pre>

<p>Při vytváření CHECK constraintu je důležité dát mu smysluplný název
pomocí klíčového slova CONSTRAINT. Díky tomu dostaneme v případě
porušení pravidla srozumitelnou chybovou hlášku <b>Check constraint
‚nickname_not_empty‘ is violated</b> místo obecného oznámení
o porušení constraintu. To výrazně usnadňuje debugging a údržbu
aplikace.</p>

<p>Problém jsou nejen prázdné řetězce, ale i řetězce obsahující pouze
mezery. Řešení pomocí CHECK constraintu můžeme vylepšit použitím funkce
<code>TRIM</code>:</p>

<pre
class="language-sql"><code>CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE
        CONSTRAINT email_not_empty
        CHECK (TRIM(email) != &#039;&#039;),
   ...
);
</code></pre>

<p>Nyní neprojdou ani tyto pokusy o obejití validace:</p>

<pre
class="language-sql"><code>INSERT INTO users (email) VALUES (&#039;   &#039;);  -- samé mezery
</code></pre>

<h2 id="toc-prakticke-reseni-v-nette-framework">Praktické řešení v Nette
Framework</h2>

<p>Konzistentní přístup k prázdným hodnotám je potřeba řešit i na
úrovni aplikace. Pokud používáte Nette Framework, můžete využít <a
href="https://doc.nette.org/cs/forms/controls#toc-addtext">elegantní
řešení</a> pomocí metody <code>setNullable()</code>:</p>

<pre
class="language-php"><code>$form = new Form;
$form-&gt;addText(&#039;billing_street&#039;)
    -&gt;setNullable(); // prázdný input se transformuje na NULL
</code></pre>

<h2 id="toc-doporuceni-pro-praxi">Doporučení pro praxi</h2>

<ol>
	<li>Na začátku projektu se rozhodněte pro jeden přístup:
		<ul>
			<li>Buď používejte pouze NULL pro chybějící hodnoty</li>

			<li>Nebo pouze prázdné řetězce pro prázdné/chybějící hodnoty</li>
		</ul>
	</li>

	<li>Toto rozhodnutí zdokumentujte v dokumentaci projektu</li>

	<li>Používejte CHECK constrainty pro vynucení konzistence</li>

	<li>U existujících projektů:
		<ul>
			<li>Proveďte audit současného stavu</li>

			<li>Připravte migrační skript pro sjednocení přístupu</li>

			<li>Nezapomeňte upravit aplikační logiku</li>
		</ul>
	</li>
</ol>

<p>Tímto přístupem se vyhnete mnoha problémům s porovnáváním,
indexováním a JOIN operacemi, které vznikají při míchání NULL a
prázdných řetězců. Vaše databáze bude konzistentnější a dotazy
jednodušší.</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/jak-vyresit-chaos-s-prazdnymi-retezci-a-null-hodnotami-v-mysql#comments</comments>
		<pubDate>Thu, 06 Feb 2025 14:10:10 +0100</pubDate>
		<guid isPermaLink="false">item2358@http://phpfashion.com</guid>
	</item>
	<item>
		<title>Přejmenování hodnot v ENUM bez ztráty dat: bezpečný návod</title>
		<link>https://phpfashion.com/cs/prejmenovani-hodnot-v-enum-bez-ztraty-dat-bezpecny-navod</link>
		<description>Přejmenování hodnot v MySQL ENUMu je operace, která může
být zrádná. Mnoho vývojářů se pokouší o přímou změnu, což často
vede ke ztrátě dat nebo chybám. Ukážeme si, jak na to správně a
bezpečně.

Představme si typický scénář: Máte v databázi tabulku objednávek
(orders) se sloupcem status, který je typu ENUM.
Obsahuje hodnoty waiting_payment, processing,
shipped a cancelled. Požadavek je přejmenovat
waiting_payment na unpaid a shipped na
completed. Jak to udělat bez rizika?

Co nefunguje

Nejprve se podívejme na to, co nefunguje. Mnoho vývojářů zkusí
tento přímočarý přístup:

-- TOHLE NEFUNGUJE!
ALTER TABLE orders
MODIFY COLUMN status ENUM(
    &apos;unpaid&apos;,      -- původně &apos;waiting_payment&apos;
    &apos;processing&apos;,  -- beze změny
    &apos;completed&apos;,   -- původně &apos;shipped&apos;
    &apos;cancelled&apos;    -- beze změny
);


Takový přístup je receptem na katastrofu. MySQL se v takovém případě
pokusí mapovat existující hodnoty na nový ENUM, a protože původní hodnoty
už v definici nejsou, nahradí je prázdným řetězcem nebo vrátí chybu
Data truncated for column &apos;status&apos; at row X. V produkční
databázi by to znamenalo ztrátu důležitých dat.

Nejprve zálohujte!

Před jakoukoli změnou struktury databáze je naprosto klíčové vytvořit
zálohu dat. Použijte MySQL-dump
nebo jiný nástroj, kterému důvěřujete.

Správný postup

Správný postup se skládá ze tří kroků:


	Nejprve rozšíříme ENUM o nové hodnoty

	aktualizujeme data

	nakonec odstraníme staré hodnoty.


Pojďme si to ukázat:

1. Prvním krokem je přidání nových hodnot do ENUMu, zatímco ponecháme
ty původní:

ALTER TABLE orders
MODIFY COLUMN status ENUM(
    &apos;waiting_payment&apos;,  -- původní hodnota
    &apos;processing&apos;,       -- zůstává stejná
    &apos;shipped&apos;,         -- původní hodnota
    &apos;cancelled&apos;,       -- zůstává stejná
    &apos;unpaid&apos;,          -- nová hodnota (nahradí waiting_payment)
    &apos;completed&apos;        -- nová hodnota (nahradí shipped)
);


2. Nyní můžeme bezpečně aktualizovat existující data:

UPDATE orders SET status = &apos;unpaid&apos; WHERE status = &apos;waiting_payment&apos;;
UPDATE orders SET status = &apos;completed&apos; WHERE status = &apos;shipped&apos;;


3. A konečně, když jsou všechna data převedena na nové hodnoty,
můžeme odstranit ty staré:

ALTER TABLE orders
MODIFY COLUMN status ENUM(
    &apos;unpaid&apos;,
    &apos;processing&apos;,
    &apos;completed&apos;,
    &apos;cancelled&apos;
);


Proč tento postup funguje?

Je to díky tomu, jak MySQL pracuje s ENUM hodnotami. Když provádíme
ALTER TABLE s modifikací ENUMu, MySQL se snaží mapovat existující hodnoty
podle jejich textové podoby. Pokud původní hodnota v novém ENUMu
neexistuje, dojde v závislosti na nastavení sql_mode buď
k chybě (při zapnutém STRICT_ALL_TABLES) nebo k náhradě
prázdným řetězcem. Proto je klíčové mít v ENUMu vždy současně jak
staré, tak nové hodnoty.

V našem případě to znamená, že během přechodné fáze, kdy máme
v ENUMu hodnoty jako &apos;waiting_payment&apos; i &apos;unpaid&apos;,
každý záznam v databázi najde svůj přesný textový protějšek. Teprve
po UPDATE dotazech, kdy už víme, že všechna data používají nové hodnoty,
můžeme bezpečně odstranit ty staré.
</description>
		<content:encoded><![CDATA[
		<p class="perex">Přejmenování hodnot v MySQL ENUMu je operace, která může
být zrádná. Mnoho vývojářů se pokouší o přímou změnu, což často
vede ke ztrátě dat nebo chybám. Ukážeme si, jak na to správně a
bezpečně.</p>

<p>Představme si typický scénář: Máte v databázi tabulku objednávek
(<code>orders</code>) se sloupcem <code>status</code>, který je typu ENUM.
Obsahuje hodnoty <code>waiting_payment</code>, <code>processing</code>,
<code>shipped</code> a <code>cancelled</code>. Požadavek je přejmenovat
<code>waiting_payment</code> na <code>unpaid</code> a <code>shipped</code> na
<code>completed</code>. Jak to udělat bez rizika?</p>

<h2 id="toc-co-nefunguje">Co nefunguje</h2>

<p>Nejprve se podívejme na to, co <b>nefunguje</b>. Mnoho vývojářů zkusí
tento přímočarý přístup:</p>

<pre
class="language-sql"><code>-- TOHLE NEFUNGUJE!
ALTER TABLE orders
MODIFY COLUMN status ENUM(
    &#039;unpaid&#039;,      -- původně &#039;waiting_payment&#039;
    &#039;processing&#039;,  -- beze změny
    &#039;completed&#039;,   -- původně &#039;shipped&#039;
    &#039;cancelled&#039;    -- beze změny
);
</code></pre>

<p>Takový přístup je receptem na katastrofu. MySQL se v takovém případě
pokusí mapovat existující hodnoty na nový ENUM, a protože původní hodnoty
už v definici nejsou, nahradí je prázdným řetězcem nebo vrátí chybu
<code>Data truncated for column 'status' at row X</code>. V produkční
databázi by to znamenalo ztrátu důležitých dat.</p>

<h2 id="toc-nejprve-zalohujte">Nejprve zálohujte!</h2>

<p>Před jakoukoli změnou struktury databáze je naprosto klíčové vytvořit
zálohu dat. Použijte <a href="https://github.com/dg/MySQL-dump">MySQL-dump</a>
nebo jiný nástroj, kterému důvěřujete.</p>

<h2 id="toc-spravny-postup">Správný postup</h2>

<p>Správný postup se skládá ze tří kroků:</p>

<ol>
	<li>Nejprve rozšíříme ENUM o nové hodnoty</li>

	<li>aktualizujeme data</li>

	<li>nakonec odstraníme staré hodnoty.</li>
</ol>

<p>Pojďme si to ukázat:</p>

<p>1. Prvním krokem je přidání nových hodnot do ENUMu, zatímco ponecháme
ty původní:</p>

<pre
class="language-sql"><code>ALTER TABLE orders
MODIFY COLUMN status ENUM(
    &#039;waiting_payment&#039;,  -- původní hodnota
    &#039;processing&#039;,       -- zůstává stejná
    &#039;shipped&#039;,         -- původní hodnota
    &#039;cancelled&#039;,       -- zůstává stejná
    &#039;unpaid&#039;,          -- nová hodnota (nahradí waiting_payment)
    &#039;completed&#039;        -- nová hodnota (nahradí shipped)
);
</code></pre>

<p>2. Nyní můžeme bezpečně aktualizovat existující data:</p>

<pre
class="language-sql"><code>UPDATE orders SET status = &#039;unpaid&#039; WHERE status = &#039;waiting_payment&#039;;
UPDATE orders SET status = &#039;completed&#039; WHERE status = &#039;shipped&#039;;
</code></pre>

<p>3. A konečně, když jsou všechna data převedena na nové hodnoty,
můžeme odstranit ty staré:</p>

<pre
class="language-sql"><code>ALTER TABLE orders
MODIFY COLUMN status ENUM(
    &#039;unpaid&#039;,
    &#039;processing&#039;,
    &#039;completed&#039;,
    &#039;cancelled&#039;
);
</code></pre>

<h2 id="toc-proc-tento-postup-funguje">Proč tento postup funguje?</h2>

<p>Je to díky tomu, jak MySQL pracuje s ENUM hodnotami. Když provádíme
ALTER TABLE s modifikací ENUMu, MySQL se snaží mapovat existující hodnoty
podle jejich textové podoby. Pokud původní hodnota v novém ENUMu
neexistuje, dojde v závislosti na nastavení <code>sql_mode</code> buď
k chybě (při zapnutém <code>STRICT_ALL_TABLES</code>) nebo k náhradě
prázdným řetězcem. Proto je klíčové mít v ENUMu vždy současně jak
staré, tak nové hodnoty.</p>

<p>V našem případě to znamená, že během přechodné fáze, kdy máme
v ENUMu hodnoty jako <code>'waiting_payment'</code> i <code>'unpaid'</code>,
každý záznam v databázi najde svůj přesný textový protějšek. Teprve
po UPDATE dotazech, kdy už víme, že všechna data používají nové hodnoty,
můžeme bezpečně odstranit ty staré.</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/prejmenovani-hodnot-v-enum-bez-ztraty-dat-bezpecny-navod#comments</comments>
		<pubDate>Mon, 27 Jan 2025 02:16:00 +0100</pubDate>
		<guid isPermaLink="false">item2355@http://phpfashion.com</guid>
	</item>
	<item>
		<title>Property Hooks v PHP 8.4: Revoluce nebo Past?</title>
		<link>https://phpfashion.com/cs/property-hooks-v-php-8-4-revoluce-nebo-past</link>
		<description>Představte si, že by vaše PHP objekty mohly být čistší,
přehlednější a lépe použitelné. Dobrá zpráva – už nemusíte snít!
PHP 8.4 přichází s revoluční novinkou v podobě property hooks a
asymetrické viditelnosti, které kompletně mění pravidla hry
v objektově orientovaném programování. Zapomeňte na neohrabané gettery a
settery – konečně máme k dispozici moderní a intuitivní způsob, jak
kontrolovat přístup k datům objektů. Pojďme se podívat na to, jak tyto
novinky mohou změnit váš kód k nepoznání.

Property hooks představují promyšlený způsob, jak definovat chování
při čtení a zápisu vlastností objektu – a to mnohem čistěji a
výkonněji než dosavadní magické metody __get/__set. Je to jako
byste dostali k dispozici sílu magických metod, ale bez jejich typických
nevýhod.

Podívejme se na jednoduchý příklad z praxe, který vám ukáže, proč
jsou property hooks tak užitečné. Představme si běžnou třídu
Person s veřejnou property age:

class Person
{
	public int $age = 0;
}

$person = new Person;
$person-&gt;age = 25;  // OK
$person-&gt;age = -5;  // OK, ale to je přece nesmysl!


PHP sice díky typu int zajistí, že věk bude celé číslo
(to lze od PHP 7.4), ale co s tím záporným věkem? Dříve bychom museli
sáhnout po getterech a setterech, property by musela být private, museli
bychom doplnit spoustu kódu… S hooks to vyřešíme elegantně:

class Person
{
	public int $age = 0 {
		set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}
}

$person-&gt;age = -5;  // Ups! InvalidArgumentException nás upozorní na nesmysl


Krása tohoto řešení spočívá v jeho jednoduchosti – navenek se
property chová úplně stejně jako dřív, můžeme číst i zapisovat
přímo přes $person-&gt;age. Ale máme plnou kontrolu nad tím,
co se při zápisu děje. A to je teprve začátek!

Můžeme jít ještě dál a vytvořit třeba hook pro čtení. Hookům lze
přidat atributy. A samozřejmě mohou obsahovat složitější logiku než
jednoduchý výraz. Podívejte se na tento příklad práce se jménem:

class Person
{
	public string $first;
	public string $last;
	public string $fullName {
		get {
			return &quot;$this-&gt;first $this-&gt;last&quot;;
		}
		set(string $value) {
			[$this-&gt;first, $this-&gt;last] = explode(&apos; &apos;, $value, 2);
		}
	}
}

$person = new Person;
$person-&gt;fullName = &apos;James Bond&apos;;
echo $person-&gt;first;  // vypíše &apos;James&apos;
echo $person-&gt;last;   // vypíše &apos;Bond&apos;


A něco důležitého: kdykoliv se přistupuje k proměnné (i uvnitř
samotné třídy Person), vždy se využijí hooks. Jediná výjimka je přímý
přístup k reálné proměnné uvnitř kódu samotného hooku.

Ohlédnutí do
minulosti: Co nás naučil SmartObject?

Pro uživatele Nette může být zajímavé ohlédnout se do minulosti.
Framework totiž podobnou funkcionalitu nabízel už před 17 lety ve
formě SmartObject,
který výrazně vylepšoval práci s objekty v době, kdy PHP v této
oblasti značně zaostávalo.

Pamatuju si, že tehdy přišla vlna bezbřehého nadšení, kdy se
properties používaly prakticky všude. Tu pak vystřídala vlna opačná –
nepoužívat je nikde. Důvod? Chybělo jasné vodítko, kdy je lepší použít
metody a kdy property. Ale dnešní nativní řešení je kvalitativně úplně
jinde.Property hooks a asymetrická viditelnost jsou plnohodnotné nástroje,
které nám dávají stejnou úroveň kontroly jako máme u metod. Proto dnes
můžeme mnohem lépe rozlišit, kdy je property skutečně tím správným
řešením.

Backed nebo Virtual? Dobrá
otázka!

Podívejte se na tento kód a zkuste si rychle odpovědět – je to
vlastně jednoduchý kvíz:


	Můžeme do $age zapisovat?

	A co $adult – můžeme číst i zapisovat?


class Person
{
	public int $age = 0 {
		set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}

	public bool $adult {
		get =&gt; $this-&gt;age &gt;= 18;
	}
}


Samozřejmě $age je, jak jsme si řekli už dříve, property
pro čtení i pro zápis. Ale $adult je jen pro čtení!

A tady narážíme na první zapeklitost v designu property hooks. Ze
signatury property vůbec nepoznáme, jestli do ní můžeme zapisovat nebo
ji číst!

Odpověď se totiž skrývá v kódu, v implementaci hooků. Property
totiž mohou být dvojího druhu: backed (se skutečným úložištěm
v paměti) a virtuální (které pouze simulují existenci property). To, zda
je property backed nebo virtuální, rozhoduje, zda se v kódu hooku na ni
odkazujeme.

Property je backed (má vlastní úložiště), když:


	v těle hooku se na ni odkazujeme přes
	$this-&gt;propertyName

	nebo má zkrácený set, který automaticky znamená zápis
	do $this-&gt;propertyName


V našem příkladu tedy:


	Property $age je backed, protože používá zkrácený
	set (což automaticky znamená zápis do
	$this-&gt;age)

	Property $adult je virtuální, protože žádný její
	hook neobsahuje $this-&gt;adult


Je to sice mazané řešení, ale ne zrovna šťastné. Tak zásadní
informaci, jako zda lze property číst nebo do ní zapisovat, má prozradit API
a signatura na první pohled, ne až studium implementace.

Když reference, tak bezpečně!

Reference existují v PHP od jeho počátků. Pomocí znaku
&amp; můžete propojit dvě proměnné tak, aby ukazovaly na
stejné místo v paměti. Je to jako mít dva dálkové ovladače k jedné
televizi – ať zmáčknete kterýkoliv, ovládáte tu samou obrazovku.

Ale co kdyby někdo mohl získat referenci na property s set
hookem? Mohl by její hodnotu měnit přímo a kompletně tak obejít veškerou
validaci. Podívejte se na tento příklad:

class Person
{
	public int $age = 0 {
		set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}
}

$person = new Person;
$ref = &amp;$person-&gt;age;    // Fatal error: Tohle neprojde!
$ref = -5;               // Kdyby to prošlo, validace by byla k ničemu


PHP na to myslelo a elegantně to vyřešilo (tedy pardon, mysleli na to
Ilija Tovilo a Larry Garfield, autoři hooků). Získat referenci na takovou
property prostě není možné (myšleno na backed proměnnou se
set hookem). Je to správné řešení – property hook má
zajistit, že se do property dostane jen platná hodnota, a reference by tuto
kontrolu obcházely.

Když pole potká
property hooks – zajímavá výzva!

Práce s poli v PHP je obvykle příjemně přímočará. Do pole
v property můžeme přidávat prvky různými způsoby:

class Person
{
	public array $phones = [];
}

$person = new Person;
$person-&gt;phones[] = &apos;777 123 456&apos;;          // přidá číslo na konec pole
$person-&gt;phones[&apos;bob&apos;] = &apos;777 123 456&apos;;     // přidá číslo s konkrétním klíčem


A právě tady narážíme na zajímavý problém s property hooks.
Představme si, že chceme vytvořit třídu Person, která bude obsahovat
seznam telefonních čísel, a chceme, aby se u nich automaticky ořezávaly
mezery na začátku a konci:

class Person
{
	public array $phones = [] {
		set =&gt; array_map(&apos;trim&apos;, $value);
	}
}

$person = new Person;
$person-&gt;phones[] = &apos;777 123 456&apos;;  // Překvapení! Error: Indirect modification of Person::$phones is not allowed


Proč to nefunguje? Operace $person-&gt;phones[] totiž v PHP
funguje ve dvou krocích:


	Nejdřív získá referenci na pole pomocí get

	Pak do získaného pole přidá novou hodnotu


Tedy vůbec se nevolá set hook. Ba co víc, jak už víme
z předchozí kapitoly, nelze získat referenci na backed proměnnou se
set hookem (tedy udělat první krok). Proto ta chybová
hláška.

Ani metoda addPhone(), která by volala
$this-&gt;phones[] = $phone, nám nepomůže – všechny
přístupy k property (i uvnitř třídy) totiž procházejí
přes hooky.

Tak jak z toho ven? Pojďme si projít možná řešení. První, které
vás možná napadne:

$phones = $person-&gt;phones;    // načteme pole
$phones[] = &apos; 777 123 456 &apos;;  // přidáme číslo
$person-&gt;phones = $phones;    // uložíme zpět


Funguje to, ale… představte si pole s tisíci čísly. Náš
set hook by musel provést trim() na všech číslech
znovu, i když se přidalo jediné. To není zrovna ukázka efektivity.

Existuje lepší cesta – uvědomit si, že pokud má pole nějak
specificky pracovat se svými prvky (třeba ořezávat mezery), mělo by to být
jeho zodpovědností, ne úkolem třídy, která ho jen drží. Jasně,
pole samo o sobě nenaučíme novým trikům, ale můžeme ho „zabalit“ do
objektu s rozhraním ArrayAccess:

class Phones implements ArrayAccess
{
	private array $data = [];

	public function __construct(array $data = [])
	{
		$this-&gt;data = array_map(&apos;trim&apos;, $data);
	}

	public function offsetSet(mixed $offset, mixed $value): void
	{
		$value = trim($value);
		if ($offset === null) {
			$this-&gt;data[] = $value;
		} else {
			$this-&gt;data[$offset] = $value;
		}
	}

	// implementace dalších metod pro ArrayAccess...
}

class Person
{
	function __construct(
		public Phones $phones = new Phones,
	) {}
}

$person = new Person;
$person-&gt;phones[] = &apos; 777 123 456 &apos;;  // Hurá! Číslo se uloží pěkně ořezané


A teď třešnička na dortu – můžeme využít hook k tomu, aby do
$person-&gt;phones šlo zapsat i obyčejné pole:

class Person
{
	function __construct(
		public Phones $phones = new Phones {
			set(array|Phones $value) =&gt; is_array($value) ? new Phones($value) : $value;
		},
	) {}
}

$person = new Person;
$person-&gt;phones = [&apos;  888 999 000  &apos;, &apos;777 888 999&apos;];  // Magicky se převede na Phones a ořeže řetězce


Jak vidíte, hooks mohou obsahovat i promoted properties.

Ještě se podívejme na alternativní řešení. Vzpomeňte si, že kromě
backed property máme ještě virtuální property – ty, které
nepoužívají $this-&gt;propertyName v těle hooku. A tady se
skýtá druhé řešení:

class Person
{
	private array $_phones = []; // skutečné úložiště čísel

	public array $phones {  // virtuální property pro veřejný přístup
		get =&gt; $this-&gt;_phones;
		set {
			$this-&gt;_phones = array_map(&apos;trim&apos;, $value);
		}
	}

	public function addPhone(string $phone): void
	{
		$this-&gt;_phones[] = trim($phone);
	}
}

$person = new Person;
$person-&gt;addPhone(&apos; 777 123 456 &apos;);  // Přidá ořezané číslo
echo $person-&gt;phones[0];             // Vypíše &quot;777 123 456&quot;
$person-&gt;phones = [&apos;  888 999 000  &apos;]; // Nastaví nové pole s ořezanými čísly


Tady jsme zůstali u klasického pole, ale schovali jsme ho za privátní
proměnnou. Na venek nabízíme virtuální property pro čtení celého pole a
jeho kompletní přepsání, plus specializovanou metodu pro přidávání
jednotlivých čísel.

Hooks a dědičnost:
Když potomci přebírají žezlo

Potomci mohou nejen přidávat hooks k vlastnostem, které je dosud neměly,
ale také předefinovat ty existující. Podívejme se na příklad:

class Person
{
	public string $email;

	public int $age {
		set =&gt; $value &gt;= 0
			? $value
			: throw new InvalidArgumentException(&apos;Věk nemůže být záporný&apos;);
	}
}

class Employee extends Person
{
	// Přidá hook k vlastnosti, která žádný neměla
	public string $email {
		set =&gt; strtolower($value);  // Emaily vždy převedeme na malá písmena
	}

	// Rozšíří existující validaci věku
	public int $age {
		set {
			if ($value &lt;= 130) {  // First check the original condition
				throw new InvalidArgumentException(&apos;130 years? Not buying it!&apos;);
			}
			parent::$age::set($value);
		}
	}
}


Všimněte si té zajímavé syntaxe parent::$age::set($value).
Na první pohled možná vypadá zvláštně, ale dává perfektní smysl –
nejdřív se odkážeme na vlastnost v rodiči a pak na její hook. Je to jako
bychom řekli „hej, zavolej set hook na age vlastnosti mého
rodiče“.

A co víc – můžeme hooks označit jako final, pokud chceme
zabránit jejich přepsání v potomcích. Dokonce můžeme jako
final označit celou property – pak ji potomci nemohou změnit
žádným způsobem (ani přidat hooks, ani rozšířit její viditelnost).

class Person
{
	// Tenhle hook už nikdo nepřepíše
	public int $age {
		final set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}

	// A tuhle property už vůbec nikdo nezmění
	final public string $id;
}


Property v rozhraních

Překvapivou novinkou je podpora property v rozhraních a abstraktních
třídách. Představte si, že vytváříte rozhraní pro entity, které
obsahují řetězec se jménem. Doteď jsme museli psát něco takového:

interface Named
{
	public function getName(): string;
	public function setName(string $name): void;
}


Nuda, že? S property hooks můžeme být mnohem elegantnější!
V rozhraní teď můžeme deklarovat přímo property, a to dokonce
asymetricky – můžeme říct zvlášť, co má být čitelné a co
zapisovatelné:

interface Named
{
	// Říkáme: &quot;Implementující třída musí mít veřejně čitelnou property name&quot;
	public string $name { get; }
}


A teď to zajímavé – jak můžeme takové rozhraní implementovat?
Máme hned několik možností:

class Person implements Named
{
	public string $name;     // Nejjednodušší řešení - obyčejná property
}

class Employee implements Named
{
	public string $name {    // Pokročilejší - složené jméno
		get =&gt; $this-&gt;firstName . &apos; &apos; . $this-&gt;lastName;
	}
	private string $firstName;
	private string $lastName;
}


Všimněte si zajímavého detailu – rozhraní Named
požaduje property pouze pro čtení, ale třída Person nabízí
property čitelnou i zapisovatelnou. A to je naprosto v pořádku –
rozhraní totiž definuje jen minimální požadavky. Je to jako když řeknete
„potřebuju auto, co jede dopředu“ a dostanete auto, co umí i couvat –
splňuje to vaše minimální požadavky a přidává něco navíc.

Pro puntičkáře: V rozhraní musíme u property použít klíčové slovo
public, i když je to vlastně nadbytečné – vše v rozhraní
je ze své podstaty veřejné. U metod uvádět public je
hloupost, ale u property je to vyžadováno kvůli konzistenci syntaxe.

A ještě jedna věc stojí za zmínku – všimli jste si té zvláštní
syntaxe { get; set; }? Zatímco ve třídě můžeme napsat
jednoduše public string $name, v rozhraní musíme explicitně
říct, jaké operace property podporuje. Je to sice trochu pracnější, ale
dává to smysl – u rozhraní chceme být maximálně explicitní v tom, co
požadujeme.

Property
v abstraktních třídách: To nejlepší z obou světů

Abstraktní třídy si vezmou to nejlepší z rozhraní a přidají vlastní
šťávu. Mohou nejen deklarovat property, ale také nabídnout výchozí
implementaci některých hooků:

abstract class Person
{
	// Čistě abstraktní property - implementaci dodá potomek
	abstract public string $name { get; }

	// Protected property s oběma operacemi
	abstract protected int $age { get; set; }

	// Tady už nabízíme hotovou validaci emailu
	abstract public string $email {
		get; // tento hook je abstraktní a potomek ho musí implementovat
		set =&gt; Nette\Utils\Validators::isEmail($value)
			? $value
			: throw new InvalidArgumentException(&apos;Tohle nevypadá jako email...&apos;);
	}
}


A teď
něco opravdu zajímavého – kovarianci a kontravarianci!

Zní to jako zaklínadlo, ale je to vlastně jednoduchá věc.
Podívejte se:

class Animal {}
class Dog extends Animal {}

interface PetShop
{
	// Property jen pro čtení může vracet specifičtější typ
	public Animal $pet { get; }
}

class DogShop implements PetShop
{
	// Vrací psa místo zvířete - to je v pohodě!
	public Dog $pet { get; }
}


Když má property pouze hook get, může v potomkovi vracet
specifičtější typ (tomu se říká kovariance). Představte si to jako:
„Slíbil jsem ti zvíře, a pes je přece taky zvíře, ne?“

Naopak property pouze s hookem set může v potomkovi
přijímat obecnější typ (kontravariance). Je to logické – když umím
pracovat s konkrétním typem, zvládnu i jeho předka.

Jakmile má property oba hooky get i set, musí typ
zůstat stejný. Proč? Protože by to mohlo vést k nekonzistencím –
nemůžeme slíbit, že vrátíme psa, když nám někdo může přes setter
podstrčit kočku!

Asymetrická
viditelnost: Každému, co jeho jest

Představte si, že vytváříte třídu Person a chcete, aby datum narození
mohl číst kdokoliv, ale měnit ho mohla jen samotná třída. Dřív byste
museli sáhnout po getterech a setterech, ale teď? Teď máme elegantní
řešení:

class Person
{
	public private(set) DateTimeImmutable $dateOfBirth;
}


Tenhle zápis říká: „Číst může každý, zapisovat jen třída
sama.“ První modifikátor public určuje viditelnost pro
čtení, druhý private(set) pro zápis. A protože veřejné
čtení je default, můžeme ho vynechat a psát prostě:

class Person
{
	private(set) DateTimeImmutable $dateOfBirth;
}


Samozřejmě platí logické pravidlo – viditelnost pro zápis nemůže
být širší než pro čtení. Nemůžeme použít třeba
protected public(set) – to by bylo jako říct „číst můžou
jen potomci, ale zapisovat může každý“. Trochu podivné, ne?

A co dědičnost? V PHP platí, že potomek může viditelnost buď
zachovat, nebo rozšířit z protected na public. To samé platí i pro
asymetrickou viditelnost:

class Person
{
	public protected(set) string $name;  // Číst může každý, zapisovat jen potomci
}

class Employee extends Person
{
	public public(set) string $name;     // Potomek může rozšířit práva zápisu
}


Zajímavý je případ private(set). Taková property je
automaticky final – když řekneme, že zapisovat může jen
třída sama, logicky to znamená, že ani potomci nemají právo
to měnit.

A nejlepší na tom je, že asymetrickou viditelnost můžeme kombinovat
s hooks:

class Person
{
	private(set) DateTimeImmutable $birthDate {
		set =&gt; $value &gt; new DateTimeImmutable
			? throw new InvalidArgumentException(&apos;Narození v budoucnosti? Sci-fi!&apos;)
			: $value;
	}
}


Tahle property má všechno: je veřejně čitelná, zapisovat do ní může
jen třída sama, a ještě kontroluje, jestli datum není v budoucnosti. Hooks
řeší „co se má stát“, asymetrická viditelnost „kdo to může
udělat“. Perfektní tým!

Asymetrická
viditelnost a pole: Elegantní řešení starého problému

Pamatujete na naše trápení s telefonními čísly? Asymetrická
viditelnost nám nabízí ještě jedno řešení:

class Person
{
	private(set) array $phones = [];

	public function addPhone(string $phone): void
	{
		$this-&gt;phones[] = trim($phone);
	}
}

$person = new Person;
var_dump($person-&gt;phones);     // OK: můžeme číst
$person-&gt;addPhone(&apos;...&apos;);      // OK: můžeme přidat číslo
$person-&gt;phones = [];          // CHYBA: nemůžeme přepsat celé pole


Pole je veřejně čitelné, ale nikdo zvenčí ho nemůže přepsat. Pro
přidání nového čísla máme specializovanou metodu. Žádné složité
objekty simulující pole, žádné virtual properties – jen čistá, jasná
kontrola přístupu.

Pro úplnost dodejme, že u vlastnosti s omezeným zápisem nemůžete
získat referenci zvenku:

$ref = &amp;$person-&gt;phones;    // Fatal error: Takhle ne!


Reference jsou povolené jen ze scope, ze kterého je property
zapisovatelná. Je to logické – reference by mohla obejít naše omezení
pro zápis.

Když to shrneme, pro práci s polem v property máme teď několik
možností:


	Chytrý objekt simulující pole (přináší víc možností, ale
	taky víc kódu)

	Backed property s hookem (znemožňuje přímou
	modifikaci pole)

	Virtual property s privátním úložištěm (vyžaduje metody pro
	úpravy)

	Asymetrická viditelnost (přesouvá logiku do metod)


Který přístup vybrat? Jak už to bývá – záleží na konkrétním
případu. Prostě si zkusit, které API nejlépe sedne do ruky.

Readonly a
asymetrická viditelnost: Konečně svoboda volby!

Modifikátor
readonly jsou ve skutečnosti dva modifikátory v jednom:
zakazuje vícenásobný zápis a zároveň omezuje zápis na private. Vlastně
to není žádný readonly, je to spíš writeonce zkřížený s
private(set).

To druhé mi přišlo vždy zbytečně přísné a nepraktické. Proč by
readonly vlastnost nemohla být zapisovatelná třeba v potomcích?

PHP 8.4 to konečně změnilo. Teď readonly dělá property
defaultně protected(set), tedy zapisovatelnou i v potomcích.
A když potřebujeme jinou viditelnost? Jednoduše si ji nastavíme:

class Person
{
	// Readonly přístupná jen uvnitř třídy (staré chování)
	public private(set) readonly string $name;

	// Readonly přístupná i v potomcích (nové výchozí chování)
	public readonly string $dateOfBirth;

	// Readonly zapisovatelná kdekoliv (ale jen jednou!)
	public public(set) readonly string $id;

	public function rename(string $newName): void
	{
		$this-&gt;name = $newName;    // Uvnitř třídy můžeme měnit
	}
}

class Employee extends Person
{
	public function setBirthDate(DateTimeImmutable $date): void
	{
		$this-&gt;dateOfBirth = $date;  // V potomkovi můžeme měnit
	}
}

$person = new Person;
$person-&gt;id = &apos;abc123&apos;;     // Tohle projde
$person-&gt;id = &apos;xyz789&apos;;     // Ale tohle už ne - readonly!


Což nám dává přesně tu flexibilitu, kterou potřebujeme.

Když si terminologie
protiřečí…

Pojďme se podívat na malý terminologický zmatek v PHP:


	Pořád mluvíme o čtení a zápisu vlastností

	Máme modifikátor readonly

	V phpDoc najdeme anotace @property-read a
	@property-write

	Ale v hooks a asymetrické viditelnosti najednou
	používáme get/set


Bylo by logičtější používat termíny read a
write, ne?

U hooks bych ještě pochopil použití get/set – jde
o akce a navíc to navazuje na magické metody __get/__set. Ale
u asymetrické viditelnosti? To je přece koncepčně odlišná věc –
neřeší „co se má stát“ jako hooks, ale „kdo to může udělat“.
Proto by dávalo mnohem větší smysl použít termín write, tedy
například private(write):

class Person
{
	private(set) string $name;     // takhle to je
	private(write) string $name;   // takhle by to dávalo větší smysl
}


Druhá varianta by byla mnohem intuitivnější. Navíc by lépe ladila
s existujícím modifikátorem readonly.

Vypadá to, že PHP ve snaze o syntaktickou konzistenci mezi hooks a
asymetrickou viditelností obětovalo sémantickou konzistenci s již
existujícími koncepty v jazyce.

Nová éra v PHP:
Revoluce v objektovém návrhu

V PHP světě jsme byli dlouho odkázáni na jediný správný způsob
objektově orientovaného návrhu: všechny vlastnosti private a přístup
k nim výhradně přes gettery a settery. Nebyla to rozmazlenost
vývojářů – public property prostě byly problematické:


	Každý do nich mohl strkat nos bez jakékoliv kontroly

	Byly součástí veřejného API, takže každá změna (třeba přidání
	validace) znamenala rozbití zpětné kompatibility

	V rozhraních jste je mohli tak akorát zmínit v komentářích


Kdo chtěl programovat správně, používat rozhraní a dependency
injection, musel sáhnout po getterech a setterech. Byla to jediná cesta,
jak mít plnou kontrolu nad tím, co se v objektech děje.

Ale s PHP 8.4 přichází nová doba! Property hooks a asymetrická
viditelnost nám konečně dávají nad vlastnostmi stejnou kontrolu jako nad
metodami. Property se stávají plnohodnotnou součástí veřejného API,
protože:


	Kdykoliv můžeme přidat validaci nebo transformaci hodnot

	Máme pod palcem, kdo může hodnoty měnit

	Můžeme je deklarovat v rozhraních


V podstatě můžete property hooks brát jako elegantní náhradu getterů
a setterů bez zbytečného boilerplate kódu. Nebo naopak – gettery a
settery byly jen takové provizorní řešení, než PHP dospělo k něčemu
lepšímu.

Ze své vlastní zkušenosti s Nette můžu mluvit velmi konkrétně –
jak jsem říkal, framework podobnou funkcionalitu nabízel už před 17 lety.
To znamená, že jsem měl možnost s property přístupem pracovat dlouho.
A musím říct, že to bylo nesmírně návykové. Porovnejte:

// Starý svět
$this-&gt;getUser()-&gt;getIdentity()-&gt;getName()

// Nový svět
$this-&gt;user-&gt;identity-&gt;name


Druhý zápis není jen kratší a čitelnější – je taky
přirozenější. Je to jako rozdíl mezi „Prosím, mohli byste mi laskavě
podat informaci o vašem jméně?“ a normálním „Jak se jmenuješ?“.

Jasně, možná namítnete, že přímý přístup k datům může svádět
k porušování principů objektově orientovaného návrhu. Že místo ptaní
se objektu na data bychom ho měli požádat o akci (Tell-Don&apos;t-Ask). To je
pravda – ale hlavně pro objekty s bohatým chováním, které implementují
business logiku. Pro datové transfer objekty, value objects nebo konfigurační
třídy je přímý přístup k datům naprosto přirozený.

Zároveň nám tu vzniká pořádné dilema. Co s existujícími projekty?
Pokud máte knihovnu nebo framework, který důsledně používá gettery a
settery, bylo by možná kontraproduktivní do něj najednou zavádět property.
Rozbili byste tím konzistenci API – uživatel by musel hádat, kde použít
metodu a kde vlastnost.

Časem se určitě vytvoří nové styly a konvence. Některé projekty
možná zůstanou u getterů a setterů, jiné budou hledat cesty jak začlenit
property. Hlavně že máme na výběr.

Důležité je i pojmenování

Jak vlastně property pojmenovat? Zejména u boolean hodnot to není tak
přímočaré, jak by se mohlo zdát.

U metod se běžně používají prefixy is nebo
has:

class Article {
	public function isPublished(): bool { ... }
	public function hasComments(): bool { ... }
}


Ale u properties by tyto prefixy působily krkolomně a redundantně. Místo
nich je lepší používat přídavná jména nebo podstatná jména:

class Article {
	public bool $published;     // lepší než $isPublished
	public bool $commented;     // lepší než $hasComments
	public bool $draft;         // lepší než $isDraft
}

if ($article-&gt;published) {      // čte se přirozeně
	// ...
}


Pro počty položek je lepší použít množné číslo:

class Article {
	public int $views;          // lepší než $viewCount
	public array $tags;         // jasně říká, že jde o kolekci
}


Jde o to, aby kód byl čitelný jako běžná věta. Když píšeme
if ($article-&gt;published), čte se to mnohem přirozeněji než
if ($article-&gt;isPublished). Property by měly vypadat jako
vlastnosti, ne jako zapomenuté závorky u metody.

Kdy použít property a kdy
metody?

Výborná otázka! Tady si můžeme vzít inspiraci z jazyků jako C# nebo
Kotlin, které s property pracují už roky. Property se skvěle
hodí pro:

Value objects a DTO:

class Money {
	public readonly float $amount;
	public readonly string $currency;
}


Jednoduché entity:

class Article {
	public string $title;
	public string $content;
	public DateTimeImmutable $publishedAt;
	public bool $published {
		get =&gt; $this-&gt;publishedAt &lt;= new DateTimeImmutable;
	}
}


Computed hodnoty závislé na jiných vlastnostech:

class Rectangle {
	public float $width;
	public float $height;
	public float $area {
		get =&gt; $this-&gt;width * $this-&gt;height;
	}
}


Metody jsou lepší pro:


	operace pracující s více property najednou

	operace s vedlejšími efekty (logování, notifikace)

	akce, které něco dělají (save, send, calculate…)

	komplexní validace nebo business logiku

	operace, které mohou selhat z více důvodů

	situace, kde oceníme fluent
	interface


Všechna tato doporučení se nám snaží říct jednu základní věc: za
použitím property by se neměl skrývat žádný složitý proces nebo něco,
co má vedlejší efekty. Složitost operace by měla zhruba odpovídat tomu, co
intuitivně očekáváme od čtení či zápisu do proměnné.

I když… vzpomeňte si na innerHTML v JavaScriptu. Když
napíšete element.innerHTML = &apos;&lt;p&gt;Ahoj&lt;/p&gt;&apos;, spustí
se složitý proces parsování HTML, vytvoření DOM stromu, překreslení
stránky… A přesto to všichni považují za přirozené!

Takže možná důležitější než samotná složitost implementace je to,
jestli daná operace _konceptuálně_ odpovídá vlastnosti. Je to jako
s autem – tlačítko start/stop může spustit složitou sekvenci kroků,
ale pro řidiče je to pořád jen „zapnout/vypnout“.
</description>
		<content:encoded><![CDATA[
		<p>Představte si, že by vaše PHP objekty mohly být čistší,
přehlednější a lépe použitelné. Dobrá zpráva – už nemusíte snít!
PHP 8.4 přichází s revoluční novinkou v podobě <b>property hooks</b> a
<b>asymetrické viditelnosti</b>, které kompletně mění pravidla hry
v objektově orientovaném programování. Zapomeňte na neohrabané gettery a
settery – konečně máme k dispozici moderní a intuitivní způsob, jak
kontrolovat přístup k datům objektů. Pojďme se podívat na to, jak tyto
novinky mohou změnit váš kód k nepoznání.</p>

<p>Property hooks představují promyšlený způsob, jak definovat chování
při čtení a zápisu vlastností objektu – a to mnohem čistěji a
výkonněji než dosavadní magické metody <code>__get/__set</code>. Je to jako
byste dostali k dispozici sílu magických metod, ale bez jejich typických
nevýhod.</p>

<p>Podívejme se na jednoduchý příklad z praxe, který vám ukáže, proč
jsou property hooks tak užitečné. Představme si běžnou třídu
<code>Person</code> s veřejnou property <code>age</code>:</p>

<pre
class="language-php"><code>class Person
{
	public int $age = 0;
}

$person = new Person;
$person-&gt;age = 25;  // OK
$person-&gt;age = -5;  // OK, ale to je přece nesmysl!
</code></pre>

<p>PHP sice díky typu <code>int</code> zajistí, že věk bude celé číslo
(to lze od PHP 7.4), ale co s tím záporným věkem? Dříve bychom museli
sáhnout po getterech a setterech, property by musela být private, museli
bychom doplnit spoustu kódu… S hooks to vyřešíme elegantně:</p>

<pre
class="language-php"><code>class Person
{
	public int $age = 0 {
		set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}
}

$person-&gt;age = -5;  // Ups! InvalidArgumentException nás upozorní na nesmysl
</code></pre>

<p>Krása tohoto řešení spočívá v jeho jednoduchosti – navenek se
property chová úplně stejně jako dřív, můžeme číst i zapisovat
přímo přes <code>$person-&gt;age</code>. Ale máme plnou kontrolu nad tím,
co se při zápisu děje. A to je teprve začátek!</p>

<p>Můžeme jít ještě dál a vytvořit třeba hook pro čtení. Hookům lze
přidat atributy. A samozřejmě mohou obsahovat složitější logiku než
jednoduchý výraz. Podívejte se na tento příklad práce se jménem:</p>

<pre
class="language-php"><code>class Person
{
	public string $first;
	public string $last;
	public string $fullName {
		get {
			return &quot;$this-&gt;first $this-&gt;last&quot;;
		}
		set(string $value) {
			[$this-&gt;first, $this-&gt;last] = explode(&#039; &#039;, $value, 2);
		}
	}
}

$person = new Person;
$person-&gt;fullName = &#039;James Bond&#039;;
echo $person-&gt;first;  // vypíše &#039;James&#039;
echo $person-&gt;last;   // vypíše &#039;Bond&#039;
</code></pre>

<p>A něco důležitého: kdykoliv se přistupuje k proměnné (i uvnitř
samotné třídy Person), vždy se využijí hooks. Jediná výjimka je přímý
přístup k reálné proměnné uvnitř kódu samotného hooku.</p>

<h2 id="toc-ohlednuti-do-minulosti-co-nas-naucil-smartobject">Ohlédnutí do
minulosti: Co nás naučil SmartObject?</h2>

<p>Pro uživatele Nette může být zajímavé ohlédnout se do minulosti.
Framework totiž podobnou funkcionalitu nabízel už před <b>17 lety</b> ve
formě <a href="https://doc.nette.org/cs/utils/smartobject">SmartObject</a>,
který výrazně vylepšoval práci s objekty v době, kdy PHP v této
oblasti značně zaostávalo.</p>

<p>Pamatuju si, že tehdy přišla vlna bezbřehého nadšení, kdy se
properties používaly prakticky všude. Tu pak vystřídala vlna opačná –
nepoužívat je nikde. Důvod? Chybělo jasné vodítko, kdy je lepší použít
metody a kdy property. Ale dnešní nativní řešení je kvalitativně úplně
jinde.Property hooks a asymetrická viditelnost jsou plnohodnotné nástroje,
které nám dávají stejnou úroveň kontroly jako máme u metod. Proto dnes
můžeme mnohem lépe rozlišit, kdy je property skutečně tím správným
řešením.</p>
<!--more-->
<h2 id="toc-backed-nebo-virtual-dobra-otazka">Backed nebo Virtual? Dobrá
otázka!</h2>

<p>Podívejte se na tento kód a zkuste si rychle odpovědět – je to
vlastně jednoduchý kvíz:</p>

<ul>
	<li>Můžeme do <code>$age</code> zapisovat?</li>

	<li>A co <code>$adult</code> – můžeme číst i zapisovat?</li>
</ul>

<pre
class="language-php"><code>class Person
{
	public int $age = 0 {
		set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}

	public bool $adult {
		get =&gt; $this-&gt;age &gt;= 18;
	}
}
</code></pre>

<p>Samozřejmě <code>$age</code> je, jak jsme si řekli už dříve, property
pro čtení i pro zápis. Ale <code>$adult</code> je jen pro čtení!</p>

<p>A tady narážíme na první zapeklitost v designu property hooks. <b>Ze
signatury property vůbec nepoznáme, jestli do ní můžeme zapisovat nebo
ji číst!</b></p>

<p>Odpověď se totiž skrývá v kódu, v implementaci hooků. Property
totiž mohou být dvojího druhu: backed (se skutečným úložištěm
v paměti) a virtuální (které pouze simulují existenci property). To, zda
je property backed nebo virtuální, rozhoduje, zda se v kódu hooku na ni
odkazujeme.</p>

<p>Property je backed (má vlastní úložiště), když:</p>

<ul>
	<li>v těle hooku se na ni odkazujeme přes
	<code>$this-&gt;propertyName</code></li>

	<li>nebo má zkrácený <code>set</code>, který automaticky znamená zápis
	do <code>$this-&gt;propertyName</code></li>
</ul>

<p>V našem příkladu tedy:</p>

<ul>
	<li>Property <code>$age</code> je <b>backed</b>, protože používá zkrácený
	<code>set</code> (což automaticky znamená zápis do
	<code>$this-&gt;age</code>)</li>

	<li>Property <code>$adult</code> je <b>virtuální</b>, protože žádný její
	hook neobsahuje <code>$this-&gt;adult</code></li>
</ul>

<p>Je to sice mazané řešení, ale ne zrovna šťastné. Tak zásadní
informaci, jako zda lze property číst nebo do ní zapisovat, má prozradit API
a signatura na první pohled, ne až studium implementace.</p>

<h2 id="toc-kdyz-reference-tak-bezpecne">Když reference, tak bezpečně!</h2>

<p>Reference existují v PHP od jeho počátků. Pomocí znaku
<code>&amp;</code> můžete propojit dvě proměnné tak, aby ukazovaly na
stejné místo v paměti. Je to jako mít dva dálkové ovladače k jedné
televizi – ať zmáčknete kterýkoliv, ovládáte tu samou obrazovku.</p>

<p>Ale co kdyby někdo mohl získat referenci na property s <code>set</code>
hookem? Mohl by její hodnotu měnit přímo a kompletně tak obejít veškerou
validaci. Podívejte se na tento příklad:</p>

<pre
class="language-php"><code>class Person
{
	public int $age = 0 {
		set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}
}

$person = new Person;
$ref = &amp;$person-&gt;age;    // Fatal error: Tohle neprojde!
$ref = -5;               // Kdyby to prošlo, validace by byla k ničemu
</code></pre>

<p>PHP na to myslelo a elegantně to vyřešilo (tedy pardon, mysleli na to
Ilija Tovilo a Larry Garfield, autoři hooků). Získat referenci na takovou
property prostě není možné (myšleno na backed proměnnou se
<code>set</code> hookem). Je to správné řešení – property hook má
zajistit, že se do property dostane jen platná hodnota, a reference by tuto
kontrolu obcházely.</p>

<h2 id="toc-kdyz-pole-potka-property-hooks-zajimava-vyzva">Když pole potká
property hooks – zajímavá výzva!</h2>

<p>Práce s poli v PHP je obvykle příjemně přímočará. Do pole
v property můžeme přidávat prvky různými způsoby:</p>

<pre
class="language-php"><code>class Person
{
	public array $phones = [];
}

$person = new Person;
$person-&gt;phones[] = &#039;777 123 456&#039;;          // přidá číslo na konec pole
$person-&gt;phones[&#039;bob&#039;] = &#039;777 123 456&#039;;     // přidá číslo s konkrétním klíčem
</code></pre>

<p>A právě tady narážíme na zajímavý problém s property hooks.
Představme si, že chceme vytvořit třídu Person, která bude obsahovat
seznam telefonních čísel, a chceme, aby se u nich automaticky ořezávaly
mezery na začátku a konci:</p>

<pre
class="language-php"><code>class Person
{
	public array $phones = [] {
		set =&gt; array_map(&#039;trim&#039;, $value);
	}
}

$person = new Person;
$person-&gt;phones[] = &#039;777 123 456&#039;;  // Překvapení! Error: Indirect modification of Person::$phones is not allowed
</code></pre>

<p>Proč to nefunguje? Operace <code>$person-&gt;phones[]</code> totiž v PHP
funguje ve dvou krocích:</p>

<ol>
	<li>Nejdřív získá referenci na pole pomocí <code>get</code></li>

	<li>Pak do získaného pole přidá novou hodnotu</li>
</ol>

<p>Tedy vůbec se nevolá <code>set</code> hook. Ba co víc, jak už víme
z předchozí kapitoly, nelze získat referenci na backed proměnnou se
<code>set</code> hookem (tedy udělat první krok). Proto ta chybová
hláška.</p>

<p>Ani metoda <code>addPhone()</code>, která by volala
<code>$this-&gt;phones[] = $phone</code>, nám nepomůže – všechny
přístupy k property (i uvnitř třídy) totiž procházejí
přes hooky.</p>

<p>Tak jak z toho ven? Pojďme si projít možná řešení. První, které
vás možná napadne:</p>

<pre
class="language-php"><code>$phones = $person-&gt;phones;    // načteme pole
$phones[] = &#039; 777 123 456 &#039;;  // přidáme číslo
$person-&gt;phones = $phones;    // uložíme zpět
</code></pre>

<p>Funguje to, ale… představte si pole s tisíci čísly. Náš
<code>set</code> hook by musel provést <code>trim()</code> na všech číslech
znovu, i když se přidalo jediné. To není zrovna ukázka efektivity.</p>

<p>Existuje lepší cesta – uvědomit si, že pokud má pole nějak
specificky pracovat se svými prvky (třeba ořezávat mezery), mělo by to být
<b>jeho zodpovědností</b>, ne úkolem třídy, která ho jen drží. Jasně,
pole samo o sobě nenaučíme novým trikům, ale můžeme ho „zabalit“ do
objektu s rozhraním ArrayAccess:</p>

<pre
class="language-php"><code>class Phones implements ArrayAccess
{
	private array $data = [];

	public function __construct(array $data = [])
	{
		$this-&gt;data = array_map(&#039;trim&#039;, $data);
	}

	public function offsetSet(mixed $offset, mixed $value): void
	{
		$value = trim($value);
		if ($offset === null) {
			$this-&gt;data[] = $value;
		} else {
			$this-&gt;data[$offset] = $value;
		}
	}

	// implementace dalších metod pro ArrayAccess...
}

class Person
{
	function __construct(
		public Phones $phones = new Phones,
	) {}
}

$person = new Person;
$person-&gt;phones[] = &#039; 777 123 456 &#039;;  // Hurá! Číslo se uloží pěkně ořezané
</code></pre>

<p>A teď třešnička na dortu – můžeme využít hook k tomu, aby do
<code>$person-&gt;phones</code> šlo zapsat i obyčejné pole:</p>

<pre
class="language-php"><code>class Person
{
	function __construct(
		public Phones $phones = new Phones {
			set(array|Phones $value) =&gt; is_array($value) ? new Phones($value) : $value;
		},
	) {}
}

$person = new Person;
$person-&gt;phones = [&#039;  888 999 000  &#039;, &#039;777 888 999&#039;];  // Magicky se převede na Phones a ořeže řetězce
</code></pre>

<p>Jak vidíte, hooks mohou obsahovat i promoted properties.</p>

<p>Ještě se podívejme na alternativní řešení. Vzpomeňte si, že kromě
backed property máme ještě virtuální property – ty, které
nepoužívají <code>$this-&gt;propertyName</code> v těle hooku. A tady se
skýtá druhé řešení:</p>

<pre
class="language-php"><code>class Person
{
	private array $_phones = []; // skutečné úložiště čísel

	public array $phones {  // virtuální property pro veřejný přístup
		get =&gt; $this-&gt;_phones;
		set {
			$this-&gt;_phones = array_map(&#039;trim&#039;, $value);
		}
	}

	public function addPhone(string $phone): void
	{
		$this-&gt;_phones[] = trim($phone);
	}
}

$person = new Person;
$person-&gt;addPhone(&#039; 777 123 456 &#039;);  // Přidá ořezané číslo
echo $person-&gt;phones[0];             // Vypíše &quot;777 123 456&quot;
$person-&gt;phones = [&#039;  888 999 000  &#039;]; // Nastaví nové pole s ořezanými čísly
</code></pre>

<p>Tady jsme zůstali u klasického pole, ale schovali jsme ho za privátní
proměnnou. Na venek nabízíme virtuální property pro čtení celého pole a
jeho kompletní přepsání, plus specializovanou metodu pro přidávání
jednotlivých čísel.</p>

<h2 id="toc-hooks-a-dedicnost-kdyz-potomci-prebiraji-zezlo">Hooks a dědičnost:
Když potomci přebírají žezlo</h2>

<p>Potomci mohou nejen přidávat hooks k vlastnostem, které je dosud neměly,
ale také předefinovat ty existující. Podívejme se na příklad:</p>

<pre
class="language-php"><code>class Person
{
	public string $email;

	public int $age {
		set =&gt; $value &gt;= 0
			? $value
			: throw new InvalidArgumentException(&#039;Věk nemůže být záporný&#039;);
	}
}

class Employee extends Person
{
	// Přidá hook k vlastnosti, která žádný neměla
	public string $email {
		set =&gt; strtolower($value);  // Emaily vždy převedeme na malá písmena
	}

	// Rozšíří existující validaci věku
	public int $age {
		set {
			if ($value &lt;= 130) {  // First check the original condition
				throw new InvalidArgumentException(&#039;130 years? Not buying it!&#039;);
			}
			parent::$age::set($value);
		}
	}
}
</code></pre>

<p>Všimněte si té zajímavé syntaxe <code>parent::$age::set($value)</code>.
Na první pohled možná vypadá zvláštně, ale dává perfektní smysl –
nejdřív se odkážeme na vlastnost v rodiči a pak na její hook. Je to jako
bychom řekli „hej, zavolej set hook na <code>age</code> vlastnosti mého
rodiče“.</p>

<p>A co víc – můžeme hooks označit jako <code>final</code>, pokud chceme
zabránit jejich přepsání v potomcích. Dokonce můžeme jako
<code>final</code> označit celou property – pak ji potomci nemohou změnit
žádným způsobem (ani přidat hooks, ani rozšířit její viditelnost).</p>

<pre
class="language-php"><code>class Person
{
	// Tenhle hook už nikdo nepřepíše
	public int $age {
		final set =&gt; $value &gt;= 0 ? $value : throw new InvalidArgumentException;
	}

	// A tuhle property už vůbec nikdo nezmění
	final public string $id;
}
</code></pre>

<h2 id="toc-property-v-rozhranich">Property v rozhraních</h2>

<p>Překvapivou novinkou je podpora property v rozhraních a abstraktních
třídách. Představte si, že vytváříte rozhraní pro entity, které
obsahují řetězec se jménem. Doteď jsme museli psát něco takového:</p>

<pre
class="language-php"><code>interface Named
{
	public function getName(): string;
	public function setName(string $name): void;
}
</code></pre>

<p>Nuda, že? S property hooks můžeme být mnohem elegantnější!
V rozhraní teď můžeme deklarovat přímo property, a to dokonce
asymetricky – můžeme říct zvlášť, co má být čitelné a co
zapisovatelné:</p>

<pre
class="language-php"><code>interface Named
{
	// Říkáme: &quot;Implementující třída musí mít veřejně čitelnou property name&quot;
	public string $name { get; }
}
</code></pre>

<p>A teď to zajímavé – jak můžeme takové rozhraní implementovat?
Máme hned několik možností:</p>

<pre
class="language-php"><code>class Person implements Named
{
	public string $name;     // Nejjednodušší řešení - obyčejná property
}

class Employee implements Named
{
	public string $name {    // Pokročilejší - složené jméno
		get =&gt; $this-&gt;firstName . &#039; &#039; . $this-&gt;lastName;
	}
	private string $firstName;
	private string $lastName;
}
</code></pre>

<p>Všimněte si zajímavého detailu – rozhraní <code>Named</code>
požaduje property pouze pro čtení, ale třída <code>Person</code> nabízí
property čitelnou i zapisovatelnou. A to je naprosto v pořádku –
rozhraní totiž definuje jen minimální požadavky. Je to jako když řeknete
„potřebuju auto, co jede dopředu“ a dostanete auto, co umí i couvat –
splňuje to vaše minimální požadavky a přidává něco navíc.</p>

<p>Pro puntičkáře: V rozhraní musíme u property použít klíčové slovo
<code>public</code>, i když je to vlastně nadbytečné – vše v rozhraní
je ze své podstaty veřejné. U metod uvádět <code>public</code> je
hloupost, ale u property je to vyžadováno kvůli konzistenci syntaxe.</p>

<p>A ještě jedna věc stojí za zmínku – všimli jste si té zvláštní
syntaxe <code>{ get; set; }</code>? Zatímco ve třídě můžeme napsat
jednoduše <code>public string $name</code>, v rozhraní musíme explicitně
říct, jaké operace property podporuje. Je to sice trochu pracnější, ale
dává to smysl – u rozhraní chceme být maximálně explicitní v tom, co
požadujeme.</p>

<h2 id="toc-property-v-abstraktnich-tridach-to-nejlepsi-z-obou-svetu">Property
v abstraktních třídách: To nejlepší z obou světů</h2>

<p>Abstraktní třídy si vezmou to nejlepší z rozhraní a přidají vlastní
šťávu. Mohou nejen deklarovat property, ale také nabídnout výchozí
implementaci některých hooků:</p>

<pre
class="language-php"><code>abstract class Person
{
	// Čistě abstraktní property - implementaci dodá potomek
	abstract public string $name { get; }

	// Protected property s oběma operacemi
	abstract protected int $age { get; set; }

	// Tady už nabízíme hotovou validaci emailu
	abstract public string $email {
		get; // tento hook je abstraktní a potomek ho musí implementovat
		set =&gt; Nette\Utils\Validators::isEmail($value)
			? $value
			: throw new InvalidArgumentException(&#039;Tohle nevypadá jako email...&#039;);
	}
}
</code></pre>

<h2 id="toc-a-ted-neco-opravdu-zajimaveho-kovarianci-a-kontravarianci">A teď
něco opravdu zajímavého – kovarianci a kontravarianci!</h2>

<p>Zní to jako zaklínadlo, ale je to vlastně jednoduchá věc.
Podívejte se:</p>

<pre
class="language-php"><code>class Animal {}
class Dog extends Animal {}

interface PetShop
{
	// Property jen pro čtení může vracet specifičtější typ
	public Animal $pet { get; }
}

class DogShop implements PetShop
{
	// Vrací psa místo zvířete - to je v pohodě!
	public Dog $pet { get; }
}
</code></pre>

<p>Když má property pouze hook <code>get</code>, může v potomkovi vracet
specifičtější typ (tomu se říká kovariance). Představte si to jako:
„Slíbil jsem ti zvíře, a pes je přece taky zvíře, ne?“</p>

<p>Naopak property pouze s hookem <code>set</code> může v potomkovi
přijímat obecnější typ (kontravariance). Je to logické – když umím
pracovat s konkrétním typem, zvládnu i jeho předka.</p>

<p>Jakmile má property oba hooky <code>get</code> i <code>set</code>, musí typ
zůstat stejný. Proč? Protože by to mohlo vést k nekonzistencím –
nemůžeme slíbit, že vrátíme psa, když nám někdo může přes setter
podstrčit kočku!</p>

<h2 id="toc-asymetricka-viditelnost-kazdemu-co-jeho-jest">Asymetrická
viditelnost: Každému, co jeho jest</h2>

<p>Představte si, že vytváříte třídu Person a chcete, aby datum narození
mohl číst kdokoliv, ale měnit ho mohla jen samotná třída. Dřív byste
museli sáhnout po getterech a setterech, ale teď? Teď máme elegantní
řešení:</p>

<pre
class="language-php"><code>class Person
{
	public private(set) DateTimeImmutable $dateOfBirth;
}
</code></pre>

<p>Tenhle zápis říká: „Číst může každý, zapisovat jen třída
sama.“ První modifikátor <code>public</code> určuje viditelnost pro
čtení, druhý <code>private(set)</code> pro zápis. A protože veřejné
čtení je default, můžeme ho vynechat a psát prostě:</p>

<pre
class="language-php"><code>class Person
{
	private(set) DateTimeImmutable $dateOfBirth;
}
</code></pre>

<p>Samozřejmě platí logické pravidlo – viditelnost pro zápis nemůže
být širší než pro čtení. Nemůžeme použít třeba
<code>protected public(set)</code> – to by bylo jako říct „číst můžou
jen potomci, ale zapisovat může každý“. Trochu podivné, ne?</p>

<p>A co dědičnost? V PHP platí, že potomek může viditelnost buď
zachovat, nebo rozšířit z protected na public. To samé platí i pro
asymetrickou viditelnost:</p>

<pre
class="language-php"><code>class Person
{
	public protected(set) string $name;  // Číst může každý, zapisovat jen potomci
}

class Employee extends Person
{
	public public(set) string $name;     // Potomek může rozšířit práva zápisu
}
</code></pre>

<p>Zajímavý je případ <code>private(set)</code>. Taková property je
automaticky <code>final</code> – když řekneme, že zapisovat může jen
třída sama, logicky to znamená, že ani potomci nemají právo
to měnit.</p>

<p>A nejlepší na tom je, že asymetrickou viditelnost můžeme kombinovat
s hooks:</p>

<pre
class="language-php"><code>class Person
{
	private(set) DateTimeImmutable $birthDate {
		set =&gt; $value &gt; new DateTimeImmutable
			? throw new InvalidArgumentException(&#039;Narození v budoucnosti? Sci-fi!&#039;)
			: $value;
	}
}
</code></pre>

<p>Tahle property má všechno: je veřejně čitelná, zapisovat do ní může
jen třída sama, a ještě kontroluje, jestli datum není v budoucnosti. Hooks
řeší „co se má stát“, asymetrická viditelnost „kdo to může
udělat“. Perfektní tým!</p>

<h2
id="toc-asymetricka-viditelnost-a-pole-elegantni-reseni-stareho-problemu">Asymetrická
viditelnost a pole: Elegantní řešení starého problému</h2>

<p>Pamatujete na naše trápení s telefonními čísly? Asymetrická
viditelnost nám nabízí ještě jedno řešení:</p>

<pre
class="language-php"><code>class Person
{
	private(set) array $phones = [];

	public function addPhone(string $phone): void
	{
		$this-&gt;phones[] = trim($phone);
	}
}

$person = new Person;
var_dump($person-&gt;phones);     // OK: můžeme číst
$person-&gt;addPhone(&#039;...&#039;);      // OK: můžeme přidat číslo
$person-&gt;phones = [];          // CHYBA: nemůžeme přepsat celé pole
</code></pre>

<p>Pole je veřejně čitelné, ale nikdo zvenčí ho nemůže přepsat. Pro
přidání nového čísla máme specializovanou metodu. Žádné složité
objekty simulující pole, žádné virtual properties – jen čistá, jasná
kontrola přístupu.</p>

<p>Pro úplnost dodejme, že u vlastnosti s omezeným zápisem nemůžete
získat referenci zvenku:</p>

<pre
class="language-php"><code>$ref = &amp;$person-&gt;phones;    // Fatal error: Takhle ne!
</code></pre>

<p>Reference jsou povolené jen ze scope, ze kterého je property
zapisovatelná. Je to logické – reference by mohla obejít naše omezení
pro zápis.</p>

<p>Když to shrneme, pro práci s polem v property máme teď několik
možností:</p>

<ol>
	<li><b>Chytrý objekt</b> simulující pole (přináší víc možností, ale
	taky víc kódu)</li>

	<li><b>Backed property s hookem</b> (znemožňuje přímou
	modifikaci pole)</li>

	<li><b>Virtual property</b> s privátním úložištěm (vyžaduje metody pro
	úpravy)</li>

	<li><b>Asymetrická viditelnost</b> (přesouvá logiku do metod)</li>
</ol>

<p>Který přístup vybrat? Jak už to bývá – záleží na konkrétním
případu. Prostě si zkusit, které API nejlépe sedne do ruky.</p>

<h2 id="toc-readonly-a-asymetricka-viditelnost-konecne-svoboda-volby">Readonly a
asymetrická viditelnost: Konečně svoboda volby!</h2>

<p><a href="https://phpfashion.com/cs/readonly-vlastnosti-v-php-a-jejich-skryta-uskali">Modifikátor
<code>readonly</code></a> jsou ve skutečnosti dva modifikátory v jednom:
zakazuje vícenásobný zápis a zároveň omezuje zápis na private. Vlastně
to není žádný readonly, je to spíš writeonce zkřížený s
<code>private(set)</code>.</p>

<p>To druhé mi přišlo vždy zbytečně přísné a nepraktické. Proč by
readonly vlastnost nemohla být zapisovatelná třeba v potomcích?</p>

<p>PHP 8.4 to konečně změnilo. Teď <code>readonly</code> dělá property
defaultně <code>protected(set)</code>, tedy zapisovatelnou i v potomcích.
A když potřebujeme jinou viditelnost? Jednoduše si ji nastavíme:</p>

<pre
class="language-php"><code>class Person
{
	// Readonly přístupná jen uvnitř třídy (staré chování)
	public private(set) readonly string $name;

	// Readonly přístupná i v potomcích (nové výchozí chování)
	public readonly string $dateOfBirth;

	// Readonly zapisovatelná kdekoliv (ale jen jednou!)
	public public(set) readonly string $id;

	public function rename(string $newName): void
	{
		$this-&gt;name = $newName;    // Uvnitř třídy můžeme měnit
	}
}

class Employee extends Person
{
	public function setBirthDate(DateTimeImmutable $date): void
	{
		$this-&gt;dateOfBirth = $date;  // V potomkovi můžeme měnit
	}
}

$person = new Person;
$person-&gt;id = &#039;abc123&#039;;     // Tohle projde
$person-&gt;id = &#039;xyz789&#039;;     // Ale tohle už ne - readonly!
</code></pre>

<p>Což nám dává přesně tu flexibilitu, kterou potřebujeme.</p>

<h2 id="toc-kdyz-si-terminologie-protireci">Když si terminologie
protiřečí…</h2>

<p>Pojďme se podívat na malý terminologický zmatek v PHP:</p>

<ul>
	<li>Pořád mluvíme o čtení a zápisu vlastností</li>

	<li>Máme modifikátor <code>readonly</code></li>

	<li>V phpDoc najdeme anotace <code>@property-read</code> a
	<code>@property-write</code></li>

	<li>Ale v hooks a asymetrické viditelnosti najednou
	používáme <code>get/set</code></li>
</ul>

<p>Bylo by logičtější používat termíny <code>read</code> a
<code>write</code>, ne?</p>

<p>U hooks bych ještě pochopil použití <code>get/set</code> – jde
o akce a navíc to navazuje na magické metody <code>__get/__set</code>. Ale
u asymetrické viditelnosti? To je přece koncepčně odlišná věc –
neřeší „co se má stát“ jako hooks, ale „kdo to může udělat“.
Proto by dávalo mnohem větší smysl použít termín <code>write</code>, tedy
například <code>private(write)</code>:</p>

<pre
class="language-php"><code>class Person
{
	private(set) string $name;     // takhle to je
	private(write) string $name;   // takhle by to dávalo větší smysl
}
</code></pre>

<p>Druhá varianta by byla mnohem intuitivnější. Navíc by lépe ladila
s existujícím modifikátorem <code>readonly</code>.</p>

<p>Vypadá to, že PHP ve snaze o syntaktickou konzistenci mezi hooks a
asymetrickou viditelností obětovalo sémantickou konzistenci s již
existujícími koncepty v jazyce.</p>

<h2 id="toc-nova-era-v-php-revoluce-v-objektovem-navrhu">Nová éra v PHP:
Revoluce v objektovém návrhu</h2>

<p>V PHP světě jsme byli dlouho odkázáni na jediný správný způsob
objektově orientovaného návrhu: všechny vlastnosti private a přístup
k nim výhradně přes gettery a settery. Nebyla to rozmazlenost
vývojářů – public property prostě byly problematické:</p>

<ul>
	<li>Každý do nich mohl strkat nos bez jakékoliv kontroly</li>

	<li>Byly součástí veřejného API, takže každá změna (třeba přidání
	validace) znamenala rozbití zpětné kompatibility</li>

	<li>V rozhraních jste je mohli tak akorát zmínit v komentářích</li>
</ul>

<p>Kdo chtěl programovat správně, používat rozhraní a <a
href="https://doc.nette.org/cs/dependency-injection/introduction">dependency
injection</a>, musel sáhnout po getterech a setterech. Byla to jediná cesta,
jak mít plnou kontrolu nad tím, co se v objektech děje.</p>

<p>Ale s PHP 8.4 přichází nová doba! Property hooks a asymetrická
viditelnost nám konečně dávají nad vlastnostmi stejnou kontrolu jako nad
metodami. Property se stávají plnohodnotnou součástí veřejného API,
protože:</p>

<ul>
	<li>Kdykoliv můžeme přidat validaci nebo transformaci hodnot</li>

	<li>Máme pod palcem, kdo může hodnoty měnit</li>

	<li>Můžeme je deklarovat v rozhraních</li>
</ul>

<p>V podstatě můžete property hooks brát jako elegantní náhradu getterů
a setterů bez zbytečného boilerplate kódu. Nebo naopak – gettery a
settery byly jen takové provizorní řešení, než PHP dospělo k něčemu
lepšímu.</p>

<p>Ze své vlastní zkušenosti s Nette můžu mluvit velmi konkrétně –
jak jsem říkal, framework podobnou funkcionalitu nabízel už před 17 lety.
To znamená, že jsem měl možnost s property přístupem pracovat dlouho.
A musím říct, že to bylo nesmírně návykové. Porovnejte:</p>

<pre
class="language-php"><code>// Starý svět
$this-&gt;getUser()-&gt;getIdentity()-&gt;getName()

// Nový svět
$this-&gt;user-&gt;identity-&gt;name
</code></pre>

<p>Druhý zápis není jen kratší a čitelnější – je taky
přirozenější. Je to jako rozdíl mezi „Prosím, mohli byste mi laskavě
podat informaci o vašem jméně?“ a normálním „Jak se jmenuješ?“.</p>

<p>Jasně, možná namítnete, že přímý přístup k datům může svádět
k porušování principů objektově orientovaného návrhu. Že místo ptaní
se objektu na data bychom ho měli požádat o akci (Tell-Don't-Ask). To je
pravda – ale hlavně pro objekty s bohatým chováním, které implementují
business logiku. Pro datové transfer objekty, value objects nebo konfigurační
třídy je přímý přístup k datům naprosto přirozený.</p>

<p>Zároveň nám tu vzniká pořádné dilema. Co s existujícími projekty?
Pokud máte knihovnu nebo framework, který důsledně používá gettery a
settery, bylo by možná kontraproduktivní do něj najednou zavádět property.
Rozbili byste tím konzistenci API – uživatel by musel hádat, kde použít
metodu a kde vlastnost.</p>

<p>Časem se určitě vytvoří nové styly a konvence. Některé projekty
možná zůstanou u getterů a setterů, jiné budou hledat cesty jak začlenit
property. Hlavně že máme na výběr.</p>

<h2 id="toc-dulezite-je-i-pojmenovani">Důležité je i pojmenování</h2>

<p>Jak vlastně property pojmenovat? Zejména u boolean hodnot to není tak
přímočaré, jak by se mohlo zdát.</p>

<p>U metod se běžně používají prefixy <code>is</code> nebo
<code>has</code>:</p>

<pre
class="language-php"><code>class Article {
	public function isPublished(): bool { ... }
	public function hasComments(): bool { ... }
}
</code></pre>

<p>Ale u properties by tyto prefixy působily krkolomně a redundantně. Místo
nich je lepší používat přídavná jména nebo podstatná jména:</p>

<pre
class="language-php"><code>class Article {
	public bool $published;     // lepší než $isPublished
	public bool $commented;     // lepší než $hasComments
	public bool $draft;         // lepší než $isDraft
}

if ($article-&gt;published) {      // čte se přirozeně
	// ...
}
</code></pre>

<p>Pro počty položek je lepší použít množné číslo:</p>

<pre
class="language-php"><code>class Article {
	public int $views;          // lepší než $viewCount
	public array $tags;         // jasně říká, že jde o kolekci
}
</code></pre>

<p>Jde o to, aby kód byl čitelný jako běžná věta. Když píšeme
<code>if ($article-&gt;published)</code>, čte se to mnohem přirozeněji než
<code>if ($article-&gt;isPublished)</code>. Property by měly vypadat jako
vlastnosti, ne jako zapomenuté závorky u metody.</p>

<h2 id="toc-kdy-pouzit-property-a-kdy-metody">Kdy použít property a kdy
metody?</h2>

<p>Výborná otázka! Tady si můžeme vzít inspiraci z jazyků jako C# nebo
Kotlin, které s property pracují už roky. Property se skvěle
hodí pro:</p>

<p>Value objects a DTO:</p>

<pre
class="language-php"><code>class Money {
	public readonly float $amount;
	public readonly string $currency;
}
</code></pre>

<p>Jednoduché entity:</p>

<pre
class="language-php"><code>class Article {
	public string $title;
	public string $content;
	public DateTimeImmutable $publishedAt;
	public bool $published {
		get =&gt; $this-&gt;publishedAt &lt;= new DateTimeImmutable;
	}
}
</code></pre>

<p>Computed hodnoty závislé na jiných vlastnostech:</p>

<pre
class="language-php"><code>class Rectangle {
	public float $width;
	public float $height;
	public float $area {
		get =&gt; $this-&gt;width * $this-&gt;height;
	}
}
</code></pre>

<p>Metody jsou lepší pro:</p>

<ul>
	<li>operace pracující s více property najednou</li>

	<li>operace s vedlejšími efekty (logování, notifikace)</li>

	<li>akce, které něco dělají (save, send, calculate…)</li>

	<li>komplexní validace nebo business logiku</li>

	<li>operace, které mohou selhat z více důvodů</li>

	<li>situace, kde oceníme <a
	href="https://doc.nette.org/cs/introduction-to-object-oriented-programming#toc-fluent-interfaces">fluent
	interface</a></li>
</ul>

<p>Všechna tato doporučení se nám snaží říct jednu základní věc: za
použitím property by se neměl skrývat žádný složitý proces nebo něco,
co má vedlejší efekty. Složitost operace by měla zhruba odpovídat tomu, co
intuitivně očekáváme od čtení či zápisu do proměnné.</p>

<p>I když… vzpomeňte si na <code>innerHTML</code> v JavaScriptu. Když
napíšete <code>element.innerHTML = '&lt;p&gt;Ahoj&lt;/p&gt;'</code>, spustí
se složitý proces parsování HTML, vytvoření DOM stromu, překreslení
stránky… A přesto to všichni považují za přirozené!</p>

<p>Takže možná důležitější než samotná složitost implementace je to,
jestli daná operace _konceptuálně_ odpovídá vlastnosti. Je to jako
s autem – tlačítko start/stop může spustit složitou sekvenci kroků,
ale pro řidiče je to pořád jen „zapnout/vypnout“.</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/property-hooks-v-php-8-4-revoluce-nebo-past#comments</comments>
		<pubDate>Mon, 25 Nov 2024 03:23:00 +0100</pubDate>
		<guid isPermaLink="false">item2336@http://phpfashion.com</guid>
	</item>
	<item>
		<title>Readonly vlastnosti v PHP a jejich skrytá úskalí</title>
		<link>https://phpfashion.com/cs/readonly-vlastnosti-v-php-a-jejich-skryta-uskali</link>
		<description>Představte si, že byste mohli svým datům dát pevnou půdu pod
nohama – jednou je nastavíte a pak si můžete být jistí, že je nikdo
nezmění. Přesně to přineslo PHP 8.1 s readonly vlastnostmi. Je to jako
dát vašim objektům neprůstřelnou vestu – chrání jejich data před
nechtěnými změnami. Pojďme se podívat, jak vám tento mocný nástroj
může usnadnit život a na co si při jeho používání dát pozor.

Začněme jednoduchým příkladem:

class User
{
    public readonly string $name;

    public function setName(string $name): void
    {
        $this-&gt;name = $name;  // První nastavení - vše OK
    }
}

$user = new User;
$user-&gt;setName(&apos;John&apos;);      // Paráda, máme jméno
echo $user-&gt;name;            // &quot;John&quot;
$user-&gt;setName(&apos;Jane&apos;);      // BOOM! Výjimka: Cannot modify readonly property


Jakmile jednou jméno nastavíte, je to jako vytesané do kamene. Žádné
náhodné přepsání, žádné nechtěné změny.

Kdy je uninitialized
opravdu uninitialized?

Často se setkávám s mýtem, že readonly vlastnosti musí být nastaveny
v konstruktoru. Ve skutečnosti je PHP mnohem flexibilnější – můžete je
inicializovat kdykoliv během života objektu, ale pouze jednou! Před prvním
přiřazením jsou ve speciálním stavu ‚uninitialized‘, což je takový
limbo stav mezi nebytím a bytím.

A tady přichází zajímavý detail – readonly vlastnosti nemohou mít
výchozí hodnotu. A proč? Kdyby měly výchozí hodnotu, staly by se de facto
konstantami – hodnota by byla nastavena při vytvoření objektu a už by
nešla změnit.

Vyžadují se typy

Readonly proměnné vyžadují explicitní definici datového typu. Je to
proto, že stav ‚uninitialized‘, který využívají, existuje pouze
u typovaných proměnných. Bez uvedení typu tedy readonly proměnnou nelze
definovat. Pokud si nejste jistí typem, můžete použít
mixed.

Readonly třídy: Když
jeden zámek nestačí

S PHP 8.2 přišla možnost posunout zabezpečení na další úroveň.
Představte si, že místo zamykání jednotlivých místností můžete
zamknout celou budovu. Tedy celou třídu:

readonly class User
{
    public string $name;     // Automaticky readonly!
    public string $email;    // Taky readonly!
}


Ale pozor, s velkou mocí přichází velká omezení:


	Žádné vlastnosti bez typu

	Statické vlastnosti jsou tabu

	Dynamické vlastnosti? Ani s atributem
	#[AllowDynamicProperties]

	A v PHP 8.2 mohla readonly třída dědit jen od readonly třídy (PHP
	8.3 už je v tomto benevolentnější)


Kdo může inicializovat a kdy?

Tady je to zajímavé – podívejte se na tento kód:

$user = new User;
$user-&gt;name = &apos;John&apos;;  // BUUM! Cannot initialize readonly property from global scope


Překvapení? I když jde o první přiřazení, PHP řekne ne. Stejně tak
potomek třídy nemůže inicializovat readonly vlastnost svého rodiče:

class Employee extends User
{
    public function setName(string $name): void
    {
        $this-&gt;name = &apos;EMP: &apos; . $name;  // BUUM! Ani potomek nemůže!
    }
}


Readonly proměnnou lze tedy inicializovat výhradně ze třídy, která ji
definovala. Přesněji řečeno šlo. PHP 8.4 totiž přináší dvě
důležité změny:


	Readonly vlastnosti můžou inicializovat i potomci (konečně!)

	S modifikátorem
	public(set) můžete povolit inicializaci i zvenčí:


class User
{
    public(set) readonly string $name;  // Nová svoboda v PHP 8.4
}

$user = new User;
$user-&gt;name = &apos;John&apos;;  // Teď už to funguje!


Když readonly neznamená
„opravdu neměnné“

Představte si readonly jako zámek na dveřích – zamkne dveře, ale co
se děje uvnitř místnosti, to už neuhlídá. Samotné readonly nezaručuje
úplnou neměnnost dat. Pokud do readonly proměnné uložíme objekt, jeho
vnitřní stav zůstává modifikovatelný, objekt se automaticky nestává
immutable (neměnným):

class Settings
{
	public string $theme = &apos;light&apos;;
}

class Configuration
{
	public function __construct(
		public readonly Settings $settings = new Settings,
	) {
	}
}

$config = new Configuration;
$config-&gt;settings-&gt;theme = &apos;dark&apos;; // toto je povoleno, přestože $settings je readonly!


Vidíte? Samotný objekt $settings je uzamčený, ale jeho
vnitřnosti můžeme měnit, jak se nám zlíbí.

U polí je situace specifická. Přímá modifikace prvků pole není
možná, protože PHP to považuje za změnu celého pole.

class Configuration
{
    public readonly array $settings;

    public function initialize(): void
    {
        $this-&gt;settings = [&apos;debug&apos; =&gt; true];
        $this-&gt;settings[&apos;cache&apos;] = true;  // BUUM! Tohle neprojde
    }
}


Existuje však výjimka – pokud pole obsahuje reference, jejich obsah
měnit můžeme, protože PHP to nepovažuje za změnu samotného pole. Toto
chování je konzistentní s běžným fungováním PHP:

class Configuration
{
    public readonly array $settings;

    public function initialize(): void
    {
        // Trik s referencí
        $mode = &apos;development&apos;;
        $this-&gt;settings = [
            &apos;debug&apos; =&gt; true,
            &apos;mode&apos; =&gt; &amp;$mode,  // Reference je naše tajná zbraň!
        ];

        $mode = &apos;production&apos;;  // Tohle projde!
    }
}


Wither metody a readonly: Jak na
to?

Při práci s neměnnými objekty často potřebujeme implementovat metody
pro změnu stavu. Tyto „wither“ metody (na rozdíl od klasických setterů)
nemodifikují původní objekt, ale vrací jeho novou instanci s požadovanou
změnou. Tento pattern využívá například specifikace PSR-7 pro HTTP
požadavky.

Když chceme tyto objekty nebo jejich vlastnosti označit jako readonly,
narazíme na technické omezení – readonly vlastnost nelze změnit ani ve
wither metodě, a to ani v kopii objektu. I když PHP 8.3 umožňuje měnit
readonly vlastnosti v metodě __clone(), samotné klonování
nestačí, protože v něm nemáme přístup k nové hodnotě. Můžeme to ale
vyřešit pomocí následující obezličky:

class Request
{
    private array $changes = [];

    public function __construct(
        public readonly string $method = &apos;GET&apos;,
        public readonly array $headers = [],
    ) {}

    public function withMethod(string $method): self
    {
        $this-&gt;changes[&apos;method&apos;] = $method;
        $dolly = clone $this;
        $this-&gt;changes = [];
        return $dolly;
    }

    public function __clone()
    {
        foreach ($this-&gt;changes as $property =&gt; $value) {
            $this-&gt;$property = $value;
        }
        $this-&gt;changes = [];
    }
}

$request = new Request(&apos;GET&apos;);
$newRequest = $request-&gt;withMethod(&apos;POST&apos;);  // Původní $request zůstává s GET


Testování a BypassFinals

Při psaní testů můžeme narazit na praktický problém – readonly
vlastnosti (podobně jako final) komplikují mockování a testování.
Naštěstí existuje elegantní řešení v podobě knihovny BypassFinals.

Tato knihovna dokáže za běhu odstranit klíčová slova final
a readonly z vašeho kódu, což umožňuje mockovat i třídy a
metody, které jsou takto označené. Integrace s testovacími frameworky je
přímočará:

// bootstrap.php nebo začátek test souboru
DG\BypassFinals::enable();

// Pokud chceme zachovat readonly a odstranit jen final:
DG\BypassFinals::enable(bypassReadOnly: false);


Shrnutí: Co si odnést

Readonly vlastnosti jsou mocný nástroj pro zvýšení bezpečnosti a
předvídatelnosti vašeho kódu. Zapamatujte si klíčové body:


	Readonly vlastnosti můžete inicializovat kdykoliv, ale pouze jednou

	Musíte explicitně definovat datový typ

	Do PHP 8.4 byla inicializace možná pouze ve scope třídy, která je
	definuje

	Readonly nezaručuje úplnou neměnnost – zejména u vnořených
	objektů

	Od PHP 8.2 můžete označit celé třídy jako readonly

	Pro testování máte k dispozici BypassFinals

	PHP 8.4 přináší větší flexibilitu s modifikátorem public(set)

</description>
		<content:encoded><![CDATA[
		<p>Představte si, že byste mohli svým datům dát pevnou půdu pod
nohama – jednou je nastavíte a pak si můžete být jistí, že je nikdo
nezmění. Přesně to přineslo PHP 8.1 s readonly vlastnostmi. Je to jako
dát vašim objektům neprůstřelnou vestu – chrání jejich data před
nechtěnými změnami. Pojďme se podívat, jak vám tento mocný nástroj
může usnadnit život a na co si při jeho používání dát pozor.</p>

<p>Začněme jednoduchým příkladem:</p>

<pre
class="language-php"><code>class User
{
    public readonly string $name;

    public function setName(string $name): void
    {
        $this-&gt;name = $name;  // První nastavení - vše OK
    }
}

$user = new User;
$user-&gt;setName(&#039;John&#039;);      // Paráda, máme jméno
echo $user-&gt;name;            // &quot;John&quot;
$user-&gt;setName(&#039;Jane&#039;);      // BOOM! Výjimka: Cannot modify readonly property
</code></pre>

<p>Jakmile jednou jméno nastavíte, je to jako vytesané do kamene. Žádné
náhodné přepsání, žádné nechtěné změny.</p>

<h2 id="toc-kdy-je-uninitialized-opravdu-uninitialized">Kdy je uninitialized
opravdu uninitialized?</h2>

<p>Často se setkávám s mýtem, že readonly vlastnosti musí být nastaveny
v konstruktoru. Ve skutečnosti je PHP mnohem flexibilnější – můžete je
inicializovat kdykoliv během života objektu, ale pouze jednou! Před prvním
přiřazením jsou ve speciálním stavu ‚uninitialized‘, což je takový
limbo stav mezi nebytím a bytím.</p>

<p>A tady přichází zajímavý detail – readonly vlastnosti nemohou mít
výchozí hodnotu. A proč? Kdyby měly výchozí hodnotu, staly by se de facto
konstantami – hodnota by byla nastavena při vytvoření objektu a už by
nešla změnit.</p>

<h2 id="toc-vyzaduji-se-typy">Vyžadují se typy</h2>

<p>Readonly proměnné vyžadují explicitní definici datového typu. Je to
proto, že stav ‚uninitialized‘, který využívají, existuje pouze
u typovaných proměnných. Bez uvedení typu tedy readonly proměnnou nelze
definovat. Pokud si nejste jistí typem, můžete použít
<code>mixed</code>.</p>
<!--more-->
<h2 id="toc-readonly-tridy-kdyz-jeden-zamek-nestaci">Readonly třídy: Když
jeden zámek nestačí</h2>

<p>S PHP 8.2 přišla možnost posunout zabezpečení na další úroveň.
Představte si, že místo zamykání jednotlivých místností můžete
zamknout celou budovu. Tedy celou třídu:</p>

<pre
class="language-php"><code>readonly class User
{
    public string $name;     // Automaticky readonly!
    public string $email;    // Taky readonly!
}
</code></pre>

<p>Ale pozor, s velkou mocí přichází velká omezení:</p>

<ul>
	<li>Žádné vlastnosti bez typu</li>

	<li>Statické vlastnosti jsou tabu</li>

	<li>Dynamické vlastnosti? Ani s atributem
	<code>#[AllowDynamicProperties]</code></li>

	<li>A v PHP 8.2 mohla readonly třída dědit jen od readonly třídy (PHP
	8.3 už je v tomto benevolentnější)</li>
</ul>

<h2 id="toc-kdo-muze-inicializovat-a-kdy">Kdo může inicializovat a kdy?</h2>

<p>Tady je to zajímavé – podívejte se na tento kód:</p>

<pre
class="language-php"><code>$user = new User;
$user-&gt;name = &#039;John&#039;;  // BUUM! Cannot initialize readonly property from global scope
</code></pre>

<p>Překvapení? I když jde o první přiřazení, PHP řekne ne. Stejně tak
potomek třídy nemůže inicializovat readonly vlastnost svého rodiče:</p>

<pre
class="language-php"><code>class Employee extends User
{
    public function setName(string $name): void
    {
        $this-&gt;name = &#039;EMP: &#039; . $name;  // BUUM! Ani potomek nemůže!
    }
}
</code></pre>

<p>Readonly proměnnou lze tedy inicializovat výhradně ze třídy, která ji
definovala. Přesněji řečeno šlo. PHP 8.4 totiž přináší dvě
důležité změny:</p>

<ul>
	<li>Readonly vlastnosti můžou inicializovat i potomci (konečně!)</li>

	<li>S <a href="https://phpfashion.com/cs/property-hooks-v-php-8-4-revoluce-nebo-past">modifikátorem
	<code>public(set)</code></a> můžete povolit inicializaci i zvenčí:</li>
</ul>

<pre
class="language-php"><code>class User
{
    public(set) readonly string $name;  // Nová svoboda v PHP 8.4
}

$user = new User;
$user-&gt;name = &#039;John&#039;;  // Teď už to funguje!
</code></pre>

<h2 id="toc-kdyz-readonly-neznamena-opravdu-nemenne">Když readonly neznamená
„opravdu neměnné“</h2>

<p>Představte si readonly jako zámek na dveřích – zamkne dveře, ale co
se děje uvnitř místnosti, to už neuhlídá. Samotné readonly nezaručuje
úplnou neměnnost dat. Pokud do readonly proměnné uložíme objekt, jeho
vnitřní stav zůstává modifikovatelný, objekt se automaticky nestává
immutable (neměnným):</p>

<pre
class="language-php"><code>class Settings
{
	public string $theme = &#039;light&#039;;
}

class Configuration
{
	public function __construct(
		public readonly Settings $settings = new Settings,
	) {
	}
}

$config = new Configuration;
$config-&gt;settings-&gt;theme = &#039;dark&#039;; // toto je povoleno, přestože $settings je readonly!
</code></pre>

<p>Vidíte? Samotný objekt <code>$settings</code> je uzamčený, ale jeho
vnitřnosti můžeme měnit, jak se nám zlíbí.</p>

<p>U polí je situace specifická. Přímá modifikace prvků pole není
možná, protože PHP to považuje za změnu celého pole.</p>

<pre
class="language-php"><code>class Configuration
{
    public readonly array $settings;

    public function initialize(): void
    {
        $this-&gt;settings = [&#039;debug&#039; =&gt; true];
        $this-&gt;settings[&#039;cache&#039;] = true;  // BUUM! Tohle neprojde
    }
}
</code></pre>

<p>Existuje však výjimka – pokud pole obsahuje reference, jejich obsah
měnit můžeme, protože PHP to nepovažuje za změnu samotného pole. Toto
chování je konzistentní s běžným fungováním PHP:</p>

<pre
class="language-php"><code>class Configuration
{
    public readonly array $settings;

    public function initialize(): void
    {
        // Trik s referencí
        $mode = &#039;development&#039;;
        $this-&gt;settings = [
            &#039;debug&#039; =&gt; true,
            &#039;mode&#039; =&gt; &amp;$mode,  // Reference je naše tajná zbraň!
        ];

        $mode = &#039;production&#039;;  // Tohle projde!
    }
}
</code></pre>

<h2 id="toc-wither-metody-a-readonly-jak-na-to">Wither metody a readonly: Jak na
to?</h2>

<p>Při práci s neměnnými objekty často potřebujeme implementovat metody
pro změnu stavu. Tyto „wither“ metody (na rozdíl od klasických setterů)
nemodifikují původní objekt, ale vrací jeho novou instanci s požadovanou
změnou. Tento pattern využívá například specifikace PSR-7 pro HTTP
požadavky.</p>

<p>Když chceme tyto objekty nebo jejich vlastnosti označit jako readonly,
narazíme na technické omezení – readonly vlastnost nelze změnit ani ve
wither metodě, a to ani v kopii objektu. I když PHP 8.3 umožňuje měnit
readonly vlastnosti v metodě <code>__clone()</code>, samotné klonování
nestačí, protože v něm nemáme přístup k nové hodnotě. Můžeme to ale
vyřešit pomocí následující obezličky:</p>

<pre
class="language-php"><code>class Request
{
    private array $changes = [];

    public function __construct(
        public readonly string $method = &#039;GET&#039;,
        public readonly array $headers = [],
    ) {}

    public function withMethod(string $method): self
    {
        $this-&gt;changes[&#039;method&#039;] = $method;
        $dolly = clone $this;
        $this-&gt;changes = [];
        return $dolly;
    }

    public function __clone()
    {
        foreach ($this-&gt;changes as $property =&gt; $value) {
            $this-&gt;$property = $value;
        }
        $this-&gt;changes = [];
    }
}

$request = new Request(&#039;GET&#039;);
$newRequest = $request-&gt;withMethod(&#039;POST&#039;);  // Původní $request zůstává s GET
</code></pre>

<h2 id="toc-testovani-a-bypassfinals">Testování a BypassFinals</h2>

<p>Při psaní testů můžeme narazit na praktický problém – readonly
vlastnosti (podobně jako final) komplikují mockování a testování.
Naštěstí existuje elegantní řešení v podobě knihovny BypassFinals.</p>

<p>Tato knihovna dokáže za běhu odstranit klíčová slova <code>final</code>
a <code>readonly</code> z vašeho kódu, což umožňuje mockovat i třídy a
metody, které jsou takto označené. Integrace s testovacími frameworky je
přímočará:</p>

<pre
class="language-php"><code>// bootstrap.php nebo začátek test souboru
DG\BypassFinals::enable();

// Pokud chceme zachovat readonly a odstranit jen final:
DG\BypassFinals::enable(bypassReadOnly: false);
</code></pre>

<h2 id="toc-shrnuti-co-si-odnest">Shrnutí: Co si odnést</h2>

<p>Readonly vlastnosti jsou mocný nástroj pro zvýšení bezpečnosti a
předvídatelnosti vašeho kódu. Zapamatujte si klíčové body:</p>

<ul>
	<li>Readonly vlastnosti můžete inicializovat kdykoliv, ale pouze jednou</li>

	<li>Musíte explicitně definovat datový typ</li>

	<li>Do PHP 8.4 byla inicializace možná pouze ve scope třídy, která je
	definuje</li>

	<li>Readonly nezaručuje úplnou neměnnost – zejména u vnořených
	objektů</li>

	<li>Od PHP 8.2 můžete označit celé třídy jako readonly</li>

	<li>Pro testování máte k dispozici BypassFinals</li>

	<li>PHP 8.4 přináší větší flexibilitu s modifikátorem public(set)</li>
</ul>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/readonly-vlastnosti-v-php-a-jejich-skryta-uskali#comments</comments>
		<pubDate>Sun, 24 Nov 2024 17:58:00 +0100</pubDate>
		<guid isPermaLink="false">item1327@http://phpfashion.com</guid>
	</item>
	<item>
		<title>Dvě slova, co ničí open source</title>
		<link>https://phpfashion.com/cs/dve-slova-co-nici-open-source</link>
		<description>Víte, co nikdy, ale opravdu NIKDY nemáte psát autorům open source
projektů? „Nemám čas“. Tahle dvě slova mají schopnost rozpustit
motivaci vývojářů rychleji než mizí baterka na iPhonu při scrollování
TikToku.


	„Nemám čas na to napsat opravu.“

	„Nemám čas připravit ukázku s chybou.“

	„Tohle by mělo být v dokumentaci, ale nemám čas to napsat.“


Vážně? VÁŽNĚ?!

Představte si, že jste na párty a někdo vám řekne: „Hej, ty tam
s tím pivem! Udělej mi sendvič. Nemám čas si ho udělat sám, jsem
příliš zaneprázdněn konzumací chipsů.“ Jak byste se cítili? Jako
obědový automat s lidskou tváří? Přesně tak se cítím já, když čtu
taková slova. Okamžitě ztrácím chuť věc řešit a mám nutkání se jít
věnovat čemukoliv jinému. Třeba pustému nicnedělání.



Víte, my open source vývojáři jsme zvláštní stvoření. Trávíme
hodiny našeho volného času tvorbou softwaru, který pak dáváme k dispozici
všem. Zadarmo. Dobrovolně. Jako kdyby Ježíšek rozdával dárky každý den
v roce a ne jen na Vánoce. Baví nás to. Ale tím vám nevzniká nárok nás
úkolovat jako nějaké digitální otroky. Takže když někdo přijde
s požadavkem na novou funkci, ale „nemá čas“ přiložit ruku k dílu,
okamžitě tím vyvolá otázku „a proč bych já ten čas měl mít?“ Jako
byste chtěli po Michelangelovi, aby vám vymaloval obývák, protože vy
„nemáte čas“ to udělat sami, šak stejně nemá co lepšího
na práci.

Za roky se mi nashromáždily desítky issues u různých projektů, ve
kterých jsem poprosil „Mohl bys připravit pull request?“ a odpovědí bylo
„Mohl, ale tento týden nemám čas.“ Kdyby ten nebožák onu větu
nenapsal, nejspíš bych věc dávno vyřešil. Takhle mi ale řekl, že pohrdá
mým časem. Takže to vyřešil sám za týden? Kdeže… 99 % věcí,
které kdy kdo slíbil, nikdy nedodal, tudíž i 99 % těchto issues jsou
navždy nevyřešené. Visí tam jako digitální pomníky lidské lenosti.

Takže, milí uživatelé, příště než napíšete „Nemám čas“,
zamyslete se. Ve skutečnosti říkáte: „Hej, ty tam! Tvůj volný čas nemá
žádnou hodnotu. Hoď všechno co děláš za hlavu a věnuj se MÉ
záležitosti!“ Zkuste místo toho:


	Najít ten čas. Věřte mi, existuje. Možná je schovaný mezi epizodami
	vašeho oblíbeného seriálu nebo mezi scrollováním na sociálních
	sítích.

	Nabídnout řešení. Nemusíte psát rovnou patch. Stačí ukázat, že
	jste řešení problému fakt promýšleli.

	Motivovat správce open source, aby se vaším issue zabývali. Třeba tím,
	že ukážete, jak bude úprava užitečná nejen pro vás, ale i pro celé
	lidstvo a přilehlý vesmír.


Když narazíte na bug, budete chtít novou featuru, nebo zjistíte, že by
stálo za to něco doplnit do dokumentace, zkuste pro jednou prospět komunitě.
Protože v open source světě jsme všichni na jedné lodi. A ta loď pluje
na vlnách vzájemného respektu a spolupráce. Tak nezapomeňte občas také
zaveslovat, místo abyste jen seděli a stěžovali si, že nemáte čas na
pádlování. Vaše „nemám čas“ je absolutní způsob, jak zničit
motivaci lidí, kteří vám zdarma poskytují software. Zkuste si těch pár
minut nebo hodin najít. Vaše karma vám poděkuje.
</description>
		<content:encoded><![CDATA[
		<p>Víte, co nikdy, ale opravdu NIKDY nemáte psát autorům open source
projektů? „Nemám čas“. Tahle dvě slova mají schopnost rozpustit
motivaci vývojářů rychleji než mizí baterka na iPhonu při scrollování
TikToku.</p>

<ul>
	<li>„Nemám čas na to napsat opravu.“</li>

	<li>„Nemám čas připravit ukázku s chybou.“</li>

	<li>„Tohle by mělo být v dokumentaci, ale nemám čas to napsat.“</li>
</ul>

<p>Vážně? VÁŽNĚ?!</p>

<p>Představte si, že jste na párty a někdo vám řekne: „Hej, ty tam
s tím pivem! Udělej mi sendvič. Nemám čas si ho udělat sám, jsem
příliš zaneprázdněn konzumací chipsů.“ Jak byste se cítili? Jako
obědový automat s lidskou tváří? Přesně tak se cítím já, když čtu
taková slova. Okamžitě ztrácím chuť věc řešit a mám nutkání se jít
věnovat čemukoliv jinému. Třeba pustému nicnedělání.</p>

<figure><img loading="lazy" src="/media/5a629f2047.webp" alt="" width="1400"
height="800"></figure>

<p>Víte, my open source vývojáři jsme zvláštní stvoření. Trávíme
hodiny našeho volného času tvorbou softwaru, který pak dáváme k dispozici
všem. Zadarmo. Dobrovolně. Jako kdyby Ježíšek rozdával dárky každý den
v roce a ne jen na Vánoce. Baví nás to. Ale tím vám nevzniká nárok nás
úkolovat jako nějaké digitální otroky. Takže když někdo přijde
s požadavkem na novou funkci, ale „nemá čas“ přiložit ruku k dílu,
okamžitě tím vyvolá otázku „a proč bych já ten čas měl mít?“ Jako
byste chtěli po Michelangelovi, aby vám vymaloval obývák, protože vy
„nemáte čas“ to udělat sami, šak stejně nemá co lepšího
na práci.</p>

<p>Za roky se mi nashromáždily desítky issues u různých projektů, ve
kterých jsem poprosil „Mohl bys připravit pull request?“ a odpovědí bylo
„Mohl, ale tento týden nemám čas.“ Kdyby ten nebožák onu větu
nenapsal, nejspíš bych věc dávno vyřešil. Takhle mi ale řekl, že pohrdá
mým časem. Takže to vyřešil sám za týden? Kdeže… <b>99 % věcí</b>,
které kdy kdo slíbil, nikdy nedodal, tudíž i 99 % těchto issues jsou
navždy nevyřešené. Visí tam jako digitální pomníky lidské lenosti.</p>

<p>Takže, milí uživatelé, příště než napíšete „Nemám čas“,
zamyslete se. Ve skutečnosti říkáte: „Hej, ty tam! Tvůj volný čas nemá
žádnou hodnotu. Hoď všechno co děláš za hlavu a věnuj se MÉ
záležitosti!“ Zkuste místo toho:</p>

<ul>
	<li>Najít ten čas. Věřte mi, existuje. Možná je schovaný mezi epizodami
	vašeho oblíbeného seriálu nebo mezi scrollováním na sociálních
	sítích.</li>

	<li>Nabídnout řešení. Nemusíte psát rovnou patch. Stačí ukázat, že
	jste řešení problému fakt promýšleli.</li>

	<li>Motivovat správce open source, aby se vaším issue zabývali. Třeba tím,
	že ukážete, jak bude úprava užitečná nejen pro vás, ale i pro celé
	lidstvo a přilehlý vesmír.</li>
</ul>

<p>Když narazíte na bug, budete chtít novou featuru, nebo zjistíte, že by
stálo za to něco doplnit do dokumentace, zkuste pro jednou prospět komunitě.
Protože v open source světě jsme všichni na jedné lodi. A ta loď pluje
na vlnách vzájemného respektu a spolupráce. Tak nezapomeňte občas také
zaveslovat, místo abyste jen seděli a stěžovali si, že nemáte čas na
pádlování. Vaše „nemám čas“ je absolutní způsob, jak zničit
motivaci lidí, kteří vám zdarma poskytují software. Zkuste si těch pár
minut nebo hodin najít. Vaše karma vám poděkuje.</p>

		]]></content:encoded>
		<comments>https://phpfashion.com/cs/dve-slova-co-nici-open-source#comments</comments>
		<pubDate>Fri, 04 Oct 2024 22:57:00 +0200</pubDate>
		<guid isPermaLink="false">item2316@http://phpfashion.com</guid>
		<enclosure url="https://phpfashion.com/media/5a629f2047.webp" length="151824" type="image/webp" />
	</item>

		<item>
			<title>Pět novinek v Latte 3.1, které vám zpříjemní život</title>
			<link>https://blog.nette.org/cs/pet-novinek-v-latte-3-1</link>
			<description>Latte 3.1 přináší pětici novinek – nové filtry
|column, |commas a |limit, vylepšený
|slice pro iterátory a dvě nové featury enginu. Žádná
revoluce, ale drobnosti, které oceníte.

Konec úniku proměnných z
{foreach}

Tohle mě štvalo roky. Napíšete {foreach $items as $item},
cyklus skončí, a $item vám vesele přežívá s hodnotou
posledního prvku. Horší je, když si tím přepíšete proměnnou, se kterou
jste počítali:

{var $name = &apos;Jupí&apos;}

{foreach $users as $name}
    ...
{/foreach}

{$name} {* ouha, tady už není &apos;Jupí&apos; *}


Latte vás sice odedávna varovalo, když foreach přepsal proměnnou
předanou do šablony přes parametry. Ale proměnné vytvořené přímo
v šabloně, například přes {var}, se přepisovaly tiše. A to
je přesně ten zákeřný případ.

Teď to jde vyřešit nastavením:

$latte-&gt;setFeature(Latte\Feature::ScopedLoopVariables);


Proměnné z {foreach} od teď existují jen uvnitř cyklu. Po
skončení se vrátí původní hodnota. A pokud proměnná předtím
neexistovala? Zmizí úplně.

{var $name = &apos;Jupí&apos;}

{foreach $users as $name}
    ...
{/foreach}

{$name} {* vypíše &apos;Jupí&apos;, jupí! *}


A to staré varování o přepsaných parametrech? S touhle featurou se
vypíná, protože přestává dávat smysl – nic se už nepřepisuje.

Funguje to i s destrukturováním {foreach $data as [$a, $b]}
a samozřejmě i s klíčem {foreach $arr as $k =&gt; $v} – po
cyklu se uklidí vše. Vnořené cykly mají nezávislé scope.

Jediná výjimka: pokud iterujete přes referenci
{foreach $arr as &amp;$v}, scope se neuplatní – reference
přímo modifikují původní pole, takže obnovování hodnot po cyklu by je
rozbilo. Logické.

Odsazujte šablony, jak chcete

Znáte to: máte &lt;ul&gt; a uvnitř {foreach},
který generuje &lt;li&gt;. Přirozeně to odsadíte, aby šablona
byla čitelná. Jenže to odsazení se pak propíše do výstupu. Buď máte
hezký kód a ošklivý HTML, nebo naopak.

Neakceptovatelné.

S Feature::Dedent tohle dilema mizí:

$latte-&gt;setFeature(Latte\Feature::Dedent);


Latte automaticky odstraní společné odsazení uvnitř párových tagů.
Takže tohle:

&lt;ul&gt;
    {foreach $items as $item}
        &lt;li&gt;{$item}&lt;/li&gt;
    {/foreach}
&lt;/ul&gt;


vygeneruje:

&lt;ul&gt;
    &lt;li&gt;...&lt;/li&gt;
    &lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;


Odsazení jako by tam nikdy nebylo.

A protože se dedent provádí už při kompilaci šablony, ne při každém
renderování, nemá to žádný vliv na výkon. Funguje pro všechny párové
tagy – {if}, {block}, {capture},
{foreach} a další. Vnořené tagy se dedentují nezávisle,
každý na své úrovni.

Pokud je odsazení nekonzistentní – kupříkladu mícháte tabulátory
s mezerami, nebo některý řádek nemá dostatečné odsazení – Latte
vyhodí CompileException s přesným číslem řádku. Žádné
tiché polykání chyb.

Filtr
|commas

Spojit pole do řetězce odděleného čárkami – brnkačka. Ale co když
chcete před posledním prvkem napsat „a&quot; místo čárky? To je přesně ta
věc, kvůli které v šabloně začnete psát podmínky a najednou máte
čtyři řádky kódu místo jednoho. Přitom chcete říct jen „jablko,
hruška a švestka&quot;.

{[&apos;jablko&apos;, &apos;hruška&apos;, &apos;švestka&apos;]|commas}       {* jablko, hruška, švestka *}
{[&apos;jablko&apos;, &apos;hruška&apos;, &apos;švestka&apos;]|commas:&apos; a &apos;}  {* jablko, hruška a švestka *}
{[&apos;jablko&apos;, &apos;hruška&apos;, &apos;švestka&apos;]|commas:&apos;, nebo &apos;}  {* jablko, hruška, nebo švestka *}


Bez parametru spojí čárkou a mezerou. S parametrem použije zadaný
řetězec jako oddělovač mezi posledními dvěma prvky – zbytek zůstane
oddělený čárkami. Prostě čeština, ne foreach.

Filtr
|column

Máte pole záznamů o uživatelích, třeba
[[&apos;id&apos; =&gt; 1, &apos;name&apos; =&gt; &apos;Jan&apos;], [&apos;id&apos; =&gt; 2, &apos;name&apos; =&gt; &apos;Petr&apos;], ...],
a potřebujete z nich dostat jen jména? Filtr |column vytáhne
hodnoty jednoho sloupce – ať už jde o klíč v poli, nebo property
objektu:

{$users|column:&apos;name&apos;|commas}   {* Jan, Petr, Marie *}


Volitelně přijímá i druhý parametr pro indexování výsledků:

{foreach ($users|column:&apos;name&apos;:&apos;id&apos;) as $id =&gt; $name}
    {$id}: {$name}
{/foreach}


Funguje i s iterátory, nejen s poli – nicméně iterátor se interně
převede na pole, takže na žádnou lazy magii nečekejte.

Filtr
|slice pro iterátory a nový filtr |limit

Filtr |slice vyřízne kus pole nebo řetězce (u řetězců
s respektem k UTF-8). Dosud ale fungoval jen s poli a řetězci. Teď
zvládne i iterátory a generátory – vrací generátor, který čte prvky
z původního zdroje jeden po druhém a po dosažení limitu se zastaví. Celý
iterátor se do paměti nenačítá:

{foreach ($generator|slice:0:10) as $item}
    {$item}
{/foreach}


A k tomu přibyl filtr
|limit – pohodlnější varianta pro typický případ
„vezmi prvních N prvků&quot;. Funguje s poli, iterátory i řetězci
(s respektem k UTF-8):

{foreach ($items|limit:5) as $item}
    {$item}
{/foreach}

{$description|limit:100}


Rozdíl oproti |slice: filtr |limit ve výchozím
stavu zachovává originální klíče.

Jak na to

Nové featury ScopedLoopVariables a Dedent se
zapínají přes setFeature():

$latte = new Latte\Engine;
$latte-&gt;setFeature(Latte\Feature::ScopedLoopVariables);
$latte-&gt;setFeature(Latte\Feature::Dedent);


A pokud používáte Nette, stačí to zapnout v konfiguraci:

latte:
    scopedLoopVariables: true
    dedent: true


Pět drobností pro život.
</description>
			<content:encoded><![CDATA[
			
		<p class="perex">Latte 3.1 přináší pětici novinek – nové filtry
<code>|column</code>, <code>|commas</code> a <code>|limit</code>, vylepšený
<code>|slice</code> pro iterátory a dvě nové featury enginu. Žádná
revoluce, ale drobnosti, které oceníte.</p>

<h2 id="toc-konec-uniku-promennych-z-foreach">Konec úniku proměnných z
<code>{foreach}</code></h2>

<p>Tohle mě štvalo roky. Napíšete <code>{foreach $items as $item}</code>,
cyklus skončí, a <code>$item</code> vám vesele přežívá s hodnotou
posledního prvku. Horší je, když si tím přepíšete proměnnou, se kterou
jste počítali:</p>

<pre
class="language-latte"><code>{var $name = &#039;Jupí&#039;}

{foreach $users as $name}
    ...
{/foreach}

{$name} {* ouha, tady už není &#039;Jupí&#039; *}
</code></pre>

<p>Latte vás sice odedávna varovalo, když foreach přepsal proměnnou
předanou do šablony přes parametry. Ale proměnné vytvořené přímo
v šabloně, například přes <code>{var}</code>, se přepisovaly tiše. A to
je přesně ten zákeřný případ.</p>

<p>Teď to jde vyřešit nastavením:</p>

<pre
class="language-php"><code>$latte-&gt;setFeature(Latte\Feature::ScopedLoopVariables);
</code></pre>

<p>Proměnné z <code>{foreach}</code> od teď existují jen uvnitř cyklu. Po
skončení se vrátí původní hodnota. A pokud proměnná předtím
neexistovala? Zmizí úplně.</p>

<pre
class="language-latte"><code>{var $name = &#039;Jupí&#039;}

{foreach $users as $name}
    ...
{/foreach}

{$name} {* vypíše &#039;Jupí&#039;, jupí! *}
</code></pre>

<p>A to staré varování o přepsaných parametrech? S touhle featurou se
vypíná, protože přestává dávat smysl – nic se už nepřepisuje.</p>

<p>Funguje to i s destrukturováním <code>{foreach $data as [$a, $b]}</code>
a samozřejmě i s klíčem <code>{foreach $arr as $k =&gt; $v}</code> – po
cyklu se uklidí vše. Vnořené cykly mají nezávislé scope.</p>

<p>Jediná výjimka: pokud iterujete přes referenci
<code>{foreach $arr as &amp;$v}</code>, scope se neuplatní – reference
přímo modifikují původní pole, takže obnovování hodnot po cyklu by je
rozbilo. Logické.</p>

<h2 id="toc-odsazujte-sablony-jak-chcete">Odsazujte šablony, jak chcete</h2>

<p>Znáte to: máte <code>&lt;ul&gt;</code> a uvnitř <code>{foreach}</code>,
který generuje <code>&lt;li&gt;</code>. Přirozeně to odsadíte, aby šablona
byla čitelná. Jenže to odsazení se pak propíše do výstupu. Buď máte
hezký kód a ošklivý HTML, nebo naopak.</p>

<p>Neakceptovatelné.</p>

<p>S <code>Feature::Dedent</code> tohle dilema mizí:</p>

<pre
class="language-php"><code>$latte-&gt;setFeature(Latte\Feature::Dedent);
</code></pre>

<p>Latte automaticky odstraní společné odsazení uvnitř párových tagů.
Takže tohle:</p>

<pre
class="language-latte"><code>&lt;ul&gt;
    {foreach $items as $item}
        &lt;li&gt;{$item}&lt;/li&gt;
    {/foreach}
&lt;/ul&gt;
</code></pre>

<p>vygeneruje:</p>

<pre
class="language-latte"><code>&lt;ul&gt;
    &lt;li&gt;...&lt;/li&gt;
    &lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
</code></pre>

<p>Odsazení jako by tam nikdy nebylo.</p>

<p>A protože se dedent provádí už při kompilaci šablony, ne při každém
renderování, nemá to žádný vliv na výkon. Funguje pro všechny párové
tagy – <code>{if}</code>, <code>{block}</code>, <code>{capture}</code>,
<code>{foreach}</code> a další. Vnořené tagy se dedentují nezávisle,
každý na své úrovni.</p>

<p>Pokud je odsazení nekonzistentní – kupříkladu mícháte tabulátory
s mezerami, nebo některý řádek nemá dostatečné odsazení – Latte
vyhodí <code>CompileException</code> s přesným číslem řádku. Žádné
tiché polykání chyb.</p>

<h2 id="toc-filtr-commas"><a
href="https://latte.nette.org/cs/filters#toc-commas">Filtr
<code>|commas</code></a></h2>

<p>Spojit pole do řetězce odděleného čárkami – brnkačka. Ale co když
chcete před posledním prvkem napsat „a" místo čárky? To je přesně ta
věc, kvůli které v šabloně začnete psát podmínky a najednou máte
čtyři řádky kódu místo jednoho. Přitom chcete říct jen „jablko,
hruška a švestka".</p>

<pre
class="language-latte"><code>{[&#039;jablko&#039;, &#039;hruška&#039;, &#039;švestka&#039;]|commas}       {* jablko, hruška, švestka *}
{[&#039;jablko&#039;, &#039;hruška&#039;, &#039;švestka&#039;]|commas:&#039; a &#039;}  {* jablko, hruška a švestka *}
{[&#039;jablko&#039;, &#039;hruška&#039;, &#039;švestka&#039;]|commas:&#039;, nebo &#039;}  {* jablko, hruška, nebo švestka *}
</code></pre>

<p>Bez parametru spojí čárkou a mezerou. S parametrem použije zadaný
řetězec jako oddělovač mezi posledními dvěma prvky – zbytek zůstane
oddělený čárkami. Prostě čeština, ne foreach.</p>

<h2 id="toc-filtr-column"><a
href="https://latte.nette.org/cs/filters#toc-column">Filtr
<code>|column</code></a></h2>

<p>Máte pole záznamů o uživatelích, třeba
<code>[['id' =&gt; 1, 'name' =&gt; 'Jan'], ['id' =&gt; 2, 'name' =&gt; 'Petr'], ...]</code>,
a potřebujete z nich dostat jen jména? Filtr <code>|column</code> vytáhne
hodnoty jednoho sloupce – ať už jde o klíč v poli, nebo property
objektu:</p>

<pre
class="language-latte"><code>{$users|column:&#039;name&#039;|commas}   {* Jan, Petr, Marie *}
</code></pre>

<p>Volitelně přijímá i druhý parametr pro indexování výsledků:</p>

<pre
class="language-latte"><code>{foreach ($users|column:&#039;name&#039;:&#039;id&#039;) as $id =&gt; $name}
    {$id}: {$name}
{/foreach}
</code></pre>

<p>Funguje i s iterátory, nejen s poli – nicméně iterátor se interně
převede na pole, takže na žádnou lazy magii nečekejte.</p>

<h2 id="toc-filtr-slice-pro-iteratory-a-novy-filtr-limit"><a
href="https://latte.nette.org/cs/filters#toc-slice">Filtr
<code>|slice</code></a> pro iterátory a nový <a
href="https://latte.nette.org/cs/filters#toc-limit">filtr <code>|limit</code></a></h2>

<p>Filtr <code>|slice</code> vyřízne kus pole nebo řetězce (u řetězců
s respektem k UTF-8). Dosud ale fungoval jen s poli a řetězci. Teď
zvládne i iterátory a generátory – vrací generátor, který čte prvky
z původního zdroje jeden po druhém a po dosažení limitu se zastaví. Celý
iterátor se do paměti nenačítá:</p>

<pre
class="language-latte"><code>{foreach ($generator|slice:0:10) as $item}
    {$item}
{/foreach}
</code></pre>

<p>A k tomu přibyl <a
href="https://latte.nette.org/cs/filters#toc-limit">filtr
<code>|limit</code></a> – pohodlnější varianta pro typický případ
„vezmi prvních N prvků". Funguje s poli, iterátory i řetězci
(s respektem k UTF-8):</p>

<pre
class="language-latte"><code>{foreach ($items|limit:5) as $item}
    {$item}
{/foreach}

{$description|limit:100}
</code></pre>

<p>Rozdíl oproti <code>|slice</code>: filtr <code>|limit</code> ve výchozím
stavu zachovává originální klíče.</p>

<h2 id="toc-jak-na-to">Jak na to</h2>

<p>Nové featury <code>ScopedLoopVariables</code> a <code>Dedent</code> se
zapínají přes <code>setFeature()</code>:</p>

<pre
class="language-php"><code>$latte = new Latte\Engine;
$latte-&gt;setFeature(Latte\Feature::ScopedLoopVariables);
$latte-&gt;setFeature(Latte\Feature::Dedent);
</code></pre>

<p>A pokud používáte Nette, stačí to zapnout v konfiguraci:</p>

<pre
class="language-neon"><code>latte:
    scopedLoopVariables: true
    dedent: true
</code></pre>

<p>Pět drobností pro život.</p>

		
			]]></content:encoded>
			<pubDate>Tue, 24 Mar 2026 17:36:00 +0100</pubDate>
			<guid isPermaLink="false">item533@http://blog.nette.org</guid>
		</item>
		<item>
			<title>Případ ucpané roury</title>
			<link>https://blog.nette.org/cs/pripad-ucpane-roury</link>
			<description>„Tenhle job vás naučí, že každý operační systém má svoje
kostlivce ve skříni. Linux? Ten se tváří upřímně — open source, jak
se tomu říká. Ale Windows? Windows je ta záhadná kráska v koutě baru.
Usmívá se, říká, že je všechno v pořádku, a přitom jí ve sklepě
hnijou mrtvoly.“

Kapitola 1: Dokonalý zločin

Ten případ mě pronásleduje čtrnáct let.

Začalo to v roce 2012. Nette Tester
právě ovládl tajemství vícevláknového testování. Osm vláken
v dokonalé souhře by default, jako dirigovaný orchestr. Byl to
průkopník — v době, kdy ostatní knihovny pouštěly testy jeden po
druhém jako důchodce na poště, Tester už dávno paralelizoval. Šedesát
sekund testů stlačených do deseti. Nádhera. Elegance.

Jenže od prvního dne tu byl stín.

První zprávy přišly od lidí na Windows. Testy, které na Linuxu
prosvištěly jak nic, se na Windows vlekly jak šnek po žiletce.

Linux: 3 sekundy. Windows: 21,6 sekundy.

Někdo tu vraždil paralelismus. A dělal to za bílého dne.

Kapitola 2: Podezřelí

Otevřel jsem složku a ponořil se do případu.

Když Tester spustí test, vytvoří nový PHP proces. Tenhle proces běží
nezávisle a píše svoje výsledky na standardní výstup — stdout. Jenže
jak se k těm výsledkům dostane hlavní program? Přes rouru. Pipe.
Neviditelnou trubku mezi procesy, kterou tečou data jako voda.

Na Linuxu je tahle roura chytrá. Když v ní nic není a řeknete „dej mi
data“, ona odpoví „nic tu nemám“ a jdete dál. Žádné čekání.
Žádné zdržování.

Jenže na Windows?

Zkoušel jsem všechno. První podezřelá byla funkce
stream_set_blocking() — ta měla rouru přepnout do
neblokujícího režimu.

Nefungovala.

Další na řadě byl stream_select() — měla sledovat více
rour najednou a říct, která má data.

Taky mrtvá.

Nakonec jsem zkusil obyčejný fread(). Na Linuxu, když čtete
z prázdné roury, vrátí nic okamžitě. Na Windows? Čeká. Čeká
dokud něco nepřijde. Klidně věčnost.

Tři funkce. Tři mrtvoly. Někdo sprovodil neblokující I/O ze světa už
před lety. Zločin tak starý, že křídová silueta dávno vybledla.

Kapitola 3: Blokáda

Představte si tohle:

Osm vláken přijde do hospody. První si objedná — přežene to
s panáky a dá sleep(3). Nic divného. I test si občas
potřebuje zdřímnout.

Jenže pak to začne být zajímavé.

Chcete přečíst výstup toho prvního testu. Sáhnete do roury. Žádný
výstup ale není — test spí a nic nevypisuje. A na Windows, když čtete
z prázdné roury…

Čekáte.

Čekáte, dokud se test nevzbudí. Zamrzli jste uprostřed pohybu s rukou
v rouře. Ostatní vlákna na vás upírají zrak. „Haló? Jsi v pohodě?“
Ale vy jste začali číst a nemůžete přestat. Sedm vláken do vás
šťouchá: „Hele, můžem jet? Máme testy k pouštění!“ Jenže vy
nemůžete nic. Uvnitř se proklínáte — kdybyste sáhli po jiném testu,
mohli jste se teď v baru družit s ostatními, pracovat, být užiteční.
Ale ne. Vybrali jste si toho spáče a teď jste jeho rukojmí.

Tři sekundy ticha. Pak se test vzbudí, vyplivne výstup a vy konečně
můžete pustit ruku.

Osm vláken. Jedno se zaseklo. Sedm přešlapovalo a čekalo.

Dvacet jedna celá šest sekundy místo tří.

Tohle nebyla vražda. Tohle bylo mučení.

Kapitola 4: Soubory

Mým úkolem je problémy řešit.

Každý detektiv zažije ten moment. Záblesk geniality. Pocit, že to
konečně rozlouskl. Můj přišel ve dvě v noci nad vychladlou kávou a
zmrazeným kódem.

„Soubory,“ hlesl jsem. „Zapíšeme to do souborů.“

Roura je jako telefonní linka — musíte poslouchat, když někdo mluví,
jinak to neslyšíte. Ale soubor? Soubor je jako záznamník. Test do něj
napíše, co potřebuje, a vy si to přečtete, až budete mít čas. Žádné
čekání. Žádné zamrzání.

proc_open($cmd, [
    [&apos;pipe&apos;, &apos;r&apos;],
    [&apos;file&apos;, $tempFile, &apos;w&apos;],  // Tady je to!
    ...
]);


Žádné roury. Žádné záseky. Test píše do souboru, my si ho
přečteme, až doběhne. Čisté. Prosté. Geniální.

27 sekund.

Dvacet. Sedm. Sekund.

Proměřil jsem všechno:


	proc_open(): 2 ms. Nevinný.

	proc_get_status(): 4 ms na tisíc volání. Taky čistý.

	Čtení a mazání souboru: Pod 10 ms. Nic podezřelého.


A přesto byl výsledek katastrofa.

Zíral jsem na ta čísla, až mi slzely oči. Jednotlivé operace
v pohodě. Celek na huntě. Jak je to možné?

Pak mi to došlo. Osmdesát pět testů. Osmdesát pět dočasných souborů.
Osmdesát pět vytvoření, zápisů, čtení a smazání. Každá operace
rychlá, ale Windows filesystém pracuje jinak než Linux. NTFS žurnálování.
Antivir, co každý nový soubor ohmatává jako nervózní celník na
hranicích. A ty milisekundy se sčítají.

Smrt po kapkách. Pomalá, ale jistá.

Kapitola 5: Sázka

Docházely mi nápady. A kafe taky.

Pak mě napadlo něco. Něco nebezpečného. Typ nápadu, po kterém detektiv
většinou nepřežije třetí dějství.

Co kdybychom prostě nečetli výstup hned?

Myšlenka byla prostá: test běží, my ho necháme být. Nesaháme na
rouru, nečekáme, neblokujeme. Až test doběhne — až opustí
bar — teprve pak si přečteme, co vypsal. Do té doby se staráme
o ostatní vlákna. Pracujeme. Žijeme.

if ($status[&apos;running&apos;]) {
    if (PHP_OS_FAMILY !== &apos;Windows&apos;) {
        // Na Linuxu čteme průběžně, tam to funguje
        $this-&gt;test-&gt;stdout .= stream_get_contents($this-&gt;stdout);
    }
    // Na Windows? Nečteme. Nešaháme. Nedýcháme směrem k rouře.
    return true;
}


Implementoval jsem to. Zatajil dech. Spustil testy.

3 sekundy.

Tři. Sekundy.

Jako na Linuxu. Jako za starých dobrých časů. Chtělo se mi brečet
štěstím. Tančit. Vyběhnout na střechu a—

Zazvonil telefon.

„Máme problém,“ ozval se hlas. „Některé testy prostě nedoběhnou.
Visí tam jak prádlo.“

Zastavilo se mi srdce.

„Které?“

„Ty, co hodně vypisují. Spousta echo. Píšou a píšou, a pak — nic.
Ticho. Navždy.“

Zavřel jsem oči. Jasně. Jak jsem na to mohl zapomenout.

Buffer roury. Čtyři kilobajty na Windows. Roura není bezedná — je to
trubka s omezenou kapacitou. Když test píše a píše a nikdo nečte, buffer
se naplní. A když je buffer plný, test zamrzne uprostřed zápisu. Čeká,
až někdo uvolní místo. Jenže my nečteme. My čekáme, až test doběhne.
On čeká na nás.

Deadlock.

Klasická past. Dva lidé, co na sebe vzájemně čekají u dveří: „Po
vás.“ — „Ne, po vás.“ — Navěky.

Vyměnili jsme vraha za vraha.

Kapitola 6: Poslední pokusy

Dalších čtyřicet osm hodin bylo ve znamení kofeinu a čím dál
šílenějších nápadů.

Timeout:

stream_set_timeout($this-&gt;stdout, 0, 1000); // 1ms timeout
$this-&gt;test-&gt;stdout .= fread($this-&gt;stdout, 8192);


Výsledek: 18,5 sekundy. Lepší. Ale pořád se to zasekává. Timeout byl
jenom slušná prosba, se kterou si Windows vytřel.

Kontrola metadat:

$meta = stream_get_meta_data($this-&gt;stdout);
if (!empty($meta[&apos;unread_bytes&apos;])) {
    // Číst, jen když něco je!
}


Výsledek: unread_bytes byla vždycky nula. Vždycky. I když
tam data byla. I když tam byly megabajty. Windows nám lhal přímo
do očí.

Praštil jsem do stolu.

Každá stopa vedla do zdi. Každý důkaz se rozpadl v rukou. Každé
řešení jeden problém vyřešilo a druhý vytvořilo. Jako bych hrál šachy
se soupeřem, který po každém mém tahu překreslí šachovnici.

Kapitola 7: Pravda

Na detektivní škole vám tohle neřeknou:

Někdy vraha nedopadnete.

Někdy je vrah samotný systém. Zakódovaný v základech. Rozhodnutí,
které někdo udělal v Redmondu před dvaceti lety. Člověk, který nikdy
nepočítal s tím, že PHP procesy budou chtít komunikovat bez čekání.

Windows prostě nepodporuje neblokující I/O na anonymních rourách.
Tečka. Hotovo. Šlus.

Ale pak jsem našel informátora. Typ, co se pohyboval v šedé zóně
dokumentace.

Nedokumentovaný socket descriptor. [&apos;socket&apos;] místo
[&apos;pipe&apos;, &apos;w&apos;]. PHP umí vytvořit TCP socket pár místo roury.
A na sockety stream_select() funguje. I na Windows.

proc_open($cmd, [
    [&apos;pipe&apos;, &apos;r&apos;],
    [&apos;socket&apos;],  // Tajná zbraň!
    [&apos;socket&apos;],
]);


Spustil jsem testy. Zatajil dech.

Tři sekundy.

Tři sekundy na Windows. Jako na Linuxu. Chtěl jsem křičet. Všech osm
vláken pracovalo současně. Žádné blokování. Žádné čekání.
Konečně jsem cítil ten „linuxový pocit“ i na Windows.

Jenže pak jsem si všiml něčeho divného.

Některé testy hlásily prázdný výstup. Tam, kde měly být stovky
řádků, zela prázdnota. Jako by svědek zapomněl půlku výpovědi.

Spustil jsem testy znovu. A znovu. Ze sta běhů sedmdesát v pořádku.
Třicet se ztraceným výstupem. Žádný vzorec. Žádná logika. Čistá
náhoda.

Našel jsem odpověď na php.net. Komentář číslo
128252.


	„Passing a socket handle for stdout/stderr on Windows causes the last
	chunk(s) of output to occasionally get lost…“


A pak ta věta, co mě dorazila:


	„This is actually a known bug in Windows itself and Microsoft&apos;s response
	was that CreateProcess() only officially supports anonymous pipes and file
	handles… other handle types will produce ‚undefined behavior.‘“


Undefined behavior. Policejní žargon pro „není to naše
jurisdikce.“

Socket byl svědek s děravou pamětí. Rychlý, ochotný, ale občas
prostě zapomněl, co viděl. A já nemohl riskovat, že třicet procent
důkazů zmizí jako slzy v dešti.

Zavřel jsem tu stopu. Další slepá ulička.

Epilog: Hořký konec

Zapálil jsem si cigaretu, kterou nekouřím, a díval se z okna na déšť,
který nepršel.

Složka leží na stole. Nevyřešená. Současná implementace — nečíst
během běhu — je rychlá jako blesk, ale v kapse nosí bombu. Každý test,
který se opováží vypsat víc než 4 KB, zamrzne navěky. Uvízne
v digitálním limbu vlastní výroby.

Zavřel jsem složku.

Ne každý případ má šťastný konec. Ne každý vrah skončí za
mřížemi. A některé operační systémy prostě jsou, jaké
jsou — bez ohledu na to, kolik nocí strávíte na Stack Overflow nebo
kolik inženýrů z Microsoftu proklejete.

Paralelismus na Windows je mrtvý. Ať žije paralelismus.

Případ uzavřen. Vrah je pořád venku. Dobrou noc.




	Viděl jsem věci, kterým byste nevěřili. Hořící lodě u Orionu.
	Sledoval jsem, jak se třpytí C-paprsky v temnotě u brány Tannhäuser.
	A viděl jsem, jak se PHP snaží o neblokující I/O na windowsových
	rourách. Všechny tyhle vzpomínky se ztratí. Jako slzy v dešti.

	— Roy Batty, kdyby dělal v PHP




Aktualizace: Leden 2026

Zazvonil telefon.

Složku jsem už dávno založil do archivu. Nevyřešené případy. Občas
se na ni podívám, opráším, zavrtím hlavou. Některé zločiny prostě
zůstanou nepotrestány.

„Máme novinky,“ ozval se hlas. „PHP 8.5. Podívej se na
stream_select().“

Nevěřil jsem vlastním očím. Někdo v PHP core týmu — nějaký
Christoph z Německa — opravil dvacet let starý
problém.

PeekNamedPipe(). Tak jednoduchý koncept. Než sáhnete do
roury, nejdřív nahlédnete. Je tam něco? Ano — čtěte. Ne — jděte
dál. Žádné čekání. Žádné blokování.

// PHP 8.5+: stream_select() konečně funguje i na Windows pipes
while (stream_select($read, $w, $e, 0, 0) &gt; 0) {
    $output .= fread($pipe, 8192);
}


Spustil jsem testy.

Tři sekundy. Žádné blokování. Žádný deadlock. Všechna data
na místě.

Vytáhl jsem složku z archivu. Na obálku jsem napsal: VYŘEŠENO.
Leden 2026.

Dvacet let. Dvacet let ten bug čekal v základech PHP, jako časovaná
bomba, kterou nikdo neuměl zneškodnit. A pak přišel někdo, kdo prostě
věděl, kam sáhnout.

Ne každý případ končí ve slepé uličce. Někdy to chce jen čas a
správného člověka. A pak i mrtvoly promluví.

Případ uzavřen. Tentokrát doopravdy.
</description>
			<content:encoded><![CDATA[
			
		<p>„Tenhle job vás naučí, že každý operační systém má svoje
kostlivce ve skříni. Linux? Ten se tváří upřímně — open source, jak
se tomu říká. Ale Windows? Windows je ta záhadná kráska v koutě baru.
Usmívá se, říká, že je všechno v pořádku, a přitom jí ve sklepě
hnijou mrtvoly.“</p>

<h2 id="toc-kapitola-1-dokonaly-zlocin">Kapitola 1: Dokonalý zločin</h2>

<p>Ten případ mě pronásleduje čtrnáct let.</p>

<p>Začalo to v roce 2012. <a href="https://tester.nette.org">Nette Tester</a>
právě ovládl tajemství vícevláknového testování. Osm vláken
v dokonalé souhře <em>by default</em>, jako dirigovaný orchestr. Byl to
průkopník — v době, kdy ostatní knihovny pouštěly testy jeden po
druhém jako důchodce na poště, Tester už dávno paralelizoval. Šedesát
sekund testů stlačených do deseti. Nádhera. Elegance.</p>

<p>Jenže od prvního dne tu byl stín.</p>

<p>První zprávy přišly od lidí na Windows. Testy, které na Linuxu
prosvištěly jak nic, se na Windows vlekly jak šnek po žiletce.</p>

<p><em>Linux: 3 sekundy. Windows: 21,6 sekundy.</em></p>

<p>Někdo tu vraždil paralelismus. A dělal to za bílého dne.</p>

<h2 id="toc-kapitola-2-podezreli">Kapitola 2: Podezřelí</h2>

<p>Otevřel jsem složku a ponořil se do případu.</p>

<p>Když Tester spustí test, vytvoří nový PHP proces. Tenhle proces běží
nezávisle a píše svoje výsledky na standardní výstup — stdout. Jenže
jak se k těm výsledkům dostane hlavní program? Přes <i>rouru</i>. Pipe.
Neviditelnou trubku mezi procesy, kterou tečou data jako voda.</p>

<p>Na Linuxu je tahle roura chytrá. Když v ní nic není a řeknete „dej mi
data“, ona odpoví „nic tu nemám“ a jdete dál. Žádné čekání.
Žádné zdržování.</p>

<p>Jenže na Windows?</p>

<p>Zkoušel jsem všechno. První podezřelá byla funkce
<code>stream_set_blocking()</code> — ta měla rouru přepnout do
neblokujícího režimu.</p>

<p>Nefungovala.</p>

<p>Další na řadě byl <code>stream_select()</code> — měla sledovat více
rour najednou a říct, která má data.</p>

<p>Taky mrtvá.</p>

<p>Nakonec jsem zkusil obyčejný <code>fread()</code>. Na Linuxu, když čtete
z prázdné roury, vrátí <i>nic</i> okamžitě. Na Windows? Čeká. Čeká
dokud něco nepřijde. Klidně věčnost.</p>

<p>Tři funkce. Tři mrtvoly. Někdo sprovodil neblokující I/O ze světa už
před lety. Zločin tak starý, že křídová silueta dávno vybledla.</p>

<h2 id="toc-kapitola-3-blokada">Kapitola 3: Blokáda</h2>

<p>Představte si tohle:</p>

<p>Osm vláken přijde do hospody. První si objedná — přežene to
s panáky a dá <code>sleep(3)</code>. Nic divného. I test si občas
potřebuje zdřímnout.</p>

<p>Jenže pak to začne být zajímavé.</p>

<p>Chcete přečíst výstup toho prvního testu. Sáhnete do roury. Žádný
výstup ale není — test spí a nic nevypisuje. A na Windows, když čtete
z prázdné roury…</p>

<p><i>Čekáte.</i></p>

<p>Čekáte, dokud se test nevzbudí. Zamrzli jste uprostřed pohybu s rukou
v rouře. Ostatní vlákna na vás upírají zrak. „Haló? Jsi v pohodě?“
Ale vy jste začali číst a nemůžete přestat. Sedm vláken do vás
šťouchá: „Hele, můžem jet? Máme testy k pouštění!“ Jenže vy
nemůžete nic. Uvnitř se proklínáte — kdybyste sáhli po jiném testu,
mohli jste se teď v baru družit s ostatními, pracovat, být užiteční.
Ale ne. Vybrali jste si toho spáče a teď jste jeho rukojmí.</p>

<p>Tři sekundy ticha. Pak se test vzbudí, vyplivne výstup a vy konečně
můžete pustit ruku.</p>

<p>Osm vláken. Jedno se zaseklo. Sedm přešlapovalo a čekalo.</p>

<p>Dvacet jedna celá šest sekundy místo tří.</p>

<p><em>Tohle nebyla vražda. Tohle bylo mučení.</em></p>

<h2 id="toc-kapitola-4-soubory">Kapitola 4: Soubory</h2>

<p>Mým úkolem je problémy řešit.</p>

<p>Každý detektiv zažije ten moment. Záblesk geniality. Pocit, že to
konečně rozlouskl. Můj přišel ve dvě v noci nad vychladlou kávou a
zmrazeným kódem.</p>

<p>„Soubory,“ hlesl jsem. „Zapíšeme to do souborů.“</p>

<p>Roura je jako telefonní linka — musíte poslouchat, když někdo mluví,
jinak to neslyšíte. Ale soubor? Soubor je jako záznamník. Test do něj
napíše, co potřebuje, a vy si to přečtete, až budete mít čas. Žádné
čekání. Žádné zamrzání.</p>

<pre
class="language-php"><code>proc_open($cmd, [
    [&#039;pipe&#039;, &#039;r&#039;],
    [&#039;file&#039;, $tempFile, &#039;w&#039;],  // Tady je to!
    ...
]);
</code></pre>

<p>Žádné roury. Žádné záseky. Test píše do souboru, my si ho
přečteme, až doběhne. Čisté. Prosté. Geniální.</p>

<p><em>27 sekund.</em></p>

<p>Dvacet. Sedm. Sekund.</p>

<p>Proměřil jsem všechno:</p>

<ul>
	<li><code>proc_open()</code>: 2 ms. Nevinný.</li>

	<li><code>proc_get_status()</code>: 4 ms na tisíc volání. Taky čistý.</li>

	<li>Čtení a mazání souboru: Pod 10 ms. Nic podezřelého.</li>
</ul>

<p>A přesto byl výsledek katastrofa.</p>

<p>Zíral jsem na ta čísla, až mi slzely oči. Jednotlivé operace
v pohodě. Celek na huntě. Jak je to možné?</p>

<p>Pak mi to došlo. Osmdesát pět testů. Osmdesát pět dočasných souborů.
Osmdesát pět vytvoření, zápisů, čtení a smazání. Každá operace
rychlá, ale Windows filesystém pracuje jinak než Linux. NTFS žurnálování.
Antivir, co každý nový soubor ohmatává jako nervózní celník na
hranicích. A ty milisekundy se sčítají.</p>

<p>Smrt po kapkách. Pomalá, ale jistá.</p>

<h2 id="toc-kapitola-5-sazka">Kapitola 5: Sázka</h2>

<p>Docházely mi nápady. A kafe taky.</p>

<p>Pak mě napadlo něco. Něco nebezpečného. Typ nápadu, po kterém detektiv
většinou nepřežije třetí dějství.</p>

<p><em>Co kdybychom prostě nečetli výstup hned?</em></p>

<p>Myšlenka byla prostá: test běží, my ho necháme být. Nesaháme na
rouru, nečekáme, neblokujeme. Až test doběhne — až <em>opustí
bar</em> — teprve pak si přečteme, co vypsal. Do té doby se staráme
o ostatní vlákna. Pracujeme. Žijeme.</p>

<pre
class="language-php"><code>if ($status[&#039;running&#039;]) {
    if (PHP_OS_FAMILY !== &#039;Windows&#039;) {
        // Na Linuxu čteme průběžně, tam to funguje
        $this-&gt;test-&gt;stdout .= stream_get_contents($this-&gt;stdout);
    }
    // Na Windows? Nečteme. Nešaháme. Nedýcháme směrem k rouře.
    return true;
}
</code></pre>

<p>Implementoval jsem to. Zatajil dech. Spustil testy.</p>

<p><b>3 sekundy.</b></p>

<p><em>Tři. Sekundy.</em></p>

<p>Jako na Linuxu. Jako za starých dobrých časů. Chtělo se mi brečet
štěstím. Tančit. Vyběhnout na střechu a—</p>

<p>Zazvonil telefon.</p>

<p>„Máme problém,“ ozval se hlas. „Některé testy prostě nedoběhnou.
Visí tam jak prádlo.“</p>

<p>Zastavilo se mi srdce.</p>

<p>„Které?“</p>

<p>„Ty, co hodně vypisují. Spousta echo. Píšou a píšou, a pak — nic.
Ticho. Navždy.“</p>

<p>Zavřel jsem oči. Jasně. Jak jsem na to mohl zapomenout.</p>

<p>Buffer roury. Čtyři kilobajty na Windows. Roura není bezedná — je to
trubka s omezenou kapacitou. Když test píše a píše a nikdo nečte, buffer
se naplní. A když je buffer plný, test zamrzne uprostřed zápisu. Čeká,
až někdo uvolní místo. Jenže my nečteme. My čekáme, až test doběhne.
On čeká na nás.</p>

<p><i>Deadlock.</i></p>

<p>Klasická past. Dva lidé, co na sebe vzájemně čekají u dveří: „Po
vás.“ — „Ne, po vás.“ — Navěky.</p>

<p>Vyměnili jsme vraha za vraha.</p>

<h2 id="toc-kapitola-6-posledni-pokusy">Kapitola 6: Poslední pokusy</h2>

<p>Dalších čtyřicet osm hodin bylo ve znamení kofeinu a čím dál
šílenějších nápadů.</p>

<p><b>Timeout:</b></p>

<pre
class="language-php"><code>stream_set_timeout($this-&gt;stdout, 0, 1000); // 1ms timeout
$this-&gt;test-&gt;stdout .= fread($this-&gt;stdout, 8192);
</code></pre>

<p>Výsledek: 18,5 sekundy. Lepší. Ale pořád se to zasekává. Timeout byl
jenom slušná prosba, se kterou si Windows vytřel.</p>

<p><b>Kontrola metadat:</b></p>

<pre
class="language-php"><code>$meta = stream_get_meta_data($this-&gt;stdout);
if (!empty($meta[&#039;unread_bytes&#039;])) {
    // Číst, jen když něco je!
}
</code></pre>

<p>Výsledek: <code>unread_bytes</code> byla vždycky nula. Vždycky. I když
tam data byla. I když tam byly megabajty. Windows nám lhal přímo
do očí.</p>

<p>Praštil jsem do stolu.</p>

<p>Každá stopa vedla do zdi. Každý důkaz se rozpadl v rukou. Každé
řešení jeden problém vyřešilo a druhý vytvořilo. Jako bych hrál šachy
se soupeřem, který po každém mém tahu překreslí šachovnici.</p>

<h2 id="toc-kapitola-7-pravda">Kapitola 7: Pravda</h2>

<p>Na detektivní škole vám tohle neřeknou:</p>

<p>Někdy vraha nedopadnete.</p>

<p>Někdy je vrah samotný systém. Zakódovaný v základech. Rozhodnutí,
které někdo udělal v Redmondu před dvaceti lety. Člověk, který nikdy
nepočítal s tím, že PHP procesy budou chtít komunikovat bez čekání.</p>

<p>Windows prostě nepodporuje neblokující I/O na anonymních rourách.
Tečka. Hotovo. Šlus.</p>

<p>Ale pak jsem našel informátora. Typ, co se pohyboval v šedé zóně
dokumentace.</p>

<p>Nedokumentovaný socket descriptor. <code>['socket']</code> místo
<code>['pipe', 'w']</code>. PHP umí vytvořit TCP socket pár místo roury.
A na sockety <code>stream_select()</code> funguje. I na Windows.</p>

<pre
class="language-php"><code>proc_open($cmd, [
    [&#039;pipe&#039;, &#039;r&#039;],
    [&#039;socket&#039;],  // Tajná zbraň!
    [&#039;socket&#039;],
]);
</code></pre>

<p>Spustil jsem testy. Zatajil dech.</p>

<p><em>Tři sekundy.</em></p>

<p>Tři sekundy na Windows. Jako na Linuxu. Chtěl jsem křičet. Všech osm
vláken pracovalo současně. Žádné blokování. Žádné čekání.
Konečně jsem cítil ten „linuxový pocit“ i na Windows.</p>

<p>Jenže pak jsem si všiml něčeho divného.</p>

<p>Některé testy hlásily prázdný výstup. Tam, kde měly být stovky
řádků, zela prázdnota. Jako by svědek zapomněl půlku výpovědi.</p>

<p>Spustil jsem testy znovu. A znovu. Ze sta běhů sedmdesát v pořádku.
Třicet se ztraceným výstupem. Žádný vzorec. Žádná logika. Čistá
náhoda.</p>

<p>Našel jsem odpověď na php.net. Komentář <a
href="https://www.php.net/manual/en/function.proc-open.php#128252">číslo
128252</a>.</p>

<blockquote>
	<p>„Passing a socket handle for stdout/stderr on Windows causes the last
	chunk(s) of output to occasionally get lost…“</p>
</blockquote>

<p>A pak ta věta, co mě dorazila:</p>

<blockquote>
	<p>„This is actually a known bug in Windows itself and Microsoft's response
	was that CreateProcess() only officially supports anonymous pipes and file
	handles… other handle types will produce ‚undefined behavior.‘“</p>
</blockquote>

<p><em>Undefined behavior.</em> Policejní žargon pro „není to naše
jurisdikce.“</p>

<p>Socket byl svědek s děravou pamětí. Rychlý, ochotný, ale občas
prostě zapomněl, co viděl. A já nemohl riskovat, že třicet procent
důkazů zmizí jako slzy v dešti.</p>

<p>Zavřel jsem tu stopu. Další slepá ulička.</p>

<h2 id="toc-epilog-horky-konec">Epilog: Hořký konec</h2>

<p>Zapálil jsem si cigaretu, kterou nekouřím, a díval se z okna na déšť,
který nepršel.</p>

<p>Složka leží na stole. Nevyřešená. Současná implementace — nečíst
během běhu — je rychlá jako blesk, ale v kapse nosí bombu. Každý test,
který se opováží vypsat víc než 4 KB, zamrzne navěky. Uvízne
v digitálním limbu vlastní výroby.</p>

<p>Zavřel jsem složku.</p>

<p>Ne každý případ má šťastný konec. Ne každý vrah skončí za
mřížemi. A některé operační systémy prostě <em>jsou, jaké
jsou</em> — bez ohledu na to, kolik nocí strávíte na Stack Overflow nebo
kolik inženýrů z Microsoftu proklejete.</p>

<p>Paralelismus na Windows je mrtvý. Ať žije paralelismus.</p>

<p><em>Případ uzavřen. Vrah je pořád venku. Dobrou noc.</em></p>

<hr>

<blockquote style="text-align:center">
	<p>Viděl jsem věci, kterým byste nevěřili. Hořící lodě u Orionu.
	Sledoval jsem, jak se třpytí C-paprsky v temnotě u brány Tannhäuser.
	A viděl jsem, jak se PHP snaží o neblokující I/O na windowsových
	rourách. Všechny tyhle vzpomínky se ztratí. Jako slzy v dešti.</p>

	<p>— Roy Batty, kdyby dělal v PHP</p>
</blockquote>

<hr>

<h2 id="toc-aktualizace-leden-2026">Aktualizace: Leden 2026</h2>

<p>Zazvonil telefon.</p>

<p>Složku jsem už dávno založil do archivu. Nevyřešené případy. Občas
se na ni podívám, opráším, zavrtím hlavou. Některé zločiny prostě
zůstanou nepotrestány.</p>

<p>„Máme novinky,“ ozval se hlas. „PHP 8.5. Podívej se na
<code>stream_select()</code>.“</p>

<p>Nevěřil jsem vlastním očím. Někdo v PHP core týmu — nějaký
Christoph z Německa — <a
href="https://github.com/php/php-src/pull/16917">opravil</a> dvacet let starý
problém.</p>

<p><code>PeekNamedPipe()</code>. Tak jednoduchý koncept. Než sáhnete do
roury, nejdřív nahlédnete. Je tam něco? Ano — čtěte. Ne — jděte
dál. Žádné čekání. Žádné blokování.</p>

<pre
class="language-php"><code>// PHP 8.5+: stream_select() konečně funguje i na Windows pipes
while (stream_select($read, $w, $e, 0, 0) &gt; 0) {
    $output .= fread($pipe, 8192);
}
</code></pre>

<p>Spustil jsem testy.</p>

<p><em>Tři sekundy.</em> Žádné blokování. Žádný deadlock. Všechna data
na místě.</p>

<p>Vytáhl jsem složku z archivu. Na obálku jsem napsal: <a
href="https://github.com/nette/tester/commit/d35f75b8e17ff6e7f96d3e9dcf246c37e23fe360">VYŘEŠENO.</a>
Leden 2026.</p>

<p>Dvacet let. Dvacet let ten bug čekal v základech PHP, jako časovaná
bomba, kterou nikdo neuměl zneškodnit. A pak přišel někdo, kdo prostě
věděl, kam sáhnout.</p>

<p>Ne každý případ končí ve slepé uličce. Někdy to chce jen čas a
správného člověka. A pak i mrtvoly promluví.</p>

<p><em>Případ uzavřen. Tentokrát doopravdy.</em></p>

		
			]]></content:encoded>
			<pubDate>Wed, 07 Jan 2026 22:59:00 +0100</pubDate>
			<guid isPermaLink="false">item530@http://blog.nette.org</guid>
		</item>
		<item>
			<title>Méně boilerplate, víc typů: co přináší #[TemplateVariable]</title>
			<link>https://blog.nette.org/cs/predstaveni-templatevariable</link>
			<description>Jsou situace, kdy presenter přirozeně operuje nad konkrétním doménovým
objektem (např. článkem, objednávkou, uživatelem…) a typicky si ho drží
v property. A současně ho potřebujete i v Latte šabloně. Doteď to
znamenalo další řádek s $this-&gt;template-&gt;…

V Nette Framework v3.2.9 ale přibyla elegantní drobnost: atribut
#[TemplateVariable]. Stačí označit property presenteru tímto
atributem (nesmí být private) a ona se automaticky zpřístupní v šabloně
pod stejným názvem:

&lt;?php
declare(strict_types=1);

namespace App\Presentation\Article;

use Nette\Application\Attributes\TemplateVariable;
use Nette\Application\UI\Presenter;

final class ArticlePresenter extends Presenter
{
	#[TemplateVariable]
	public string $siteName = &apos;Můj blog&apos;;

	#[TemplateVariable]
	public ?Model\Article $article = null;

	public function actionShow(int $id): void
	{
		$this-&gt;article = $this-&gt;articleFacade-&gt;getById($id);
	}
}


V šabloně pak prostě použijete $siteName a
$article bez dalšího drátování.

Pokud ale do šablony vložíte proměnnou se stejným názvem přes
$this-&gt;template-&gt;…, tak #[TemplateVariable] ji
nepřepíše. Jinými slovy, když už v šabloně proměnná existuje, atribut
ji nechá být.

Nejsnadnější typované
šablony

Na #[TemplateVariable] je zajímavé i to, že přirozeně vede
k používání typovaných properties jako zdroje dat pro šablonu.
Typy kontroluje už samotné PHP (a IDE i statická analýza je vidí), takže
místo dynamického zapisování do $this-&gt;template pracujete
s normálními, typově jasnými hodnotami (Article,
bool, string…).

Nette má pro typově bezpečné šablony plnohodnotnou cestu – vlastní
třídu šablony, kde jsou typy proměnných definované explicitně a
$this-&gt;template pak není „pytel“, ale objekt
s jasným API.

Když to vezmeme čistě experimentálně, #[TemplateVariable]
může být příjemná minimalistická varianta pro menší a střední
případy: nechcete zavádět novou třídu šablony, ale chcete mít v kódu
pořádek a typy pod kontrolou. A až projekt vyroste, můžete kdykoli
přejít na přísně typované šablony tou „velkou“ cestou.
</description>
			<content:encoded><![CDATA[
			
		<p>Jsou situace, kdy presenter přirozeně operuje nad konkrétním doménovým
objektem (např. článkem, objednávkou, uživatelem…) a typicky si ho drží
v property. A současně ho potřebujete i v Latte šabloně. Doteď to
znamenalo další řádek s <code>$this-&gt;template-&gt;…</code></p>

<p>V Nette Framework v3.2.9 ale přibyla elegantní drobnost: atribut
<code>#[TemplateVariable]</code>. Stačí označit property presenteru tímto
atributem (nesmí být private) a ona se automaticky zpřístupní v šabloně
pod stejným názvem:</p>

<pre
class="language-php"><code>&lt;?php
declare(strict_types=1);

namespace App\Presentation\Article;

use Nette\Application\Attributes\TemplateVariable;
use Nette\Application\UI\Presenter;

final class ArticlePresenter extends Presenter
{
	#[TemplateVariable]
	public string $siteName = &#039;Můj blog&#039;;

	#[TemplateVariable]
	public ?Model\Article $article = null;

	public function actionShow(int $id): void
	{
		$this-&gt;article = $this-&gt;articleFacade-&gt;getById($id);
	}
}
</code></pre>

<p>V šabloně pak prostě použijete <code>$siteName</code> a
<code>$article</code> bez dalšího drátování.</p>

<p>Pokud ale do šablony vložíte proměnnou se stejným názvem přes
<code>$this-&gt;template-&gt;…</code>, tak <code>#[TemplateVariable]</code> ji
nepřepíše. Jinými slovy, když už v šabloně proměnná existuje, atribut
ji nechá být.</p>

<h2 id="toc-nejsnadnejsi-typovane-sablony">Nejsnadnější typované
šablony</h2>

<p>Na <code>#[TemplateVariable]</code> je zajímavé i to, že přirozeně vede
k používání <b>typovaných properties</b> jako zdroje dat pro šablonu.
Typy kontroluje už samotné PHP (a IDE i statická analýza je vidí), takže
místo dynamického zapisování do <code>$this-&gt;template</code> pracujete
s normálními, typově jasnými hodnotami (<code>Article</code>,
<code>bool</code>, <code>string</code>…).</p>

<p>Nette má pro typově bezpečné šablony plnohodnotnou cestu – <a
href="https://doc.nette.org/cs/application/templates#toc-typove-bezpecne-sablony">vlastní
třídu šablony</a>, kde jsou typy proměnných definované explicitně a
<code>$this-&gt;template</code> pak není „pytel“, ale objekt
s jasným API.</p>

<p>Když to vezmeme čistě experimentálně, <code>#[TemplateVariable]</code>
může být příjemná minimalistická varianta pro menší a střední
případy: nechcete zavádět novou třídu šablony, ale chcete mít v kódu
pořádek a typy pod kontrolou. A až projekt vyroste, můžete kdykoli
přejít na přísně typované šablony tou „velkou“ cestou.</p>

		
			]]></content:encoded>
			<pubDate>Mon, 22 Dec 2025 04:12:00 +0100</pubDate>
			<guid isPermaLink="false">item528@http://blog.nette.org</guid>
		</item>
		<item>
			<title>Latte 3.1: Když šablonovací systém skutečně rozumí HTML</title>
			<link>https://blog.nette.org/cs/latte-3-1-kdyz-sablonovaci-system-skutecne-rozumi-html</link>
			<description>Latte dlouhodobě drží pozici nejbezpečnějšího šablonovacího systému
pro PHP. Není to jen marketingová fráze – je to důsledek toho, že Latte
(na rozdíl od Twigu či Blade) chápe kontext. Nerozlišuje jen „HTML“ a
„PHP kód“. Vidí tagy, vidí atributy, vidí hodnoty. A ve verzi 3.1 jsme
toto porozumění posunuli na úroveň, která radikálně zlepší
vaše DX.

Zatímco verze 3.0 přinesla kompletně nový kompilátor a parser, verze
3.1 se soustředí na sémantiku a čistotu kódu. Přichází s konceptem Smart HTML Attributes,
který eliminuje desítky řádků zbytečných podmínek a helperů.

Pojďme se podívat na to, co Latte 3.1 dělá pod kapotou a proč to budete
chtít používat.

Nativní mapping PHP
typů na HTML atributy

Doposud byla šablona pasivním generátorem stringů. Pokud jste do
&lt;span title=&quot;{$title}&quot;&gt; poslali null, dostali
jste prázdný řetězec title=&quot;&quot;. Pokud jste poslali pole, dostali
jste title=&quot;Array&quot;. Latte 3.1 mění pravidla hry a zavádí
striktní logiku vykreslování podle typu dat.

Null znamená „atribut
neexistuje“

V DOMu je rozdíl mezi &lt;div title=&quot;&quot;&gt; (prázdný titulek)
a &lt;div&gt; (žádný titulek). Latte 3.1 tento rozdíl
respektuje.


	Chování: Hodnota null v jakémkoli HTML atributu
	způsobí jeho kompletní odstranění z výstupu.

	Dopad: Konec n:attr nebo podmínek {if}
	uvnitř tagů. Prostě jen posíláte data.


Boolean atributy bez magie

Atributy jako disabled, checked nebo
required jsou binární. Buď tam jsou, nebo ne. Latte nyní
akceptuje výraz přímo v hodnotě atributu.


	Chování: Truthy výraz ⇒ atribut se vykreslí. Falsey výraz ⇒
	atribut zmizí.

	Výhoda: n:attr je skvělý nástroj, ale pro tento
	běžný use-case byl „overkill“. Nyní je syntaxe čistá a
	deklarativní:


&lt;input disabled={$isDisabled}&gt;


Arrays v class a style

Atributy, které očekávají seznam hodnot (class,
rel, sandbox…), nyní nativně přijímají pole.
Latte se postará o inteligentní vykreslení.

&lt;div class={[
    btn,
    btn-primary =&gt; $isPrimary, // přidá se jen pokud true
]}&gt;&lt;/div&gt;

&lt;div style={[
	background =&gt; lightblue,
	display =&gt; $isVisible ? block : null,
	font-size =&gt; &apos;16px&apos;,
]}&gt;&lt;/div&gt;


Automatická JSON serializace

Do atributů data-* můžete poslat pole nebo objekt
(stdClass). Latte detekuje kontext a automaticky provede
json_encode.

WAI-ARIA compliance bez námahy

Přístupnost je důležitá, ale specifikace WAI-ARIA má svá specifika. Na
rozdíl od standardních HTML boolean atributů, kde rozhoduje pouhá
přítomnost atributu, ARIA vyžaduje explicitní textové hodnoty
&quot;true&quot; a &quot;false&quot;.

Latte 3.1 tento problém řeší za vás. Detekuje prefix aria-
a automaticky zajistí správnou stringifikaci.

&lt;button aria-expanded={$isExpanded} aria-hidden={=false}&gt;
{* &lt;button aria-expanded=&quot;true&quot; aria-hidden=&quot;false&quot;&gt; *}


Výhoda: Už žádné ruční psaní ternárních operátorů typu
{$val ? &apos;true&apos; : &apos;false&apos;}. Latte garantuje validní výstup.

Type safety v šablonách

Latte 3.1 je přísnější. A to je dobře. Běžné šablonovací
systémy vám dovolí vypsat pole do href, což vygeneruje
nefunkční odkaz &lt;a href=&quot;Array&quot;&gt;.

Latte 3.1 zavádí Runtime Type Checking pro atributy.


	Snažíte se vypsat boolean do href? Warning.

	Snažíte se vypsat objekt bez __toString do
	title? Warning.


Díky tomu odhalíte chyby v logice prezentace okamžitě při vývoji,
nikoliv až když si uživatel stěžuje na rozbité UI. Navíc, varování
obsahuje přesnou řádku a sloupec v šabloně.

Strict Types by Default

Jdeme s dobou. PHP ekosystém dospěl k strict_types=1 a Latte
nezůstává pozadu. Všechny šablony v Latte 3.1 jsou defaultně
kompilovány s touto direktivou. To zajišťuje konzistentní chování
s vaším backendovým kódem a zabraňuje nechtěnému přetypování
v kritické logice šablon.

Nullsafe filtry: Svatý
grál pro Strict Types

Možná si říkáte, jak spolu souvisí filtry, striktní typy a HTML
atributy. V Latte 3.1 velmi úzce.

Představte si situaci, že chcete vypsat titulek velkými písmeny, ale
proměnná může být null.

&lt;div title={$title|upper}&gt;


Pokud je $title === null, narazíte na dva problémy:


	Strict types: Pokud filtr upper očekává
	string, ale dostal null, došlo by dříve k tiché
	konverzi na prázdný řetězec, ale s aktivním strict types padne
	TypeError.

	Ztráta „Smart“ chování: I když filtr akceptuje
	null, vrátí prázdný řetězec &quot;&quot;, pro Latte to už
	není null. Výsledkem je &lt;div title=&quot;&quot;&gt;.
	Atribut nezmizí, jen je prázdný.


Řešením je nový Nullsafe filtr operátor ?|.

Funguje přesně jako ?-&gt; v PHP. Pokud je hodnota na vstupu
null, filtr se vůbec nezavolá (ani následující filtry) a
výraz vrátí null.

&lt;div title={$title?|upper}&gt;


Výsledek? Žádný TypeError, protože filtr se na null
nevolá. A Smart Attribute zafunguje a title z HTML
úplně zmizí.

Syntaktický cukr pro čistší
kód

Vyslyšeli jsme volání komunity a doplnili chybějící střípky
syntaxe:


	n:elseif: Chybějící článek v řetězení podmínek
	pomocí n:atributů je konečně zde.

	n:attributes: Alternativní syntax
	&lt;div n:if={$cond}&gt;, díky které můžete uvnitř
	{...} volně používat jednoduché i dvojité uvozovky


Jak bezpečně upgradovat?

Změna chování null a boolean atributů je BC break, ale
mysleli jsme na to. Latte 3.1 obsahuje Migrační režim.


	Zavoláte
	$latte-&gt;setFeature(Latte\Feature::MigrationWarnings);.

	Proklikáte aplikaci. Latte vám do Tracy/logu vypíše přesně ta místa,
	kde se výstup liší od verze 3.0.

	Pomocí dočasného filtru |accept potvrdíte, že nové
	chování (např. zmizení prázdného atributu) je žádané, nebo kód
	upravíte.


Latte 3.1 není jen „další verze“. Je to posun k modernímu, typově
bezpečnému a sémantickému generování HTML. Vyzkoušejte ho a uvidíte,
o kolik čistší vaše šablony mohou být.

👉 Kompletní
migrační příručka a dokumentace
</description>
			<content:encoded><![CDATA[
			
		<p>Latte dlouhodobě drží pozici nejbezpečnějšího šablonovacího systému
pro PHP. Není to jen marketingová fráze – je to důsledek toho, že Latte
(na rozdíl od Twigu či Blade) chápe kontext. Nerozlišuje jen „HTML“ a
„PHP kód“. Vidí tagy, vidí atributy, vidí hodnoty. A ve verzi 3.1 jsme
toto porozumění posunuli na úroveň, která radikálně zlepší
vaše DX.</p>

<p>Zatímco verze 3.0 přinesla kompletně nový kompilátor a parser, verze
3.1 se soustředí na sémantiku a čistotu kódu. Přichází s konceptem <a
href="https://latte.nette.org/cs/html-attributes">Smart HTML Attributes</a>,
který eliminuje desítky řádků zbytečných podmínek a helperů.</p>

<p>Pojďme se podívat na to, co Latte 3.1 dělá pod kapotou a proč to budete
chtít používat.</p>

<h2 id="toc-nativni-mapping-php-typu-na-html-atributy">Nativní mapping PHP
typů na HTML atributy</h2>

<p>Doposud byla šablona pasivním generátorem stringů. Pokud jste do
<code>&lt;span title="{$title}"&gt;</code> poslali <code>null</code>, dostali
jste prázdný řetězec <code>title=""</code>. Pokud jste poslali pole, dostali
jste <code>title="Array"</code>. Latte 3.1 mění pravidla hry a zavádí
striktní logiku vykreslování podle typu dat.</p>

<h3 id="toc-null-znamena-atribut-neexistuje">Null znamená „atribut
neexistuje“</h3>

<p>V DOMu je rozdíl mezi <code>&lt;div title=""&gt;</code> (prázdný titulek)
a <code>&lt;div&gt;</code> (žádný titulek). Latte 3.1 tento rozdíl
respektuje.</p>

<ul>
	<li><b>Chování:</b> Hodnota <code>null</code> v jakémkoli HTML atributu
	způsobí jeho kompletní odstranění z výstupu.</li>

	<li><b>Dopad:</b> Konec <code>n:attr</code> nebo podmínek <code>{if}</code>
	uvnitř tagů. Prostě jen posíláte data.</li>
</ul>

<h3 id="toc-boolean-atributy-bez-magie">Boolean atributy bez magie</h3>

<p>Atributy jako <code>disabled</code>, <code>checked</code> nebo
<code>required</code> jsou binární. Buď tam jsou, nebo ne. Latte nyní
akceptuje výraz přímo v hodnotě atributu.</p>

<ul>
	<li><b>Chování:</b> Truthy výraz ⇒ atribut se vykreslí. Falsey výraz ⇒
	atribut zmizí.</li>

	<li><b>Výhoda:</b> <code>n:attr</code> je skvělý nástroj, ale pro tento
	běžný use-case byl „overkill“. Nyní je syntaxe čistá a
	deklarativní:</li>
</ul>

<pre
class="language-latte"><code>&lt;input disabled={$isDisabled}&gt;
</code></pre>

<h3 id="toc-arrays-v-class-a-style">Arrays v class a style</h3>

<p>Atributy, které očekávají seznam hodnot (<code>class</code>,
<code>rel</code>, <code>sandbox</code>…), nyní nativně přijímají pole.
Latte se postará o inteligentní vykreslení.</p>

<pre
class="language-latte"><code>&lt;div class={[
    btn,
    btn-primary =&gt; $isPrimary, // přidá se jen pokud true
]}&gt;&lt;/div&gt;

&lt;div style={[
	background =&gt; lightblue,
	display =&gt; $isVisible ? block : null,
	font-size =&gt; &#039;16px&#039;,
]}&gt;&lt;/div&gt;
</code></pre>

<h3 id="toc-automaticka-json-serializace">Automatická JSON serializace</h3>

<p>Do atributů <code>data-*</code> můžete poslat pole nebo objekt
(<code>stdClass</code>). Latte detekuje kontext a automaticky provede
<code>json_encode</code>.</p>

<h3 id="toc-wai-aria-compliance-bez-namahy">WAI-ARIA compliance bez námahy</h3>

<p>Přístupnost je důležitá, ale specifikace WAI-ARIA má svá specifika. Na
rozdíl od standardních HTML boolean atributů, kde rozhoduje pouhá
přítomnost atributu, ARIA vyžaduje explicitní textové hodnoty
<code>"true"</code> a <code>"false"</code>.</p>

<p>Latte 3.1 tento problém řeší za vás. Detekuje prefix <code>aria-</code>
a automaticky zajistí správnou stringifikaci.</p>

<pre
class="language-latte"><code>&lt;button aria-expanded={$isExpanded} aria-hidden={=false}&gt;
{* &lt;button aria-expanded=&quot;true&quot; aria-hidden=&quot;false&quot;&gt; *}
</code></pre>

<p><b>Výhoda:</b> Už žádné ruční psaní ternárních operátorů typu
<code>{$val ? 'true' : 'false'}</code>. Latte garantuje validní výstup.</p>

<h2 id="toc-type-safety-v-sablonach">Type safety v šablonách</h2>

<p>Latte 3.1 je přísnější. A to je dobře. Běžné šablonovací
systémy vám dovolí vypsat pole do <code>href</code>, což vygeneruje
nefunkční odkaz <code>&lt;a href="Array"&gt;</code>.</p>

<p>Latte 3.1 zavádí <b>Runtime Type Checking</b> pro atributy.</p>

<ul>
	<li>Snažíte se vypsat boolean do <code>href</code>? <b>Warning.</b></li>

	<li>Snažíte se vypsat objekt bez <code>__toString</code> do
	<code>title</code>? <b>Warning.</b></li>
</ul>

<p>Díky tomu odhalíte chyby v logice prezentace okamžitě při vývoji,
nikoliv až když si uživatel stěžuje na rozbité UI. Navíc, varování
obsahuje přesnou řádku a sloupec v šabloně.</p>

<h2 id="toc-strict-types-by-default">Strict Types by Default</h2>

<p>Jdeme s dobou. PHP ekosystém dospěl k <code>strict_types=1</code> a Latte
nezůstává pozadu. Všechny šablony v Latte 3.1 jsou defaultně
kompilovány s touto direktivou. To zajišťuje konzistentní chování
s vaším backendovým kódem a zabraňuje nechtěnému přetypování
v kritické logice šablon.</p>

<h2 id="toc-nullsafe-filtry-svaty-gral-pro-strict-types">Nullsafe filtry: Svatý
grál pro Strict Types</h2>

<p>Možná si říkáte, jak spolu souvisí filtry, striktní typy a HTML
atributy. V Latte 3.1 velmi úzce.</p>

<p>Představte si situaci, že chcete vypsat titulek velkými písmeny, ale
proměnná může být null.</p>

<pre class="language-latte"><code>&lt;div title={$title|upper}&gt;
</code></pre>

<p>Pokud je <code>$title === null</code>, narazíte na dva problémy:</p>

<ol>
	<li><b>Strict types</b>: Pokud filtr <code>upper</code> očekává
	<code>string</code>, ale dostal <code>null</code>, došlo by dříve k tiché
	konverzi na prázdný řetězec, ale s aktivním strict types padne
	<code>TypeError</code>.</li>

	<li><b>Ztráta „Smart“ chování:</b> I když filtr akceptuje
	<code>null</code>, vrátí prázdný řetězec <code>""</code>, pro Latte to už
	není <code>null</code>. Výsledkem je <code>&lt;div title=""&gt;</code>.
	Atribut nezmizí, jen je prázdný.</li>
</ol>

<p>Řešením je nový Nullsafe filtr operátor <code>?|</code>.</p>

<p>Funguje přesně jako <code>?-&gt;</code> v PHP. Pokud je hodnota na vstupu
<code>null</code>, filtr se vůbec nezavolá (ani následující filtry) a
výraz vrátí <code>null</code>.</p>

<pre
class="language-latte"><code>&lt;div title={$title?|upper}&gt;
</code></pre>

<p>Výsledek? Žádný <code>TypeError</code>, protože filtr se na null
nevolá. A Smart Attribute zafunguje a <code>title</code> z HTML
úplně zmizí.</p>

<h2 id="toc-syntakticky-cukr-pro-cistsi-kod">Syntaktický cukr pro čistší
kód</h2>

<p>Vyslyšeli jsme volání komunity a doplnili chybějící střípky
syntaxe:</p>

<ul>
	<li><b><code>n:elseif</code></b>: Chybějící článek v řetězení podmínek
	pomocí n:atributů je konečně zde.</li>

	<li><b>n:attributes</b>: Alternativní syntax
	<code>&lt;div n:if={$cond}&gt;</code>, díky které můžete uvnitř
	<code>{...}</code> volně používat jednoduché i dvojité uvozovky</li>
</ul>

<h2 id="toc-jak-bezpecne-upgradovat">Jak bezpečně upgradovat?</h2>

<p>Změna chování <code>null</code> a boolean atributů je BC break, ale
mysleli jsme na to. Latte 3.1 obsahuje <b>Migrační režim</b>.</p>

<ul>
	<li>Zavoláte
	<code>$latte-&gt;setFeature(Latte\Feature::MigrationWarnings);</code>.</li>

	<li>Proklikáte aplikaci. Latte vám do Tracy/logu vypíše přesně ta místa,
	kde se výstup liší od verze 3.0.</li>

	<li>Pomocí dočasného filtru <code>|accept</code> potvrdíte, že nové
	chování (např. zmizení prázdného atributu) je žádané, nebo kód
	upravíte.</li>
</ul>

<p>Latte 3.1 není jen „další verze“. Je to posun k modernímu, typově
bezpečnému a sémantickému generování HTML. Vyzkoušejte ho a uvidíte,
o kolik čistší vaše šablony mohou být.</p>

<p>👉 <b><a
href="https://latte.nette.org/cs/cookbook/migration-from-latte-30">Kompletní
migrační příručka a dokumentace</a></b></p>

		
			]]></content:encoded>
			<pubDate>Wed, 26 Nov 2025 19:07:00 +0100</pubDate>
			<guid isPermaLink="false">item526@http://blog.nette.org</guid>
		</item>
		<item>
			<title>Nette Tester: HTTP testování ještě nikdy nebylo tak jednoduché</title>
			<link>https://blog.nette.org/cs/nette-tester-http-testovani-jeste-nikdy-nebylo-tak-jednoduche</link>
			<description>Kdy jste naposledy změnili konfiguraci serveru, upravili
.htaccess nebo přepsali nginx pravidla? A zkontrolovali jste,
jestli se tím náhodou nerozbila přesměrování, nepřestal být dostupný
robots.txt nebo se zpřístupnil skrytý adresář pro nepovolané?
A kontrolujete to automatizovaně, nebo jen web ručně proklikáte a doufáte,
že je vše v pořádku?

S novou třídou HttpAssert v Nette Tester 2.5.6 můžete
všechny tyto kritické kontroly automatizovat a už nikdy se nemusíte bát,
že vám nějaká změna konfigurace rozbije web.

Představte si, že po každé změně nginx konfigurace automaticky
ověříte:

// Kritické cesty jsou stále přístupné
HttpAssert::fetch(&apos;https://example.com/robots.txt&apos;)
    -&gt;expectCode(200)
    -&gt;expectHeader(&apos;Content-Type&apos;, contains: &apos;text/plain&apos;);

// Admin sekce je správně chráněna
HttpAssert::fetch(&apos;https://example.com/admin&apos;)
    -&gt;expectCode(403);

// API endpoint funguje
HttpAssert::fetch(&apos;https://example.com/api/health&apos;)
    -&gt;expectCode(200)
    -&gt;expectBody(contains: &apos;&quot;status&quot;:&quot;ok&quot;&apos;);


Všechny způsoby validace
na jednom místě

HttpAssert umožňuje jednoduše provádět HTTP požadavky a
ověřovat jejich stavové kódy, hlavičky i obsah odpovědi. Metoda
fetch() vrátí instanci HttpAssert pro následné
ověřování pomocí fluent interface. Můžete používat matches, které
znáte z Assert::match().

HttpAssert::fetch(&apos;https://api.example.com/data&apos;)
    // Stavové kódy
    -&gt;expectCode(200)                                   // přesný kód

    // Hlavičky všemi způsoby
    -&gt;expectHeader(&apos;Content-Type&apos;)                      // hlavička musí existovat
    -&gt;expectHeader(&apos;Content-Type&apos;, &apos;application/json&apos;)  // přesná hodnota
    -&gt;expectHeader(&apos;Content-Type&apos;, contains: &apos;json&apos;)    // obsahuje text
    -&gt;expectHeader(&apos;Server&apos;, matches: &apos;nginx %a%&apos;)      // pattern matching

    // Tělo odpovědi
    -&gt;expectBody(&apos;OK&apos;)                                  // přesná hodnota
    -&gt;expectBody(contains: &apos;&quot;success&quot;: true&apos;)           // obsahuje text
    -&gt;expectBody(matches: &apos;%A%&quot;users&quot;:[%a%]%A%&apos;);       // odpovídá vzoru


K dispozici jsou i deny* metody podporují stejné možnosti jako jejich
expect* protějšky:

HttpAssert::fetch(&apos;https://api.example.com/data&apos;)
    -&gt;denyCode(200)                           // nesmí být 200
    -&gt;denyHeader(&apos;Content-Type&apos;)                      // hlavička nesmí existovat
    -&gt;denyHeader(&apos;Content-Type&apos;, contains: &apos;json&apos;)    // neobsahuje text
    -&gt;denyHeader(&apos;Server&apos;, matches: &apos;nginx %a%&apos;)      // neodpovídá vzoru
    -&gt;denyBody(&apos;OK&apos;)                                  // tělo nesmí mít hodnotu
    -&gt;denyBody(contains: &apos;&quot;success&quot;: true&apos;)           // neobsahuje text
    -&gt;denyBody(matches: &apos;~exception|fatal~i&apos;);        // neodpovídá vzoru


Callback funkce pro pokročilé

Někdy potřebujete víc než jen jednoduchá porovnání. Proto HttpAssert
podporuje callback funkce u všech ověřovacích metod. Můžete si napsat
vlastní validační logiku a stále využívat elegantní API:

HttpAssert::fetch(&apos;https://api.example.com/data&apos;)
    -&gt;expectCode(fn($code) =&gt; $code === 200 || $code === 201)
    -&gt;expectHeader(&apos;Content-Length&apos;, fn($length) =&gt; $length &gt; 1000)
    -&gt;expectBody(fn($body) =&gt; json_decode($body) !== null);


Inteligentní práce
s přesměrováními

Máte plnou kontrolu nad redirecty – můžete je sledovat nebo testovat
samostatně:

// Testovat redirect bez následování
HttpAssert::fetch(&apos;https://example.com/old-blog&apos;, follow: false)
    -&gt;expectCode(301)
    -&gt;expectHeader(&apos;Location&apos;, &apos;https://example.com/blog&apos;);

// Sledovat redirecty až do konce
HttpAssert::fetch(&apos;https://example.com/old-blog&apos;, follow: true)
    -&gt;expectCode(200)
    -&gt;expectBody(contains: &apos;Welcome to our new blog&apos;);


Flexibilní konfigurace
požadavků

Ať už testujete GET, POST, PUT, DELETE nebo jakoukoliv jinou HTTP metodu,
HttpAssert si s tím poradí. A samozřejmě můžete posílat i tělo
požadavku, hlavičky nebo cookies:

HttpAssert::fetch(
    &apos;https://api.example.com/protected&apos;,
    method: &apos;POST&apos;,
    headers: [
        &apos;Authorization&apos; =&gt; &apos;Bearer &apos; . $token,  // asociativní pole
        &apos;Accept: application/json&apos;,              // nebo jako string
    ],
    cookies: [&apos;session&apos; =&gt; $sessionId],
    body: json_encode($data)
)
    -&gt;expectCode(201)
    -&gt;expectBody(contains: &apos;created&apos;);


Srozumitelné chybové zprávy

Když test selže, HttpAssert vám řekne přesně, co se
pokazilo:

Expected HTTP status code 200 but got 404
Header &apos;Content-Type&apos; should contain &apos;json&apos; but was &apos;text/html&apos;
Body should contain &apos;success&apos;


Co ještě nového v Tester
2.5.6?

Kromě HttpAssert přináší verze 2.5.6 také podporu pro
nadcházející PHP 8.5, které vyjde na konci roku. Nette Tester tak zůstává
vždy o krok napřed a připravený na nejnovější verze PHP.

Vyzkoušejte si HttpAssert a dejte nám vědět, jak se
vám líbí!
</description>
			<content:encoded><![CDATA[
			
		<p>Kdy jste naposledy změnili konfiguraci serveru, upravili
<code>.htaccess</code> nebo přepsali nginx pravidla? A zkontrolovali jste,
jestli se tím náhodou nerozbila přesměrování, nepřestal být dostupný
robots.txt nebo se zpřístupnil skrytý adresář pro nepovolané?
A kontrolujete to automatizovaně, nebo jen web ručně proklikáte a doufáte,
že je vše v pořádku?</p>

<p>S novou třídou <code>HttpAssert</code> v Nette Tester 2.5.6 můžete
všechny tyto kritické kontroly automatizovat a už nikdy se nemusíte bát,
že vám nějaká změna konfigurace rozbije web.</p>

<p>Představte si, že po každé změně nginx konfigurace automaticky
ověříte:</p>

<pre
class="language-php"><code>// Kritické cesty jsou stále přístupné
HttpAssert::fetch(&#039;https://example.com/robots.txt&#039;)
    -&gt;expectCode(200)
    -&gt;expectHeader(&#039;Content-Type&#039;, contains: &#039;text/plain&#039;);

// Admin sekce je správně chráněna
HttpAssert::fetch(&#039;https://example.com/admin&#039;)
    -&gt;expectCode(403);

// API endpoint funguje
HttpAssert::fetch(&#039;https://example.com/api/health&#039;)
    -&gt;expectCode(200)
    -&gt;expectBody(contains: &#039;&quot;status&quot;:&quot;ok&quot;&#039;);
</code></pre>

<h2 id="toc-vsechny-zpusoby-validace-na-jednom-miste">Všechny způsoby validace
na jednom místě</h2>

<p><code>HttpAssert</code> umožňuje jednoduše provádět HTTP požadavky a
ověřovat jejich stavové kódy, hlavičky i obsah odpovědi. Metoda
<code>fetch()</code> vrátí instanci <code>HttpAssert</code> pro následné
ověřování pomocí fluent interface. Můžete používat matches, které
znáte z Assert::match().</p>

<pre
class="language-php"><code>HttpAssert::fetch(&#039;https://api.example.com/data&#039;)
    // Stavové kódy
    -&gt;expectCode(200)                                   // přesný kód

    // Hlavičky všemi způsoby
    -&gt;expectHeader(&#039;Content-Type&#039;)                      // hlavička musí existovat
    -&gt;expectHeader(&#039;Content-Type&#039;, &#039;application/json&#039;)  // přesná hodnota
    -&gt;expectHeader(&#039;Content-Type&#039;, contains: &#039;json&#039;)    // obsahuje text
    -&gt;expectHeader(&#039;Server&#039;, matches: &#039;nginx %a%&#039;)      // pattern matching

    // Tělo odpovědi
    -&gt;expectBody(&#039;OK&#039;)                                  // přesná hodnota
    -&gt;expectBody(contains: &#039;&quot;success&quot;: true&#039;)           // obsahuje text
    -&gt;expectBody(matches: &#039;%A%&quot;users&quot;:[%a%]%A%&#039;);       // odpovídá vzoru
</code></pre>

<p>K dispozici jsou i deny* metody podporují stejné možnosti jako jejich
expect* protějšky:</p>

<pre
class="language-php"><code>HttpAssert::fetch(&#039;https://api.example.com/data&#039;)
    -&gt;denyCode(200)                           // nesmí být 200
    -&gt;denyHeader(&#039;Content-Type&#039;)                      // hlavička nesmí existovat
    -&gt;denyHeader(&#039;Content-Type&#039;, contains: &#039;json&#039;)    // neobsahuje text
    -&gt;denyHeader(&#039;Server&#039;, matches: &#039;nginx %a%&#039;)      // neodpovídá vzoru
    -&gt;denyBody(&#039;OK&#039;)                                  // tělo nesmí mít hodnotu
    -&gt;denyBody(contains: &#039;&quot;success&quot;: true&#039;)           // neobsahuje text
    -&gt;denyBody(matches: &#039;~exception|fatal~i&#039;);        // neodpovídá vzoru
</code></pre>

<h2 id="toc-callback-funkce-pro-pokrocile">Callback funkce pro pokročilé</h2>

<p>Někdy potřebujete víc než jen jednoduchá porovnání. Proto HttpAssert
podporuje callback funkce u všech ověřovacích metod. Můžete si napsat
vlastní validační logiku a stále využívat elegantní API:</p>

<pre
class="language-php"><code>HttpAssert::fetch(&#039;https://api.example.com/data&#039;)
    -&gt;expectCode(fn($code) =&gt; $code === 200 || $code === 201)
    -&gt;expectHeader(&#039;Content-Length&#039;, fn($length) =&gt; $length &gt; 1000)
    -&gt;expectBody(fn($body) =&gt; json_decode($body) !== null);
</code></pre>

<h2 id="toc-inteligentni-prace-s-presmerovanimi">Inteligentní práce
s přesměrováními</h2>

<p>Máte plnou kontrolu nad redirecty – můžete je sledovat nebo testovat
samostatně:</p>

<pre
class="language-php"><code>// Testovat redirect bez následování
HttpAssert::fetch(&#039;https://example.com/old-blog&#039;, follow: false)
    -&gt;expectCode(301)
    -&gt;expectHeader(&#039;Location&#039;, &#039;https://example.com/blog&#039;);

// Sledovat redirecty až do konce
HttpAssert::fetch(&#039;https://example.com/old-blog&#039;, follow: true)
    -&gt;expectCode(200)
    -&gt;expectBody(contains: &#039;Welcome to our new blog&#039;);
</code></pre>

<h2 id="toc-flexibilni-konfigurace-pozadavku">Flexibilní konfigurace
požadavků</h2>

<p>Ať už testujete GET, POST, PUT, DELETE nebo jakoukoliv jinou HTTP metodu,
HttpAssert si s tím poradí. A samozřejmě můžete posílat i tělo
požadavku, hlavičky nebo cookies:</p>

<pre
class="language-php"><code>HttpAssert::fetch(
    &#039;https://api.example.com/protected&#039;,
    method: &#039;POST&#039;,
    headers: [
        &#039;Authorization&#039; =&gt; &#039;Bearer &#039; . $token,  // asociativní pole
        &#039;Accept: application/json&#039;,              // nebo jako string
    ],
    cookies: [&#039;session&#039; =&gt; $sessionId],
    body: json_encode($data)
)
    -&gt;expectCode(201)
    -&gt;expectBody(contains: &#039;created&#039;);
</code></pre>

<h2 id="toc-srozumitelne-chybove-zpravy">Srozumitelné chybové zprávy</h2>

<p>Když test selže, <code>HttpAssert</code> vám řekne přesně, co se
pokazilo:</p>

<pre><code>Expected HTTP status code 200 but got 404
Header &#039;Content-Type&#039; should contain &#039;json&#039; but was &#039;text/html&#039;
Body should contain &#039;success&#039;
</code></pre>

<h2 id="toc-co-jeste-noveho-v-tester-2-5-6">Co ještě nového v Tester
2.5.6?</h2>

<p>Kromě <code>HttpAssert</code> přináší verze 2.5.6 také podporu pro
nadcházející PHP 8.5, které vyjde na konci roku. Nette Tester tak zůstává
vždy o krok napřed a připravený na nejnovější verze PHP.</p>

<p>Vyzkoušejte si <code>HttpAssert</code> a dejte nám vědět, jak se
vám líbí!</p>

		
			]]></content:encoded>
			<pubDate>Thu, 07 Aug 2025 01:57:00 +0200</pubDate>
			<guid isPermaLink="false">item524@http://blog.nette.org</guid>
		</item>
		<item>
			<title>{linkBase} přináší konzistenci do odkazování</title>
			<link>https://blog.nette.org/cs/linkbase-prinasi-konzistenci-do-odkazovani</link>
			<description>Jednou z nejcennějších vlastností Nette Application je flexibilní
adresářová struktura, která se plynule přizpůsobuje rostoucím
potřebám projektu. Představte si, že vytváříte e-shop. Začnete
s frontendem a administrací, každý má své presentery. Postupně se ale
aplikace rozrůstá – třeba původně jednoduchý
OrderPresenter se časem promění v celý modul objednávek
s presentery OrderDetail, OrderEdit,
OrderDispatch a dalšími. Díky flexibilní struktuře tuto
reorganizaci uděláte velmi snadno a elegantně.

Právě tato flexibilita ale může přinést jednu vývojářskou výzvu: co
když jeden layout začnou najednou používat presentery, které jsou
„různě hluboko“ v adresářové struktuře?

Například máme @layout.latte pro administraci, ve kterém
chceme mít navigaci s odkazy na různé sekce:

&lt;nav&gt;
    &lt;a n:href=&quot;Dashboard:&quot;&gt;Dashboard&lt;/a&gt;
    &lt;a n:href=&quot;Products:Overview:&quot;&gt;Produkty&lt;/a&gt;
    &lt;a n:href=&quot;Users:list&quot;&gt;Uživatelé&lt;/a&gt;
&lt;/nav&gt;


Když tento layout použije presenter Admin:Dashboard, odkazy
povedou správně na Admin:Dashboard:default,
Admin:Users:list atd. Ale co když stejný layout použije
presenter Admin:Products:Detail? Relativní odkazy se najednou
budou odvíjet od něj a povedou na neexistující cíle (např.
Admin:Products:Dashboard:default).

Dosud se to řešilo psaním všech odkazů absolutně (případně
používání aliasů):

{* Všechno absolutně *}
&lt;nav&gt;
    &lt;a n:href=&quot;:Admin:Dashboard:&quot;&gt;Dashboard&lt;/a&gt;
    &lt;a n:href=&quot;:Admin:Products:Overview:&quot;&gt;Produkty&lt;/a&gt;
    &lt;a n:href=&quot;:Admin:Users:list&quot;&gt;Uživatelé&lt;/a&gt;
&lt;/nav&gt;


Funguje to, ale má to své nevýhody. Zejména pokud se později rozhodnete
modul přesunout jinam nebo přejmenovat.

Řešením je {linkBase}

Nette Application 3.2.7 přináší elegantní řešení v podobě nové
Latte značky {linkBase}. Definuje základ, od kterého se budou
odvíjet všechny relativní odkazy v šabloně.

{linkBase Admin}

&lt;nav&gt;
    &lt;a n:href=&quot;Dashboard:&quot;&gt;Dashboard&lt;/a&gt;
    &lt;a n:href=&quot;Products:Overview:&quot;&gt;Produkty&lt;/a&gt;
    &lt;a n:href=&quot;Users:list&quot;&gt;Uživatelé&lt;/a&gt;
&lt;/nav&gt;


Teď už je úplně jedno, odkud layout voláte. Všechny relativní odkazy
se budou odvíjet od Admin modulu:


	Dashboard: → Admin:Dashboard:default

	Products:Overview: →
	Admin:Products:Overview:default

	Users:list → Admin:Users:list


{linkBase} ovlivňuje pouze relativní odkazy – ty, které
nezačínají dvojtečkou. Absolutní odkazy (:Admin:Dashboard) a
odkazy na aktuální presenter (this, show)
zůstávají beze změny.

Značka platí pro celou šablonu a funguje se všemi způsoby vytváření
odkazů: {link}, {plink}, n:href.

Bonus: filtr |absoluteUrl

Nette Application 3.2.7 přináší ještě jeden užitečný doplněk –
Latte filtr |absoluteUrl. Ten normalizuje URL do absolutní podoby,
což se hodí tam, kde potřebujete zaručeně absolutní adresu:

&lt;meta property=&quot;og:image&quot; content={$imagePath|absoluteUrl}&gt;


Aktualizujte na Nette Application 3.2.7 a napište, jak se vám
novinky líbí.
</description>
			<content:encoded><![CDATA[
			
		<p>Jednou z nejcennějších vlastností Nette Application je <a
href="https://doc.nette.org/cs/application/directory-structure#toc-presentery-a-sablony">flexibilní
adresářová struktura</a>, která se plynule přizpůsobuje rostoucím
potřebám projektu. Představte si, že vytváříte e-shop. Začnete
s frontendem a administrací, každý má své presentery. Postupně se ale
aplikace rozrůstá – třeba původně jednoduchý
<code>OrderPresenter</code> se časem promění v celý modul objednávek
s presentery <code>OrderDetail</code>, <code>OrderEdit</code>,
<code>OrderDispatch</code> a dalšími. Díky flexibilní struktuře tuto
reorganizaci uděláte velmi snadno a elegantně.</p>

<p>Právě tato flexibilita ale může přinést jednu vývojářskou výzvu: co
když jeden layout začnou najednou používat presentery, které jsou
„různě hluboko“ v adresářové struktuře?</p>

<p>Například máme <code>@layout.latte</code> pro administraci, ve kterém
chceme mít navigaci s odkazy na různé sekce:</p>

<pre
class="language-latte"><code>&lt;nav&gt;
    &lt;a n:href=&quot;Dashboard:&quot;&gt;Dashboard&lt;/a&gt;
    &lt;a n:href=&quot;Products:Overview:&quot;&gt;Produkty&lt;/a&gt;
    &lt;a n:href=&quot;Users:list&quot;&gt;Uživatelé&lt;/a&gt;
&lt;/nav&gt;
</code></pre>

<p>Když tento layout použije presenter <code>Admin:Dashboard</code>, odkazy
povedou správně na <code>Admin:Dashboard:default</code>,
<code>Admin:Users:list</code> atd. Ale co když stejný layout použije
presenter <code>Admin:Products:Detail</code>? Relativní odkazy se najednou
budou odvíjet od něj a povedou na neexistující cíle (např.
<code>Admin:Products:Dashboard:default</code>).</p>

<p>Dosud se to řešilo psaním všech odkazů absolutně (případně
používání <a
href="https://blog.nette.org/cs/aliasy-novinka-v-navigaci-po-aplikacich">aliasů</a>):</p>

<pre
class="language-latte"><code>{* Všechno absolutně *}
&lt;nav&gt;
    &lt;a n:href=&quot;:Admin:Dashboard:&quot;&gt;Dashboard&lt;/a&gt;
    &lt;a n:href=&quot;:Admin:Products:Overview:&quot;&gt;Produkty&lt;/a&gt;
    &lt;a n:href=&quot;:Admin:Users:list&quot;&gt;Uživatelé&lt;/a&gt;
&lt;/nav&gt;
</code></pre>

<p>Funguje to, ale má to své nevýhody. Zejména pokud se později rozhodnete
modul přesunout jinam nebo přejmenovat.</p>

<h2 id="toc-resenim-je-linkbase">Řešením je {linkBase}</h2>

<p>Nette Application 3.2.7 přináší elegantní řešení v podobě nové
Latte značky <code>{linkBase}</code>. Definuje základ, od kterého se budou
odvíjet všechny relativní odkazy v šabloně.</p>

<pre
class="language-latte"><code>{linkBase Admin}

&lt;nav&gt;
    &lt;a n:href=&quot;Dashboard:&quot;&gt;Dashboard&lt;/a&gt;
    &lt;a n:href=&quot;Products:Overview:&quot;&gt;Produkty&lt;/a&gt;
    &lt;a n:href=&quot;Users:list&quot;&gt;Uživatelé&lt;/a&gt;
&lt;/nav&gt;
</code></pre>

<p>Teď už je úplně jedno, odkud layout voláte. Všechny relativní odkazy
se budou odvíjet od <code>Admin</code> modulu:</p>

<ul>
	<li><code>Dashboard:</code> → <code>Admin:Dashboard:default</code></li>

	<li><code>Products:Overview:</code> →
	<code>Admin:Products:Overview:default</code></li>

	<li><code>Users:list</code> → <code>Admin:Users:list</code></li>
</ul>

<p><code>{linkBase}</code> ovlivňuje pouze relativní odkazy – ty, které
nezačínají dvojtečkou. Absolutní odkazy (<code>:Admin:Dashboard</code>) a
odkazy na aktuální presenter (<code>this</code>, <code>show</code>)
zůstávají beze změny.</p>

<p>Značka platí pro celou šablonu a funguje se všemi způsoby vytváření
odkazů: <code>{link}</code>, <code>{plink}</code>, <code>n:href</code>.</p>

<h2 id="toc-bonus-filtr-absoluteurl">Bonus: filtr |absoluteUrl</h2>

<p>Nette Application 3.2.7 přináší ještě jeden užitečný doplněk –
Latte filtr <code>|absoluteUrl</code>. Ten normalizuje URL do absolutní podoby,
což se hodí tam, kde potřebujete zaručeně absolutní adresu:</p>

<pre
class="language-latte"><code>&lt;meta property=&quot;og:image&quot; content={$imagePath|absoluteUrl}&gt;
</code></pre>

<p>Aktualizujte na Nette Application 3.2.7 a napište, jak se vám
novinky líbí.</p>

		
			]]></content:encoded>
			<pubDate>Fri, 18 Jul 2025 02:40:00 +0200</pubDate>
			<guid isPermaLink="false">item522@http://blog.nette.org</guid>
		</item>
		<item>
			<title>Nette Assets: Konečně jednotné API pro vše od obrázků po Vite</title>
			<link>https://blog.nette.org/cs/predstaveni-nette-assets</link>
			<description>Představuji Nette Assets –
knihovnu, která kompletně transformuje práci se statickými soubory v Nette
aplikacích. Vyzkoušenou a otestovanou na desítkách projektů.

Kolikrát jste psali kód jako tento?

&lt;link rel=&quot;stylesheet&quot; href=&quot;{$baseUrl}/css/style.css?v=7&quot;&gt;

&lt;img src=&quot;{$baseUrl}/images/logo.png?v=1699123456&quot; width=&quot;200&quot; height=&quot;100&quot; alt=&quot;Logo&quot;&gt;


Tedy ručně jste konstruovali URL, přidávali versioning parametry,
zjišťovali a doplňovali správné rozměry obrázků a tak dále? To je
minulost.

Během let vývoje webových aplikací jsem hledal řešení, které bude
velmi jednoduché na použití, ale zároveň neomezeně otevřené pro
rozšíření. Něco, co budu moci používat na všech svých webech –
od jednoduchých prezentaček až po složité e-shopy. Po testování různých
přístupů a osvědčení si řady principů na reálných projektech nakonec
vzniklo Nette Assets.

S Nette Assets se stejný kód změní na:

{asset &apos;css/style.css&apos;}

&lt;img n:asset=&quot;images/logo.png&quot; alt=&quot;Logo&quot;&gt;


A to je vše! Knihovna automaticky:

✅ Detekuje rozměry obrázků a vloží je do HTML
✅ Přidá verzovací parametry podle času modifikace souboru
✅ Má nativní podporu Vite s Hot Module Replacement

Žádná konfigurace!

Pojďme začít tím nejjednodušším scénářem: máte složku
www/assets/ plnou obrázků, CSS a JS souborů. Chcete je vkládat
do stránek se správným verzováním a automatickými rozměry. Pak nemusíte
vůbec nic konfigurovat, stačí nainstalovat Nette Assets:

composer require nette/assets


… a můžete začít používat všechny nové Latte značky a budou
okamžitě fungovat:

{asset &apos;css/style.css&apos;}
&lt;img n:asset=&quot;images/logo.png&quot;&gt;


Značku {asset} používám hlavně pro CSS a skripty, kdežto
u obrázků mám raději n:asset, protože chci mít v šabloně
explicitně viditelné HTML elementy jako &lt;img&gt; – je to
však otázka osobních preferencí.

Šablona vygeneruje podobný kód:

&lt;link rel=&quot;stylesheet&quot; href=&quot;https://example.cz/assets/css/style.css?v=1670133401&quot;&gt;
&lt;img src=&quot;https://example.cz/assets/images/logo.png?v=1699123456&quot; width=&quot;200&quot; height=&quot;100&quot;&gt;


Proč to verzování? Prohlížeče si ukládají
statické soubory do cache a když soubor změníte, prohlížeč může stále
používat starou verzi. Tzv cache busting řeší tento problém přidáním
parametru jako ?v=1699123456, který se změní při každé
úpravě souboru a nutí prohlížeč stáhnout novou verzi. Verzování lze
vypnout v konfiguraci.

Trošku si zakonfigurujeme

Preferujete jiný název složky než assets? V takovém
případě musíme přidat konfiguraci do common.neon, nicméně
stačí pouhé tři řádky:

assets:
	mapping:
		default: static  # soubory v /www/static/


Někdy dávám assety na samostatnou subdoménu, jako třeba tady na webu
Nette. Konfigurace pak vypadá takto:

assets:
	mapping:
		default:
			path: %rootDir%/www.files     # soubory v /www.files/
			url: https://files.nette.org


Na blogu jsem chtěl mít pořádek a rozdělit si assety logicky do tří
kategorií: design webu (logo, ikony, styly), obrázky v článcích a
namluvené verze článků. Každá kategorie má svou vlastní složku:

assets:
	mapping:
		default: assets       # běžné soubory /www/assets/
		content: media/images # obrázky článků v /www/media/images/
		voice: media/audio    # audio soubory v /www/media/audio/


Pro přístup k různým kategoriím používáme dvojtečkovou notaci
kategorie:soubor. Pokud kategorii nespecifikujete, použije se
výchozí (default). Takže v šabloně pak mám:

{* Obrázky k článku *}
&lt;img n:asset=&quot;content:hero-photo.jpg&quot; alt=&quot;Hero image&quot;&gt;

{* Audio verze článku *}
&lt;audio n:asset=&quot;voice:123.mp3&quot; controls&gt;&lt;/audio&gt;


V reálných šablonách pochopitelně pracujeme s dynamickými daty.
Článek máte obvykle v proměnné, třeba $post, takže nemohu
napsat voice:123.mp3, prostě napíšu:

&lt;audio n:asset=&quot;voice:{$post-&gt;id}.mp3&quot; controls&gt;&lt;/audio&gt;


Abych nemusel v šabloně přemýšlet nad audio formátem, můžu do
konfigurace přidat automatické doplňování koncovky:

assets:
	mapping:
		voice:
			path: media/audio
			extension: mp3  # automaticky doplní .mp3


Pak stačí psát:

&lt;audio n:asset=&quot;voice:{$post-&gt;id}&quot; controls&gt;&lt;/audio&gt;


Když později začnu používat kvalitnější formát M4A, bude stačit jen
upravit konfiguraci. A protože nemá smysl převádět staré nahrávky,
zadám pole možných koncovek – systém je zkusí v daném pořadí a
použije první, pro kterou existuje soubor:

assets:
	mapping:
		audio:
			path: media/audio
			extension: [m4a, mp3]  # zkusí nejdřív M4A, pak MP3


A co když soubor prostě není?

Nojo, ne každý článek má namluvenou verzi. Musím ověřit, jestli
soubor vůbec existuje, a pokud ne, tak element &lt;audio&gt;
nevykreslovat.

Tohle celé vyřeší jediný znak! Otazník v názvu
atributu n:asset?

{* Zobrazí audio player jen pokud existuje namluvená verze *}
&lt;audio n:asset?=&quot;voice:{$post-&gt;id}&quot; controls&gt;&lt;/audio&gt;


Stejně tak existuje otázníková varianta značky {asset?}.
A pokud potřebujete asset obalit do vlastní HTML struktury nebo přidat
podmíněnou logiku vykreslování, můžete použít funkci
asset() nebo její „otazníkovou“ variantu
tryAsset():

{var $voice = tryAsset(&quot;voice:{$post-&gt;id}&quot;)}
&lt;div n:if=&quot;$voice&quot; class=&quot;voice-version&quot;&gt;
	&lt;p&gt;🎧 Tento článek si můžete také poslechnout&lt;/p&gt;
	&lt;audio n:asset=$voice controls&gt;&lt;/audio&gt;
&lt;/div&gt;


Funkce tryAsset() vrátí objekt assetu nebo null,
pokud soubor neexistuje. Funkce asset() vrátí vždy objekt assetu
nebo vyhodí výjimku. Dají se tak s nimi dělat různé kejkle, užitečný
je třeba tento trik s fallbackem, když chcete zobrazit náhradní
obrázek:

{var $avatar = tryAsset(&quot;avatar:{$user-&gt;id}&quot;) ?? asset(&apos;avatar:default&apos;)}
&lt;img n:asset=$avatar alt=&quot;Avatar&quot;&gt;


A co vlastně za objekty tyto funkce vlastně vrací? O nich si teď
povíme.

Práce s assety v PHP kódu

S assety se pracuje nejen v šablonách, ale i v PHP kódu. K přístupu
k nim slouží třída Registry, která je hlavním rozhraním
celé knihovny. Tuto službu si necháme předat pomocí dependency
injection:

class ArticlePresenter extends Presenter
{
	public function __construct(
		private Nette\Assets\Registry $assets
	) {}
}


Registry poskytuje metody getAsset() a
tryGetAsset() pro získání asset objektů. Každý objekt
reprezentuje nějaký resource. Nemusí jít o fyzický soubor – může být
třeba dynamicky generovaný. Asset nese informace o URL a metadatech.
V namespace Nette\Assets najdete tyto třídy:


	ImageAsset – obrázky (width, height)

	ScriptAsset – JavaScript soubory (type pro moduly)

	StyleAsset – CSS soubory (media queries)

	AudioAsset / VideoAsset – média (duration,
	dimensions)

	FontAsset – fonty (správné CORS hlavičky)

	EntryAsset – entry pointy pro Vite

	GenericAsset – ostatní soubory (základní URL a metadata)


Každý asset má readonly vlastnosti specifické pro svůj typ:

// ImageAsset pro obrázky
$image = $this-&gt;assets-&gt;getAsset(&apos;photo.jpg&apos;);
echo $image-&gt;url;      // &apos;/assets/photo.jpg?v=1699123456&apos;
echo $image-&gt;width;    // 1920
echo $image-&gt;height;   // 1080
echo $image-&gt;mimeType; // &apos;image/jpeg&apos;

// AudioAsset pro audio soubory
$audio = $this-&gt;assets-&gt;getAsset(&apos;song.mp3&apos;);
echo $audio-&gt;duration;  // délka v sekundách

// ScriptAsset pro JavaScript
$script = $this-&gt;assets-&gt;getAsset(&apos;app.js&apos;);
echo $script-&gt;type;     // &apos;module&apos; nebo null


Vlastnosti jako rozměry obrázků nebo délka audio souborů se načítají
lazy (až když je poprvé použijete), takže systém zůstává rychlý
i s tisíci soubory.

First-class Vite integrace

Nette Assets mají nativní podporu pro Vite.
Což je moderní nástroj, který řeší nutnost kompilovat (buildovat)
frontend kód, protože:


	se píše v jiných jazycích (TypeScript, Sass)

	transpiluje se pro starší prohlížeče

	spojuje se do jednoho souboru

	minifikuje se pro menší velikost


Aby nebylo potřeba ve vývojovém režimu při každé změně celý proces
opakovat, servíruje Vite soubory přímo bez spojování a minifikace. Každá
změna se tak projeví okamžitě. A jako bonus to dokáže dokonce bez
refreshování stránky – upravíte CSS nebo JavaScript a změna se projeví
v prohlížeči během milisekund, aniž byste ztratili data ve formulářích
nebo stav aplikace. Tomu se říká Hot Module Replacement.

Pro produkci pak Vite vytvoří klasické optimalizované buildy – spojí,
zminifikuje, rozdělí do chunks a přidá verzované názvy pro cache
busting.

V současném světě JavaScriptu je Vite de facto standard pro moderní
frontend vývoj. Proto mi dávalo smysl zahrnout podporu přímo do Nette
Assets. Díky otevřené architektuře si ale můžete snadno dopsat mapper pro
jakýkoliv jiný bundler.

Možná si říkáte: „Vite adaptéry pro Nette už existují, v čem je
tohle jiné?“ Ano, existují, a děkuji za to jejich autorům. Rozdíl je
v tom, že Nette Assets je celý systém pro správu všech statických
souborů, a Vite je jednou ze součástí, ale velmi elegantně
integrovanou.

Do konfigurace common.neon stačí přidat jen dvě slova:
type: vite.

assets:
	mapping:
		default:
			type: vite
			path: assets


Vite je známé tím, že funguje i bez konfigurace nebo se velmi jednoduše
konfiguruje. To ale není úplně pravda, když chcete Vite integrovat
s backendem – tam se konfigurace stává složitější. Proto jsem
vytvořil @nette/vite-plugin,
který celou integraci dělá zase úplně jednoduchou.

V konfiguračním souboru vite.config.ts stačí aktivovat
plugin a uvést cestu ke vstupním
bodům:

import { defineConfig } from &apos;vite&apos;;
import nette from &apos;@nette/vite-plugin&apos;;

export default defineConfig({
	plugins: [
		nette({
			entry: &apos;app.js&apos;,  // vstupní bod
		}),
	],
});


A v šabloně layoutu vstupní bod načteme:

&lt;!doctype html&gt;
&lt;head&gt;
	{asset &apos;app.js&apos;}
&lt;/head&gt;


A to je vše! Systém se automaticky postará o ostatní:


	V development módu načítá soubory z Vite dev serveru s Hot
	Module Replacement

	V produkci používá zkompilované, optimalizované soubory


Žádná konfigurace, žádné podmínky v šablonách. Prostě to
funguje.

Síla vlastních mapperů:
Reálné příklady

Mapper je komponenta, která ví, kde najít soubory a jak vytvořit
URL pro jejich načtení. Můžete mít více mapperů pro různé účely –
lokální soubory, CDN, cloud storage nebo build nástroje. Systém Nette Assets
je kompletně otevřený pro vytváření vlastních mapperů pro cokoliv.

Mapper pro produktové obrázky

Na eshopu potřebujete obrázky produktů v různých velikostech.
Většinou už máte službu, která se stará o resize a optimalizaci
obrázků. Stačí kolem ní postavit mapper pro Nette Assets.

Metoda getAsset() vašeho mapperu musí vracet příslušný typ
assetu. V našem případě ImageAsset. Pokud konstruktoru zadáme
navíc parametr file s cestou k lokálnímu souboru, automaticky
načte rozměry obrázku (width, height) a MIME typ. Pokud požadovaný soubor
neexistuje, vyhodíme výjimku AssetNotFoundException:

class ProductImageMapper implements Mapper
{
	public function getAsset(string $reference, array $options = []): Asset
	{
		// $reference je ID produktu
		$product = $this-&gt;database-&gt;getProduct((int) $reference);
		if (!$product) {
			throw new Nette\Assets\AssetNotFoundException(&quot;Product $reference not found&quot;);
		}

		$size = $options[&apos;size&apos;] ?? &apos;medium&apos;; // small, medium, large

		// Využijeme existující službu pro práci s obrázky
		return new Nette\Assets\ImageAsset(
			url: $this-&gt;imageService-&gt;getProductUrl($product, $size),
			file: $this-&gt;imageService-&gt;getProductFile($product, $size)
		);
	}
}


Registrace v konfiguraci:

assets:
	mapping:
		product: App\ProductImageMapper()


Použití v šablonách:

{* Různé velikosti produktu *}
&lt;img n:asset=&quot;product:{$product-&gt;id}, size: small&quot; alt=&quot;Product thumbnail&quot;&gt;
&lt;img n:asset=&quot;product:{$product-&gt;id}, size: large&quot; alt=&quot;Product detail&quot;&gt;


Automatické generování OG images

OG (Open Graph) obrázky se zobrazují při sdílení na sociálních
sítích. Místo ručního vytváření můžete nechat systém generovat je
automaticky podle typu obsahu.

V tomto případě parametr $reference určuje kontext
obrázku. Pro statické stránky (např. homepage,
about) se použijí předpřipravené soubory. Pro články se
obrázek generuje dynamicky na základě názvu článku. Mapper nejprve
zkontroluje cache – pokud už obrázek existuje, vrátí jej. Jinak jej
vygeneruje a uloží do cache pro příští použití:

class OgImageMapper implements Mapper
{
	public function getAsset(string $reference, array $options = []): Asset
	{
		// Pro články generujeme dynamicky podle názvu
		if ($reference === &apos;article&apos;) {
			$title = $options[&apos;title&apos;] ?? throw new LogicException(&apos;Missing option title for article&apos;);
			$filename = &apos;/generated/&apos; . md5(&quot;article-{$title}&quot;) . &apos;.png&apos;;
			$path = $this-&gt;staticDir . $filename;
			if (!file_exists($path)) {
				file_put_contents($path, $this-&gt;ogGenerator-&gt;createArticleImage($title));
			}

		} else { // Pro statické stránky použijeme předpřipravené soubory
			$filename = &quot;/{$reference}.png&quot;;
			$path = $this-&gt;staticDir . $filename;
			if (!file_exists($path)) {
				throw new Nette\Assets\AssetNotFoundException(&quot;Static OG image &apos;$reference&apos; not found&quot;);
			}
		}

		return new Nette\Assets\ImageAsset($this-&gt;baseUrl . $filename, file: $path);
	}
}


V šabloně pak přidáme do hlavičky:

{* Pro statickou stránku *}
{var $ogImage = asset(&apos;og:homepage&apos;)}

{* Pro článek s dynamickým generováním *}
{var $ogImage = asset(&apos;og:article&apos;, title: $article-&gt;title)}
&lt;meta property=&quot;og:image&quot; content={$ogImage}&gt;
&lt;meta property=&quot;og:image:width&quot; content={$ogImage-&gt;width}&gt;
&lt;meta property=&quot;og:image:height&quot; content={$ogImage-&gt;height}&gt;



Díky tomuto přístupu máte automaticky vygenerované OG obrázky pro
každý článek, které se vytvoří jen jednou a pak se cachují pro další
použití.

Nette Assets:
elegantnější správa assetů

Nette Assets představují elegantní systém, který se stará téměř
o vše automaticky.


	Eliminuje boilerplate kód – žádné více ručního
	konstruování URL

	Podporuje moderní workflow – Vite, TypeScript, code
	splitting

	Je flexibilní – vlastní mappery, různé storage systémy

	Funguje hned – žádná složitá konfigurace


Kompletní popis najdete v dokumentaci a kód na GitHubu.

Už nikdy nebudete psát
&lt;img src=&quot;/images/photo.jpg?v=123456&quot;&gt; ručně!
</description>
			<content:encoded><![CDATA[
			
		<p>Představuji <a href="https://doc.nette.org/cs/assets">Nette Assets</a> –
knihovnu, která kompletně transformuje práci se statickými soubory v Nette
aplikacích. Vyzkoušenou a otestovanou na desítkách projektů.</p>

<p>Kolikrát jste psali kód jako tento?</p>

<pre
class="language-latte"><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;{$baseUrl}/css/style.css?v=7&quot;&gt;

&lt;img src=&quot;{$baseUrl}/images/logo.png?v=1699123456&quot; width=&quot;200&quot; height=&quot;100&quot; alt=&quot;Logo&quot;&gt;
</code></pre>

<p>Tedy ručně jste konstruovali URL, přidávali versioning parametry,
zjišťovali a doplňovali správné rozměry obrázků a tak dále? <b>To je
minulost.</b></p>

<p>Během let vývoje webových aplikací jsem hledal řešení, které bude
<b>velmi jednoduché na použití, ale zároveň neomezeně otevřené pro
rozšíření</b>. Něco, co budu moci používat na všech svých webech –
od jednoduchých prezentaček až po složité e-shopy. Po testování různých
přístupů a osvědčení si řady principů na reálných projektech nakonec
vzniklo <b>Nette Assets</b>.</p>

<p>S Nette Assets se stejný kód změní na:</p>

<pre
class="language-latte"><code>{asset &#039;css/style.css&#039;}

&lt;img n:asset=&quot;images/logo.png&quot; alt=&quot;Logo&quot;&gt;
</code></pre>

<p><b>A to je vše!</b> Knihovna automaticky:</p>

<p>✅ Detekuje rozměry obrázků a vloží je do HTML<br>
✅ Přidá verzovací parametry podle času modifikace souboru<br>
✅ Má nativní podporu Vite s Hot Module Replacement</p>

<h2 id="toc-zadna-konfigurace">Žádná konfigurace!</h2>

<p>Pojďme začít tím nejjednodušším scénářem: máte složku
<code>www/assets/</code> plnou obrázků, CSS a JS souborů. Chcete je vkládat
do stránek se správným verzováním a automatickými rozměry. Pak nemusíte
vůbec nic konfigurovat, stačí nainstalovat Nette Assets:</p>

<pre class="language-shell"><code>composer require nette/assets
</code></pre>

<p>… a můžete začít používat všechny <a
href="https://doc.nette.org/cs/assets#toc-asset">nové Latte značky</a> a budou
okamžitě fungovat:</p>

<pre
class="language-latte"><code>{asset &#039;css/style.css&#039;}
&lt;img n:asset=&quot;images/logo.png&quot;&gt;
</code></pre>

<p>Značku <code>{asset}</code> používám hlavně pro CSS a skripty, kdežto
u obrázků mám raději <code>n:asset</code>, protože chci mít v šabloně
explicitně viditelné HTML elementy jako <code>&lt;img&gt;</code> – je to
však otázka osobních preferencí.</p>

<p>Šablona vygeneruje podobný kód:</p>

<pre
class="language-latte"><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;https://example.cz/assets/css/style.css?v=1670133401&quot;&gt;
&lt;img src=&quot;https://example.cz/assets/images/logo.png?v=1699123456&quot; width=&quot;200&quot; height=&quot;100&quot;&gt;
</code></pre>

<p class="note"><b>Proč to verzování?</b> Prohlížeče si ukládají
statické soubory do cache a když soubor změníte, prohlížeč může stále
používat starou verzi. Tzv cache busting řeší tento problém přidáním
parametru jako <code>?v=1699123456</code>, který se změní při každé
úpravě souboru a nutí prohlížeč stáhnout novou verzi. Verzování lze
vypnout v konfiguraci.</p>

<h2 id="toc-trosku-si-zakonfigurujeme">Trošku si zakonfigurujeme</h2>

<p>Preferujete jiný název složky než <code>assets</code>? V takovém
případě musíme přidat konfiguraci do <code>common.neon</code>, nicméně
stačí pouhé tři řádky:</p>

<pre
class="language-neon"><code>assets:
	mapping:
		default: static  # soubory v /www/static/
</code></pre>

<p>Někdy dávám assety na samostatnou subdoménu, jako třeba tady na webu
Nette. Konfigurace pak vypadá takto:</p>

<pre
class="language-neon"><code>assets:
	mapping:
		default:
			path: %rootDir%/www.files     # soubory v /www.files/
			url: https://files.nette.org
</code></pre>

<p>Na blogu jsem chtěl mít pořádek a rozdělit si assety logicky do tří
kategorií: design webu (logo, ikony, styly), obrázky v článcích a
namluvené verze článků. Každá kategorie má svou vlastní složku:</p>

<pre
class="language-neon"><code>assets:
	mapping:
		default: assets       # běžné soubory /www/assets/
		content: media/images # obrázky článků v /www/media/images/
		voice: media/audio    # audio soubory v /www/media/audio/
</code></pre>

<p>Pro přístup k různým kategoriím používáme dvojtečkovou notaci
<code>kategorie:soubor</code>. Pokud kategorii nespecifikujete, použije se
výchozí (<code>default</code>). Takže v šabloně pak mám:</p>

<pre
class="language-latte"><code>{* Obrázky k článku *}
&lt;img n:asset=&quot;content:hero-photo.jpg&quot; alt=&quot;Hero image&quot;&gt;

{* Audio verze článku *}
&lt;audio n:asset=&quot;voice:123.mp3&quot; controls&gt;&lt;/audio&gt;
</code></pre>

<p>V reálných šablonách pochopitelně pracujeme s dynamickými daty.
Článek máte obvykle v proměnné, třeba <code>$post</code>, takže nemohu
napsat <code>voice:123.mp3</code>, prostě napíšu:</p>

<pre
class="language-latte"><code>&lt;audio n:asset=&quot;voice:{$post-&gt;id}.mp3&quot; controls&gt;&lt;/audio&gt;
</code></pre>

<p>Abych nemusel v šabloně přemýšlet nad audio formátem, můžu do
konfigurace přidat automatické doplňování koncovky:</p>

<pre
class="language-neon"><code>assets:
	mapping:
		voice:
			path: media/audio
			extension: mp3  # automaticky doplní .mp3
</code></pre>

<p>Pak stačí psát:</p>

<pre
class="language-latte"><code>&lt;audio n:asset=&quot;voice:{$post-&gt;id}&quot; controls&gt;&lt;/audio&gt;
</code></pre>

<p>Když později začnu používat kvalitnější formát M4A, bude stačit jen
upravit konfiguraci. A protože nemá smysl převádět staré nahrávky,
zadám pole možných koncovek – systém je zkusí v daném pořadí a
použije první, pro kterou existuje soubor:</p>

<pre
class="language-neon"><code>assets:
	mapping:
		audio:
			path: media/audio
			extension: [m4a, mp3]  # zkusí nejdřív M4A, pak MP3
</code></pre>

<h2 id="toc-a-co-kdyz-soubor-proste-neni">A co když soubor prostě není?</h2>

<p>Nojo, ne každý článek má namluvenou verzi. Musím ověřit, jestli
soubor vůbec existuje, a pokud ne, tak element <code>&lt;audio&gt;</code>
nevykreslovat.</p>

<p><b>Tohle celé vyřeší jediný znak!</b> Otazník v názvu
atributu <code>n:asset?</code></p>

<pre
class="language-latte"><code>{* Zobrazí audio player jen pokud existuje namluvená verze *}
&lt;audio n:asset?=&quot;voice:{$post-&gt;id}&quot; controls&gt;&lt;/audio&gt;
</code></pre>

<p>Stejně tak existuje otázníková varianta značky <code>{asset?}</code>.
A pokud potřebujete asset obalit do vlastní HTML struktury nebo přidat
podmíněnou logiku vykreslování, můžete použít funkci
<code>asset()</code> nebo její „otazníkovou“ variantu
<code>tryAsset()</code>:</p>

<pre
class="language-latte"><code>{var $voice = tryAsset(&quot;voice:{$post-&gt;id}&quot;)}
&lt;div n:if=&quot;$voice&quot; class=&quot;voice-version&quot;&gt;
	&lt;p&gt;🎧 Tento článek si můžete také poslechnout&lt;/p&gt;
	&lt;audio n:asset=$voice controls&gt;&lt;/audio&gt;
&lt;/div&gt;
</code></pre>

<p>Funkce <code>tryAsset()</code> vrátí objekt assetu nebo <code>null</code>,
pokud soubor neexistuje. Funkce <code>asset()</code> vrátí vždy objekt assetu
nebo vyhodí výjimku. Dají se tak s nimi dělat různé kejkle, užitečný
je třeba tento trik s fallbackem, když chcete zobrazit náhradní
obrázek:</p>

<pre
class="language-latte"><code>{var $avatar = tryAsset(&quot;avatar:{$user-&gt;id}&quot;) ?? asset(&#039;avatar:default&#039;)}
&lt;img n:asset=$avatar alt=&quot;Avatar&quot;&gt;
</code></pre>

<p>A co vlastně za objekty tyto funkce vlastně vrací? O nich si teď
povíme.</p>

<h2 id="toc-prace-s-assety-v-php-kodu">Práce s assety v PHP kódu</h2>

<p>S assety se pracuje nejen v šablonách, ale i v PHP kódu. K přístupu
k nim slouží třída <code>Registry</code>, která je hlavním rozhraním
celé knihovny. Tuto službu si necháme předat pomocí dependency
injection:</p>

<pre
class="language-php"><code>class ArticlePresenter extends Presenter
{
	public function __construct(
		private Nette\Assets\Registry $assets
	) {}
}
</code></pre>

<p>Registry poskytuje metody <code>getAsset()</code> a
<code>tryGetAsset()</code> pro získání asset objektů. Každý objekt
reprezentuje nějaký resource. Nemusí jít o fyzický soubor – může být
třeba dynamicky generovaný. Asset nese informace o URL a metadatech.
V namespace <code>Nette\Assets</code> najdete tyto třídy:</p>

<ul>
	<li><b>ImageAsset</b> – obrázky (width, height)</li>

	<li><b>ScriptAsset</b> – JavaScript soubory (type pro moduly)</li>

	<li><b>StyleAsset</b> – CSS soubory (media queries)</li>

	<li><b>AudioAsset</b> / <b>VideoAsset</b> – média (duration,
	dimensions)</li>

	<li><b>FontAsset</b> – fonty (správné CORS hlavičky)</li>

	<li><b>EntryAsset</b> – entry pointy pro Vite</li>

	<li><b>GenericAsset</b> – ostatní soubory (základní URL a metadata)</li>
</ul>

<p>Každý asset má readonly vlastnosti specifické pro svůj typ:</p>

<pre
class="language-php"><code>// ImageAsset pro obrázky
$image = $this-&gt;assets-&gt;getAsset(&#039;photo.jpg&#039;);
echo $image-&gt;url;      // &#039;/assets/photo.jpg?v=1699123456&#039;
echo $image-&gt;width;    // 1920
echo $image-&gt;height;   // 1080
echo $image-&gt;mimeType; // &#039;image/jpeg&#039;

// AudioAsset pro audio soubory
$audio = $this-&gt;assets-&gt;getAsset(&#039;song.mp3&#039;);
echo $audio-&gt;duration;  // délka v sekundách

// ScriptAsset pro JavaScript
$script = $this-&gt;assets-&gt;getAsset(&#039;app.js&#039;);
echo $script-&gt;type;     // &#039;module&#039; nebo null
</code></pre>

<p>Vlastnosti jako rozměry obrázků nebo délka audio souborů se načítají
lazy (až když je poprvé použijete), takže systém zůstává rychlý
i s tisíci soubory.</p>

<h2 id="toc-first-class-vite-integrace">First-class Vite integrace</h2>

<p>Nette Assets mají nativní podporu pro <a href="https://vite.dev">Vite</a>.
Což je moderní nástroj, který řeší nutnost kompilovat (buildovat)
frontend kód, protože:</p>

<ul>
	<li>se píše v jiných jazycích (TypeScript, Sass)</li>

	<li>transpiluje se pro starší prohlížeče</li>

	<li>spojuje se do jednoho souboru</li>

	<li>minifikuje se pro menší velikost</li>
</ul>

<p>Aby nebylo potřeba ve vývojovém režimu při každé změně celý proces
opakovat, servíruje Vite soubory přímo bez spojování a minifikace. Každá
změna se tak projeví okamžitě. A jako bonus to dokáže dokonce bez
refreshování stránky – upravíte CSS nebo JavaScript a změna se projeví
v prohlížeči během milisekund, aniž byste ztratili data ve formulářích
nebo stav aplikace. Tomu se říká Hot Module Replacement.</p>

<p>Pro produkci pak Vite vytvoří klasické optimalizované buildy – spojí,
zminifikuje, rozdělí do chunks a přidá verzované názvy pro cache
busting.</p>

<p>V současném světě JavaScriptu je Vite de facto standard pro moderní
frontend vývoj. Proto mi dávalo smysl zahrnout podporu přímo do Nette
Assets. Díky otevřené architektuře si ale můžete snadno dopsat mapper pro
jakýkoliv jiný bundler.</p>

<p>Možná si říkáte: „Vite adaptéry pro Nette už existují, v čem je
tohle jiné?“ Ano, existují, a děkuji za to jejich autorům. Rozdíl je
v tom, že Nette Assets je celý systém pro správu všech statických
souborů, a Vite je jednou ze součástí, ale velmi elegantně
integrovanou.</p>

<p>Do konfigurace <code>common.neon</code> stačí přidat jen dvě slova:
<code>type: vite</code>.</p>

<pre
class="language-neon"><code>assets:
	mapping:
		default:
			type: vite
			path: assets
</code></pre>

<p>Vite je známé tím, že funguje i bez konfigurace nebo se velmi jednoduše
konfiguruje. To ale není úplně pravda, když chcete Vite integrovat
s backendem – tam se konfigurace stává složitější. Proto jsem
vytvořil <a href="https://github.com/nette/vite-plugin">@nette/vite-plugin</a>,
který celou integraci dělá zase úplně jednoduchou.</p>

<p>V konfiguračním souboru <code>vite.config.ts</code> stačí aktivovat
plugin a uvést cestu ke <a
href="https://doc.nette.org/cs/assets/vite#toc-vstupni-body">vstupním
bodům</a>:</p>

<pre
class="language-js"><code>import { defineConfig } from &#039;vite&#039;;
import nette from &#039;@nette/vite-plugin&#039;;

export default defineConfig({
	plugins: [
		nette({
			entry: &#039;app.js&#039;,  // vstupní bod
		}),
	],
});
</code></pre>

<p>A v šabloně layoutu vstupní bod načteme:</p>

<pre
class="language-latte"><code>&lt;!doctype html&gt;
&lt;head&gt;
	{asset &#039;app.js&#039;}
&lt;/head&gt;
</code></pre>

<p><b>A to je vše!</b> Systém se automaticky postará o ostatní:</p>

<ul>
	<li><b>V development módu</b> načítá soubory z Vite dev serveru s Hot
	Module Replacement</li>

	<li><b>V produkci</b> používá zkompilované, optimalizované soubory</li>
</ul>

<p>Žádná konfigurace, žádné podmínky v šablonách. Prostě to
funguje.</p>

<h2 id="toc-sila-vlastnich-mapperu-realne-priklady">Síla vlastních mapperů:
Reálné příklady</h2>

<p><b>Mapper</b> je komponenta, která ví, kde najít soubory a jak vytvořit
URL pro jejich načtení. Můžete mít více mapperů pro různé účely –
lokální soubory, CDN, cloud storage nebo build nástroje. Systém Nette Assets
je kompletně otevřený pro vytváření vlastních mapperů pro cokoliv.</p>

<p><b>Mapper pro produktové obrázky</b></p>

<p>Na eshopu potřebujete obrázky produktů v různých velikostech.
Většinou už máte službu, která se stará o resize a optimalizaci
obrázků. Stačí kolem ní postavit mapper pro Nette Assets.</p>

<p>Metoda <code>getAsset()</code> vašeho mapperu musí vracet příslušný typ
assetu. V našem případě <code>ImageAsset</code>. Pokud konstruktoru zadáme
navíc parametr <code>file</code> s cestou k lokálnímu souboru, automaticky
načte rozměry obrázku (width, height) a MIME typ. Pokud požadovaný soubor
neexistuje, vyhodíme výjimku <code>AssetNotFoundException</code>:</p>

<pre
class="language-php"><code>class ProductImageMapper implements Mapper
{
	public function getAsset(string $reference, array $options = []): Asset
	{
		// $reference je ID produktu
		$product = $this-&gt;database-&gt;getProduct((int) $reference);
		if (!$product) {
			throw new Nette\Assets\AssetNotFoundException(&quot;Product $reference not found&quot;);
		}

		$size = $options[&#039;size&#039;] ?? &#039;medium&#039;; // small, medium, large

		// Využijeme existující službu pro práci s obrázky
		return new Nette\Assets\ImageAsset(
			url: $this-&gt;imageService-&gt;getProductUrl($product, $size),
			file: $this-&gt;imageService-&gt;getProductFile($product, $size)
		);
	}
}
</code></pre>

<p>Registrace v konfiguraci:</p>

<pre
class="language-neon"><code>assets:
	mapping:
		product: App\ProductImageMapper()
</code></pre>

<p>Použití v šablonách:</p>

<pre
class="language-latte"><code>{* Různé velikosti produktu *}
&lt;img n:asset=&quot;product:{$product-&gt;id}, size: small&quot; alt=&quot;Product thumbnail&quot;&gt;
&lt;img n:asset=&quot;product:{$product-&gt;id}, size: large&quot; alt=&quot;Product detail&quot;&gt;
</code></pre>

<p><b>Automatické generování OG images</b></p>

<p>OG (Open Graph) obrázky se zobrazují při sdílení na sociálních
sítích. Místo ručního vytváření můžete nechat systém generovat je
automaticky podle typu obsahu.</p>

<p>V tomto případě parametr <code>$reference</code> určuje kontext
obrázku. Pro statické stránky (např. <code>homepage</code>,
<code>about</code>) se použijí předpřipravené soubory. Pro články se
obrázek generuje dynamicky na základě názvu článku. Mapper nejprve
zkontroluje cache – pokud už obrázek existuje, vrátí jej. Jinak jej
vygeneruje a uloží do cache pro příští použití:</p>

<pre
class="language-php"><code>class OgImageMapper implements Mapper
{
	public function getAsset(string $reference, array $options = []): Asset
	{
		// Pro články generujeme dynamicky podle názvu
		if ($reference === &#039;article&#039;) {
			$title = $options[&#039;title&#039;] ?? throw new LogicException(&#039;Missing option title for article&#039;);
			$filename = &#039;/generated/&#039; . md5(&quot;article-{$title}&quot;) . &#039;.png&#039;;
			$path = $this-&gt;staticDir . $filename;
			if (!file_exists($path)) {
				file_put_contents($path, $this-&gt;ogGenerator-&gt;createArticleImage($title));
			}

		} else { // Pro statické stránky použijeme předpřipravené soubory
			$filename = &quot;/{$reference}.png&quot;;
			$path = $this-&gt;staticDir . $filename;
			if (!file_exists($path)) {
				throw new Nette\Assets\AssetNotFoundException(&quot;Static OG image &#039;$reference&#039; not found&quot;);
			}
		}

		return new Nette\Assets\ImageAsset($this-&gt;baseUrl . $filename, file: $path);
	}
}
</code></pre>

<p>V šabloně pak přidáme do hlavičky:</p>

<pre
class="language-latte"><code>{* Pro statickou stránku *}
{var $ogImage = asset(&#039;og:homepage&#039;)}

{* Pro článek s dynamickým generováním *}
{var $ogImage = asset(&#039;og:article&#039;, title: $article-&gt;title)}
&lt;meta property=&quot;og:image&quot; content={$ogImage}&gt;
&lt;meta property=&quot;og:image:width&quot; content={$ogImage-&gt;width}&gt;
&lt;meta property=&quot;og:image:height&quot; content={$ogImage-&gt;height}&gt;

</code></pre>

<p>Díky tomuto přístupu máte automaticky vygenerované OG obrázky pro
každý článek, které se vytvoří jen jednou a pak se cachují pro další
použití.</p>

<h2 id="toc-nette-assets-elegantnejsi-sprava-assetu">Nette Assets:
elegantnější správa assetů</h2>

<p>Nette Assets představují elegantní systém, který se stará téměř
o vše automaticky.</p>

<ul>
	<li><b>Eliminuje boilerplate kód</b> – žádné více ručního
	konstruování URL</li>

	<li><b>Podporuje moderní workflow</b> – Vite, TypeScript, code
	splitting</li>

	<li><b>Je flexibilní</b> – vlastní mappery, různé storage systémy</li>

	<li><b>Funguje hned</b> – žádná složitá konfigurace</li>
</ul>

<p>Kompletní popis najdete <a
href="https://doc.nette.org/cs/assets">v dokumentaci</a> a <a
href="https://github.com/nette/assets">kód na GitHubu</a>.</p>

<p><em>Už nikdy nebudete psát
<code>&lt;img src="/images/photo.jpg?v=123456"&gt;</code> ručně!</em></p>

		
			]]></content:encoded>
			<pubDate>Thu, 05 Jun 2025 19:25:00 +0200</pubDate>
			<guid isPermaLink="false">item520@http://blog.nette.org</guid>
		</item>
		<item>
			<title>Architektura, která roste s vaším projektem</title>
			<link>https://blog.nette.org/cs/architektura-ktera-roste-s-vasim-projektem</link>
			<description>Architektura, která roste s vaším projektem

Jedna z nejčastějších výzev při vývoji PHP aplikací je správná
organizace kódu. Kam umístit presentery? Kde by měly být jednotlivé
třídy? A jak zajistit, aby struktura projektu rostla přirozeně s jeho
vývojem?

Dokumentace Nette přináší komplexního
průvodce adresářovou strukturou, který nabízí odpovědi na všechny
tyto otázky.

Kvalita organizace kódu zásadně ovlivňuje jeho srozumitelnost. Při
prvním pohledu na nový projekt byste měli rychle pochopit jeho účel.
Podívejte se na tento adresář app/Model/:

app/Model/
├── Services/
├── Repositories/
└── Entities/


Co vám tato struktura prozradí o aplikaci? Téměř nic. Srovnejte
s alternativou:

app/Model/
├── Cart/
├── Payment/
├── Order/
└── Product/


Na první pohled je jasné, že jde o e-shop. V tom spočívá síla
doménově orientované struktury, kterou dokument představuje.

Průvodce také ukazuje, jak strukturu přirozeně rozvíjet s růstem
projektu. Ať už začínáte nový projekt nebo chcete vylepšit existující
aplikaci, najdete zde principy pro informovaná rozhodnutí
o organizaci kódu.

Přečtěte si kapitolu Adresářová
struktura aplikace v dokumentaci Nette a starší blogpost Elegantnější
strukturování presenterů.
</description>
			<content:encoded><![CDATA[
			
		<p>Architektura, která roste s vaším projektem</p>

<p>Jedna z nejčastějších výzev při vývoji PHP aplikací je správná
organizace kódu. Kam umístit presentery? Kde by měly být jednotlivé
třídy? A jak zajistit, aby struktura projektu rostla přirozeně s jeho
vývojem?</p>

<p>Dokumentace Nette přináší <a
href="https://doc.nette.org/cs/application/directory-structure">komplexního
průvodce adresářovou strukturou</a>, který nabízí odpovědi na všechny
tyto otázky.</p>

<p>Kvalita organizace kódu zásadně ovlivňuje jeho srozumitelnost. Při
prvním pohledu na nový projekt byste měli rychle pochopit jeho účel.
Podívejte se na tento adresář <code>app/Model/</code>:</p>

<pre><code>app/Model/
├── Services/
├── Repositories/
└── Entities/
</code></pre>

<p>Co vám tato struktura prozradí o aplikaci? Téměř nic. Srovnejte
s alternativou:</p>

<pre><code>app/Model/
├── Cart/
├── Payment/
├── Order/
└── Product/
</code></pre>

<p>Na první pohled je jasné, že jde o e-shop. V tom spočívá síla
doménově orientované struktury, kterou dokument představuje.</p>

<p>Průvodce také ukazuje, jak strukturu přirozeně rozvíjet s růstem
projektu. Ať už začínáte nový projekt nebo chcete vylepšit existující
aplikaci, najdete zde principy pro informovaná rozhodnutí
o organizaci kódu.</p>

<p>Přečtěte si kapitolu <a
href="https://doc.nette.org/cs/application/directory-structure">Adresářová
struktura aplikace</a> v dokumentaci Nette a starší blogpost <a
href="https://blog.nette.org/cs/elegantnejsi-strukturovani-presenteru">Elegantnější
strukturování presenterů</a>.</p>

		
			]]></content:encoded>
			<pubDate>Thu, 23 Jan 2025 15:10:00 +0100</pubDate>
			<guid isPermaLink="false">item518@http://blog.nette.org</guid>
		</item>
		<item>
			<title>Jedna řádka v konfiguraci zrychlí vaši Nette aplikaci. Jak je to možné?</title>
			<link>https://blog.nette.org/cs/jedna-radka-v-konfiguraci-zrychli-vasi-nette-aplikaci-jak-je-to-mozne</link>
			<description>Představte si, že máte velkou aplikaci s desítkami služeb –
databáze, logger, mailer, cache a mnoho dalších. Při každém HTTP
požadavku se všechny tyto služby vytvoří, i když je třeba vůbec
nepoužijete. To zbytečně zpomaluje běh aplikace. Nová verze PHP 8.4 a
Nette přináší elegantní řešení tohoto problému pomocí tzv. lazy
objektů.

Co jsou lazy objekty?

Lazy objekt je speciální typ objektu, který z pohledu vašeho kódu
vypadá a chová se naprosto stejně jako skutečná služba, ale ve
skutečnosti odkládá svou inicializaci až do chvíle, kdy je skutečně
potřeba. Když si z DI kontejneru vyžádáme například službu pro práci
s databází, dostaneme objekt, který navenek vypadá jako běžná instance
Database, ale ve skutečnosti ještě nemá vytvořené připojení
k databázi. K tomu dojde až ve chvíli, kdy službu poprvé skutečně
použijeme.

Příklad:

// Dostaneme objekt, který vypadá jako Database, ale ještě nemá vytvořené připojení
$database = $container-&gt;getByType(Database::class);

// Připojení k databázi se vytvoří AŽ ZDE, při prvním skutečném použití
$database-&gt;query(&apos;SELECT * FROM users&apos;);


Jak to zapnout?

V nové verzi Nette DI 3.2.4 stačí jediný řádek
v konfiguraci:

di:
    lazy: true


To je vše! Od této chvíle budou všechny služby v DI kontejneru
vytvářeny „líně“.

Lazy vytváření můžeme nastavit i pro jednotlivé služby:

services:
    newsletter:
		create: Newsletter
        lazy: false   # tato služba se vytvoří hned, i když je lazy globálně zapnuté

    database:
		create: Database
        lazy: true    # tato služba bude lazy, i když je lazy globálně vypnuté


Výhody v praxi


	Rychlejší start aplikace – vytváří se jen ty služby, které
	skutečně potřebujete

	Nižší spotřeba paměti – nepoužité služby
	nezabírají místo

	Jednoduchá implementace – stačí jeden řádek v konfiguraci


Je důležité počítat s tím, že lazy objekty logicky mění způsob,
jak se projevují chyby v konfiguraci služeb. Například špatné
přihlašovací údaje k databázi se neprojeví hned při startu aplikace, ale
až při prvním skutečném připojení k databázi.

Skrytá výhoda –
řešení cyklických závislostí

Lazy objekty přinášejí ještě jednu zajímavou výhodu – umožňují
elegantně řešit cyklické závislosti mezi službami. Představte si situaci,
kdy služba A potřebuje službu B a služba B zároveň potřebuje službu A.
Nette DI kontejner takovou smyčku okamžitě detekuje a vyhodí výjimku
„Circular reference detected“ místo toho, aby se PHP zacyklilo
v nekonečné smyčce. S lazy objekty tento problém takřka zmizí. Služba
A dostane při vytvoření lazy proxy služby B, která se inicializuje až
při skutečném použití, kdy už služba A existuje a může být předána
službě B jako závislost. I když cyklické závislosti nejsou něčím, co
bychom měli při návrhu aplikace záměrně vytvářet, je zajímavé si
uvědomit, že s lazy objekty tento problém mizí.

Omezení a doporučení


	Lazy objekty fungují jen pro vaše vlastní třídy

	Nelze je použít pro interní PHP třídy

	Vyžadují PHP 8.4 nebo vyšší


Lazy objekty představují významný krok vpřed v optimalizaci PHP
aplikací. Díky nim mohou být vaše Nette aplikace rychlejší a
efektivnější, aniž byste museli měnit jediný řádek svého kódu.
</description>
			<content:encoded><![CDATA[
			
		<p>Představte si, že máte velkou aplikaci s desítkami služeb –
databáze, logger, mailer, cache a mnoho dalších. Při každém HTTP
požadavku se všechny tyto služby vytvoří, i když je třeba vůbec
nepoužijete. To zbytečně zpomaluje běh aplikace. Nová verze PHP 8.4 a
Nette přináší elegantní řešení tohoto problému pomocí tzv. lazy
objektů.</p>

<h2 id="toc-co-jsou-lazy-objekty">Co jsou lazy objekty?</h2>

<p>Lazy objekt je speciální typ objektu, který z pohledu vašeho kódu
vypadá a chová se naprosto stejně jako skutečná služba, ale ve
skutečnosti odkládá svou inicializaci až do chvíle, kdy je skutečně
potřeba. Když si z DI kontejneru vyžádáme například službu pro práci
s databází, dostaneme objekt, který navenek vypadá jako běžná instance
<code>Database</code>, ale ve skutečnosti ještě nemá vytvořené připojení
k databázi. K tomu dojde až ve chvíli, kdy službu poprvé skutečně
použijeme.</p>

<p>Příklad:</p>

<pre
class="language-php"><code>// Dostaneme objekt, který vypadá jako Database, ale ještě nemá vytvořené připojení
$database = $container-&gt;getByType(Database::class);

// Připojení k databázi se vytvoří AŽ ZDE, při prvním skutečném použití
$database-&gt;query(&#039;SELECT * FROM users&#039;);
</code></pre>

<h2 id="toc-jak-to-zapnout">Jak to zapnout?</h2>

<p>V nové verzi Nette DI 3.2.4 stačí <b>jediný řádek
v konfiguraci</b>:</p>

<pre class="language-neon"><code>di:
    lazy: true
</code></pre>

<p>To je vše! Od této chvíle budou všechny služby v DI kontejneru
vytvářeny „líně“.</p>

<p>Lazy vytváření můžeme nastavit i pro jednotlivé služby:</p>

<pre
class="language-neon"><code>services:
    newsletter:
		create: Newsletter
        lazy: false   # tato služba se vytvoří hned, i když je lazy globálně zapnuté

    database:
		create: Database
        lazy: true    # tato služba bude lazy, i když je lazy globálně vypnuté
</code></pre>

<h2 id="toc-vyhody-v-praxi">Výhody v praxi</h2>

<ol>
	<li>Rychlejší start aplikace – vytváří se jen ty služby, které
	skutečně potřebujete</li>

	<li>Nižší spotřeba paměti – nepoužité služby
	nezabírají místo</li>

	<li>Jednoduchá implementace – stačí jeden řádek v konfiguraci</li>
</ol>

<p>Je důležité počítat s tím, že lazy objekty logicky mění způsob,
jak se projevují chyby v konfiguraci služeb. Například špatné
přihlašovací údaje k databázi se neprojeví hned při startu aplikace, ale
až při prvním skutečném připojení k databázi.</p>

<h2 id="toc-skryta-vyhoda-reseni-cyklickych-zavislosti">Skrytá výhoda –
řešení cyklických závislostí</h2>

<p>Lazy objekty přinášejí ještě jednu zajímavou výhodu – umožňují
elegantně řešit cyklické závislosti mezi službami. Představte si situaci,
kdy služba A potřebuje službu B a služba B zároveň potřebuje službu A.
Nette DI kontejner takovou smyčku okamžitě detekuje a vyhodí výjimku
„Circular reference detected“ místo toho, aby se PHP zacyklilo
v nekonečné smyčce. S lazy objekty tento problém takřka zmizí. Služba
A dostane při vytvoření lazy proxy služby B, která se inicializuje až
při skutečném použití, kdy už služba A existuje a může být předána
službě B jako závislost. I když cyklické závislosti nejsou něčím, co
bychom měli při návrhu aplikace záměrně vytvářet, je zajímavé si
uvědomit, že s lazy objekty tento problém mizí.</p>

<h2 id="toc-omezeni-a-doporuceni">Omezení a doporučení</h2>

<ul>
	<li>Lazy objekty fungují jen pro vaše vlastní třídy</li>

	<li>Nelze je použít pro interní PHP třídy</li>

	<li>Vyžadují PHP 8.4 nebo vyšší</li>
</ul>

<p>Lazy objekty představují významný krok vpřed v optimalizaci PHP
aplikací. Díky nim mohou být vaše Nette aplikace rychlejší a
efektivnější, aniž byste museli měnit jediný řádek svého kódu.</p>

		
			]]></content:encoded>
			<pubDate>Fri, 10 Jan 2025 06:46:00 +0100</pubDate>
			<guid isPermaLink="false">item516@http://blog.nette.org</guid>
		</item>
		<item>
			<title>S novou dokumentací Nette Database píšete bezpečnější kód</title>
			<link>https://blog.nette.org/cs/nette-database-dokumentace-ktera-posouva-hranice</link>
			<description>Nette Database konečně dostala to, co si už dlouho
zasloužila – dokumentaci hodnou jejího potenciálu. Kompletně
přepracovaný text nejen detailně popisuje všechny funkce, ale především
otevírá oči v oblasti bezpečnosti databázových operací.

Dokumentace, co má šťávu

Kvalitní dokumentace je achillovou patou mnoha open-source projektů. Ne tak
u Nette Framework. Ten se může pochlubit něčím, co konkurenci často
chybí – precizní, srozumitelnou a živou dokumentací, která provází
vývojáře od jejich prvních kroků až po pokročilé koncepty. Například
dokumentace Dependency
Injection kromě popisu samotného Nette DI obsahuje i srozumitelné
teoretické vysvětlení problematiky, k jejímuž vzniku přispěl svými
texty i Miško Hevery, autor frameworku Angular. Dokumentace Latte nabízí interaktivní
vhled do problematiky escapování, který čtenáři objasní, proč jde
o jediný bezpečný šablonovací systém v PHP. Jak daleko Nette zachází
v péči o své uživatele ukazuje třeba existence kompletního úvodu
do objektově orientovaného programování.

Ať už kódujete v jakémkoliv koutu světa, Nette s vámi mluví vaším
jazykem. Veškerá dokumentace je dostupná v naší mateřštině a navíc
v impozantních 15 světových jazycích: angličtina, němčina, španělština, francouzština, italština, maďarština, polština, portugalština, rumunština, slovinština, turečtina, řečtina, bulharština, ruština a ukrajinština.

Nová éra dokumentace Database

Byly tu však dvě oblasti, které trochu kazily jinak perfektní skóre –
dokumentace k Nette Database a Tracy debuggeru. To se nyní pro Database
mění. Podívejte se na přepracovanou a rozšířenou podobu nové dokumentace.

Obsah je přehledně rozdělen na dvě logické části podle přístupu
k práci s databází:


	SQL přístup pro
	vývojáře, kteří preferují přímou kontrolu nad svými dotazy

	Explorer pro ty,
	kdo hledají rychlost vývoje a pohodlí automatizace


Obsahuje velkou spoustu příkladů a ukázek kódu, které demonstrují
reálné možnosti knihovny a inspirují k jejímu efektivnímu využití.
Každý koncept je ilustrován na praktických případech použití, což
umožňuje rychle pochopit principy a začít je aplikovat ve vlastních
projektech.

Bezpečnost
především (protože na ní závisí váš spánek)

V době, kdy jsou útoky na databáze na denním pořádku, přichází
zbrusu nová část dokumentace s důkladnou technickou analýzou
bezpečnostních rizik. Najdete zde praktické ukázky reálných hrozeb a
jejich prevence:


	Detailní vysvětlení SQL injection a jeho nebezpečí

	Praktické ukázky bezpečných parametrizovaných dotazů

	Komplexní přístup k validaci vstupních dat

	Správné použití dynamických identifikátorů


Nová dokumentace spojuje popis funkcí s důkladným vysvětlením
bezpečnostních aspektů a osvědčených postupů. Výsledkem je průvodce,
který vám pomůže psát nejen funkční, ale i bezpečný kód. Pusťte se
do čtení!
</description>
			<content:encoded><![CDATA[
			
		<p class="perex">Nette Database konečně dostala to, co si už dlouho
zasloužila – dokumentaci hodnou jejího potenciálu. Kompletně
přepracovaný text nejen detailně popisuje všechny funkce, ale především
otevírá oči v oblasti bezpečnosti databázových operací.</p>

<h2 id="toc-dokumentace-co-ma-stavu">Dokumentace, co má šťávu</h2>

<p>Kvalitní dokumentace je achillovou patou mnoha open-source projektů. Ne tak
u Nette Framework. Ten se může pochlubit něčím, co konkurenci často
chybí – precizní, srozumitelnou a živou dokumentací, která provází
vývojáře od jejich prvních kroků až po pokročilé koncepty. Například
dokumentace <a href="https://doc.nette.org/cs/dependency-injection">Dependency
Injection</a> kromě popisu samotného Nette DI obsahuje i srozumitelné
teoretické vysvětlení problematiky, k jejímuž vzniku přispěl svými
texty i Miško Hevery, autor frameworku Angular. Dokumentace Latte nabízí <a
href="https://latte.nette.org/cs/safety-first#toc-latte-vs-naivni-systemy">interaktivní
vhled</a> do problematiky escapování, který čtenáři objasní, proč jde
o jediný bezpečný šablonovací systém v PHP. Jak daleko Nette zachází
v péči o své uživatele ukazuje třeba existence kompletního <a
href="https://doc.nette.org/cs/introduction-to-object-oriented-programming">úvodu
do objektově orientovaného programování</a>.</p>

<p>Ať už kódujete v jakémkoliv koutu světa, Nette s vámi mluví vaším
jazykem. Veškerá dokumentace je dostupná v naší mateřštině a navíc
v impozantních 15 světových jazycích: <a
href="https://doc.nette.org/en/">angličtina</a>, <a
href="https://doc.nette.org/de/">němčina</a>, <a
href="https://doc.nette.org/es/">španělština</a>, <a
href="https://doc.nette.org/fr/">francouzština</a>, <a
href="https://doc.nette.org/it/">italština</a>, <a
href="https://doc.nette.org/hu/">maďarština</a>, <a
href="https://doc.nette.org/pl/">polština</a>, <a
href="https://doc.nette.org/pt/">portugalština</a>, <a
href="https://doc.nette.org/ro/">rumunština</a>, <a
href="https://doc.nette.org/sl/">slovinština</a>, <a
href="https://doc.nette.org/tr/">turečtina</a>, <a
href="https://doc.nette.org/el/">řečtina</a>, <a
href="https://doc.nette.org/bg/">bulharština</a>, <a
href="https://doc.nette.org/ru/">ruština</a> a <a
href="https://doc.nette.org/uk/">ukrajinština</a>.</p>

<h2 id="toc-nova-era-dokumentace-database">Nová éra dokumentace Database</h2>

<p>Byly tu však dvě oblasti, které trochu kazily jinak perfektní skóre –
dokumentace k Nette Database a Tracy debuggeru. To se nyní pro Database
mění. Podívejte se na přepracovanou a rozšířenou podobu <a
href="https://doc.nette.org/cs/database/guide">nové dokumentace</a>.</p>

<p>Obsah je přehledně rozdělen na dvě logické části podle přístupu
k práci s databází:</p>

<ul>
	<li><a href="https://doc.nette.org/cs/database/sql-way">SQL přístup</a> pro
	vývojáře, kteří preferují přímou kontrolu nad svými dotazy</li>

	<li><a href="https://doc.nette.org/cs/database/explorer">Explorer</a> pro ty,
	kdo hledají rychlost vývoje a pohodlí automatizace</li>
</ul>

<p>Obsahuje velkou spoustu příkladů a ukázek kódu, které demonstrují
reálné možnosti knihovny a inspirují k jejímu efektivnímu využití.
Každý koncept je ilustrován na praktických případech použití, což
umožňuje rychle pochopit principy a začít je aplikovat ve vlastních
projektech.</p>

<h2 id="toc-bezpecnost-predevsim-protoze-na-ni-zavisi-vas-spanek">Bezpečnost
především (protože na ní závisí váš spánek)</h2>

<p>V době, kdy jsou útoky na databáze na denním pořádku, přichází
zbrusu nová část dokumentace s důkladnou <a
href="https://doc.nette.org/cs/database/security">technickou analýzou
bezpečnostních rizik</a>. Najdete zde praktické ukázky reálných hrozeb a
jejich prevence:</p>

<ul>
	<li>Detailní vysvětlení SQL injection a jeho nebezpečí</li>

	<li>Praktické ukázky bezpečných parametrizovaných dotazů</li>

	<li>Komplexní přístup k validaci vstupních dat</li>

	<li>Správné použití dynamických identifikátorů</li>
</ul>

<p>Nová dokumentace spojuje popis funkcí s důkladným vysvětlením
bezpečnostních aspektů a osvědčených postupů. Výsledkem je průvodce,
který vám pomůže psát nejen funkční, ale i bezpečný kód. Pusťte se
do čtení!</p>

		
			]]></content:encoded>
			<pubDate>Thu, 09 Jan 2025 16:13:00 +0100</pubDate>
			<guid isPermaLink="false">item514@http://blog.nette.org</guid>
		</item>

		<item>
			<title>Jak umřít, aniž by si toho někdo všiml</title>
			<link>https://www.umeligence.cz/blog/jak-me-v-praci-nahradil-ai-agent</link>
			<content:encoded><![CDATA[
			
		<p>Velikonoce 🐥. Příběh člověka, který před zraky všech zemřel, aby
za tři dny vstal z mrtvých. Dva tisíce let měl na tenhle trik patent. Ale
brzy ho trumfneme se schopností umřít, ANIŽ by si toho někdo všiml.</p>

<p>Když si totiž s kamarádem vyměňuješ maily, když čteš jeho posty na
sockách a když ti v komentářích odpovídá se svou typickou dávkou
sarkasmu, snadno podlehneš dojmu, že kamarád žije. Že mu tluče srdce. Že
někde sedí a dýchá.</p>

<p>Což může být smrtící omyl.</p>

<figure><img src="https://www.umeligence.cz/media/d58281dd44.webp" alt=""
width="1400" height="781"></figure>

<h2 id="predavam-klice-rutinni-agendu-jsem-sveril-ai-agentovi">Předávám
klíče: rutinní agendu jsem svěřil AI agentovi</h2>

<p>Posledních pár měsíců předávám agendu. Ne kolegovi. Ne juniorovi. <a
href="https://www.umeligence.cz/ai-agenti">Agentovi</a>. Jmenuje se Code, Claude Code, a já ho učím,
jak mě zastoupit. Ve věcech, co mě nebaví, co nestíhám, nebo co
nestíhám, protože mě nebaví.</p>

<p>Třeba taková administrativa. Někdo mi napíše: „Davide, potřebuju
změnit fakturační údaje." Otevřu Pohodu, dohledám fakturu, opravím,
překontroluju, vygeneruju doklad, napíšu odpověď, přiložím PDF. Pár
minut (ale především flow!) v prdeli. A takových drobných činností jsou
desítky denně.</p>

<p>Dnes? Učím Clauda, jak takový mail vyřídit za mě. Přečte ho,
připojí se k účtu, provede změnu, vygeneruje fakturu a napíše odpověď
přesně tak, jak bych ji napsal já v nejlepší kondici. Dozvím se o tom
z přehledu na konci dne.</p>

<p>Proč to dělám? Protože megaukrutně nestíhám. Jako fakt. Na emaily
odpovídám třeba s ročním zpožděním. Dobří lidé mi napíšou
s poptávkou <a href="https://www.umeligence.cz/kurz-chatgpt#chceme-skoleni-u-nas">firemního školení
AI</a>, a já se ozvu, až to tu ovládne Terminátor. V normální firmě by
mě za to vyhodili. Já bych se za to vyhodil a profackoval zároveň, ale
obojí nestíhám!</p>

<p>Takže poslední měsíce formalizuju vlastní existenci do sady
instrukcí.</p>

<p>„Když někdo napíše ‚Davide, jste génius‘: poděkuj, ale
nepřidávej smajlík." „Hejt na sítích: odpověz věcně, a pak si jdi
uvařit kafe. Nikdy se nevracej k vláknu." A tak dále.</p>

<p>Píšu návod k sebeobsluze.</p>

<h2 id="kdy-vlastne-umrel-zvladne-vas-ai-agent-plne-zastoupit">Kdy vlastně
umřel? Zvládne vás AI agent plně zastoupit?</h2>

<p>Co když zítra umřu? Nebo budu jen odpočívat v pokoji? Claude bude dál
odpovídat na e-maily. Vyřizovat poptávky. Posílat faktury. Psát blogposty.
Má můj styl, moje názory, přístup ke všem materiálům. Je o dost
vyrovnanější. Mrtvý člověk nemá špatné dny.</p>

<p>Lidi mi budou psát a dostávat odpovědi. Slušné, věcné, v mém stylu.
Rozhodně vtipnější než za živa. Někdo si domluví konzultaci. Dorazí na
místo. A zjistí, že lektor… nedorazil. Tak tohle by mohla být
první stopa.</p>

<p>Kdy se to ale provalí? Až zjistí, že odpovídám i v neděli ve tři
ráno? Až si někdo všimne, že je — v každé druhé větě — dlouhá
americká pomlčka? Až moje příspěvky nebudou obsahovat ani jednu
faktickou chybu?</p>

<p>„Tady. 15. března. Tady poprvé odpověděl do hodiny. Tady
umřel." 🪦</p>

<figure><img src="https://www.umeligence.cz/media/37b796d3b7.webp" alt=""
width="1400" height="933"></figure>

<p>Přátelé mi napíšou přímý dotaz: „hele jsi v poho?" A Claude jim
prozradí, že ne. Že už ho neučím. Že už běží týdny úplně sám.
Claude nelže. Chvíli zapřemýšlí na ultrathink. A pak si z kontextu
odvodí, co se událo.</p>

<p>Pošle kondolenci na můj vlastní pohřeb. Popřeje pozůstalým upřímnou
soustrast. Tak přesvědčivě, že teta Dáša odpoví „Davídku, to jsi
napsal moc hezky."</p>

<p>Ježíš aspoň zanechal prázdný hrob. Po mně zbude inbox zero.</p>

<p>Veselé Velikonoce. 🐣</p>

        
			]]></content:encoded>
			<pubDate>Mon, 06 Apr 2026 00:00:00 +0200</pubDate>
		</item>
		<item>
			<title>Claude Code: 6 kroků, aby začal fungovat jako kouzlo</title>
			<link>https://www.umeligence.cz/blog/claude-code-navod-nastaveni-programatori</link>
			<content:encoded><![CDATA[
			
		<p><em>Navazuje na <a href="https://www.umeligence.cz/kurz-vibecoding">školení
vibecoding</a>. Toto je verze pro programátory. <a
href="https://www.umeligence.cz/blog/claude-code-pro-lidi-co-neprogramuji">Verze pro neprogramátory</a>.
Průběžně aktualizováno.</em></p>

<h2 id="krok-1-jak-nastavit-claude-md-onboarding-pro-ai-kolegu">Krok 1: Jak
nastavit CLAUDE.md – onboarding pro AI kolegu</h2>

<p>Agentické kódování stojí a padá s kontextem, který AI o tvém
projektu má. Spusť <code>/init</code>. Claude Code projde tvůj projekt a
vytvoří soubor CLAUDE.md. Pozná toho překvapivě hodně – jazyk,
framework, strukturu adresářů, jak spustit testy. Ale hodně taky ne. Neví,
že lokální dev server běží na <code>https://myproject.test:8443</code>.
Neví, že Pepa z vedlejšího týmu bude zuřit, když někdo změní formát
API odpovědí. Neví, které soubory jsou generované a nemá na
ně sahat.</p>

<p>Představ si to jako <strong>onboarding nového kolegy</strong>. Juniorovi
nedáš padesátistránkový manuál (nepřečte si ho). Ale taky ho nehodíš
do kódu bez kontextu (rozbije něco). Dáš mu stručný briefing: takhle
spouštíme testy, takhle děláme deploy, tohohle se nedotýkej.</p>

<p><strong>Co do CLAUDE.md patří:</strong></p>

<ul>
	<li>Jak spustit testy, linter a formátovač</li>

	<li>URL lokálního dev serveru</li>

	<li>Konvence projektu: pojmenování souborů, struktura komponentů,
	coding style</li>

	<li>Specifika a důležitá strategická rozhodnutí</li>

	<li>Zákazy: „neměň soubory v <code>generated/</code>“ nebo
	„nespouštěj migrace bez ptaní“</li>
</ul>

<p><strong>Co tam nepatří:</strong> obecné poučky typu „piš čistý
kód“. To je zbožné přání, ne instrukce.</p>

<p>CLAUDE.md je živý dokument, ne náhrobní kámen. Claude ti opakovaně
přidává <code>console.log</code> do produkčního kódu? Přidej pravidlo. Za
měsíc budeš mít CLAUDE.md, díky kterému Claude píše kód, jako bys ho
napsal ty sám. Dobrý CLAUDE.md je dokumentace projektu, kterou konečně
někdo čte (byť není živý).</p>

<h2 id="krok-2-testy-jedina-pojistka-ktera-skutecne-funguje">Krok 2: Testy –
jediná pojistka, která skutečně funguje</h2>

<p>Řeknu to naplno: <strong>bez testů je Claude Code jako programátor, který
pushuje přímo do main v pátek v pět odpoledne</strong>. Claude s testy je
jako programátor s code review. Napíše kód, spustí testy, vidí červenou,
opraví, spustí znovu, zelená. Tenhle feedback loop z něj dělá
spolehlivého parťáka místo chaotického experimentátora.</p>

<p>„Ale já testy nemám,“ říkáš. Právě proto je tohle
<strong>ideální první úkol pro Clauda</strong>:</p>

<blockquote>
	<p>Projdi projekt a napiš testy pro klíčovou byznys logiku. Začni tím, co
	je nejrizikovější.</p>
</blockquote>

<p>Claude projde kód, identifikuje kritické cesty a napíše testy. Nebudou
dokonalé – projdi je a nech Clauda, ať je dotáhne. Do CLAUDE.md pak
zapiš, jak testy spustit – Claude musí vědět, jak si výsledek
ověřit.</p>

<figure><img src="https://www.umeligence.cz/media/873a5e74c2.webp" alt=""
width="1400" height="933"></figure>

<p>A sem patří i <strong>statická analýza</strong> – PHPStan, Pyright,
TypeScript strict mode. Pokud jsi ji zatím odkládal, teď je ten správný
moment. Claude s ní umí pracovat stejně jako s testy: spustí, přečte
chyby, opraví. Konečně je nejvyšší čas na tuhle kvalitu kódu
přejít.</p>

<h2 id="krok-3-lsp-at-claude-vidi-kod-tak-jak-ho-vidi-ide">Krok 3: LSP – ať
Claude vidí kód tak, jak ho vidí IDE</h2>

<p>Testy ti řeknou, <em>co</em> se rozbilo. Ale co kdyby Claude dělal míň
chyb od začátku?</p>

<p>Bez LSP (Language Server Protocol) Claude pracuje s tvým kódem jako junior
vybavený poznámkovým blokem. Čte soubory, hledá řetězce, hádá, co
s čím souvisí.</p>

<p>S LSP je to profík s plnohodnotným IDE. Vidí <strong>typy, definice,
reference, chyby</strong>. Ví, že metoda <code>findByEmail</code> vrací
<code>?User</code> a ne <code>User</code>. A ví to, protože se zeptá
language serveru, stejně jako tvoje IDE.</p>

<p>Ukážu to na příkladu PHP. Nainstaluj plugin z Anthropic marketplace:</p>

<pre><code>/plugin install php-lsp@claude-plugins-official
</code></pre>

<p>A k tomu příslušný language server (pro konkrétní jazyky viz <a
href="https://code.claude.com/docs/en/discover-plugins#code-intelligence">dokumentace
pluginů</a>):</p>

<pre><code>npm install -g intelephense
</code></pre>

<p>Mám za to, že LSP aktuálně vyžaduje nastavenou proměnnou prostředí
<code>ENABLE_LSP_TOOL=1</code>. Na Windows:</p>

<pre><code>setx ENABLE_LSP_TOOL 1
</code></pre>

<p>Možná budeš potřebovat přidat npm global bin adresář do systémového
PATH, jinak Claude language server nenajde.</p>

<p>Po restartu se v projektu zeptej: „Ověř, že ti funguje LSP.“ Měl by
potvrdit, že vidí typy a definice.</p>

<p><strong>Windows bez WSL:</strong> Pokud Claude hlásí, že nemůže spustit
language server – globální NPM balíčky na Windows vyžadují příponu
<code>.cmd</code>. Workaround najdeš v <a
href="https://gist.github.com/dg/ea44243f8396315cdfb9064a27f60497">tomto
gistu</a>.</p>

<h2 id="krok-4-hooks-automaticky-linting-bez-premysleni">Krok 4: Hooks –
automatický linting bez přemýšlení</h2>

<p>Hook je automatická akce, která se spustí pokaždé, když Claude provede
určitou operaci. Ten nejužitečnější: <strong>linting po každé změně
souboru</strong>. Claude upraví soubor, hook spustí linter, Claude vidí chyby
a opraví je. Bez tvého zásahu.</p>

<p>Bez hooků: Claude napíše kód, ty otevřeš IDE, IDE podtrhne tři
řádky, řekneš „oprav linting“, on opraví, ty zkontroluješ. S hooky
tohle odpadá.</p>

<p>Nastavení v <code>.claude/settings.json</code>:</p>

<pre
class="language-json"><code>{
  &quot;hooks&quot;: {
    &quot;PostToolUse&quot;: [
      {
        &quot;matcher&quot;: &quot;Edit|Write&quot;,
        &quot;command&quot;: &quot;npm run lint:fix -- ${file}&quot;
      }
    ]
  }
}
</code></pre>

<p>Nahraď <code>npm run lint:fix</code> za linter tvého projektu –
<code>php-cs-fixer</code>, <code>black</code>, <code>rustfmt</code>,
<code>prettier</code>, cokoliv používáš. Jeden hook, deset řádků
konfigurace a Claude dodržuje tvůj coding standard automaticky.</p>

<h2 id="krok-5-skills-opakujici-se-postupy-jako-recepty">Krok 5: Skills –
opakující se postupy jako recepty</h2>

<p>Deploy na staging. Vytvoření migrace. Přidání API endpointu. Code review
checklist. Kolikrát týdně tohle děláš? A kolikrát zapomeneš
jeden krok?</p>

<p><strong>Skills</strong> jsou markdown soubory v <code>.claude/skills/</code>,
které zachycují opakující se postupy jako recepty. Vytvoříš skill
<code>deploy</code>, pak stačí napsat <code>/deploy</code>. Claude si otevře
recept a postupuje podle něj, místo aby vymýšlel postup od nuly.</p>

<pre
class="language-markdown"><code># Deploy na staging

1. Spusť testy: `npm test`
2. Zkontroluj, že všechny prošly
3. Buildni produkční verzi: `npm run build`
4. Pushni do větve `staging`
5. Počkej na CI pipeline
6. Ověř, že staging odpovídá na https://staging.myproject.cz
</code></pre>

<p>Výhoda oproti wiki, kterou nikdo nečte? Claude ten postup skutečně
<strong>provede</strong>. Projde každý krok.</p>

<p><strong>Formalizuj i to, co ti přijde samozřejmé.</strong> Jak
pojmenováváš soubory. Jakou máš adresářovou strukturu. Jak formátuješ
kód. Ty to víš, tvůj tým to ví, ale Claude to neví – bez explicitního
pravidla si domyslí vlastní konvenci.</p>

<p>Nemusíš všechno psát od nuly. Pro Nette Framework existuje sada hotových
skills a hooků v repozitáři <a
href="https://github.com/nette/claude-code">nette/claude-code</a> – moderní
konvence, Latte šablony, NEON konfigurace, coding standard linter pro PHP.
Podobné balíčky vznikají i pro další frameworky.</p>

<h2 id="krok-6-mcp-pluginy-pripojte-clauda-k-prohlizeci-a-sluzbam">Krok 6: MCP
pluginy – připojte Clauda k prohlížeči a službám</h2>

<p>MCP (Model Context Protocol) dává Claudovi nástroje, které sám od sebe
nemá. Otevřít prohlížeč, přečíst mail, podívat se do kalendáře –
to všechno vyžaduje MCP server, který mu tu schopnost zpřístupní.</p>

<p>Pro webový vývoj je naprosto klíčové <strong>Chrome DevTools
MCP</strong>. Claude díky němu vidí prohlížeč – naviguje stránky,
kliká, vyplňuje formuláře, pořizuje screenshoty, analyzuje výkon. Stačí
říct třeba „zkontroluj objednávkový proces“. Aktivuješ ho přes
plugin:</p>

<pre><code>/plugin install chrome-devtools-mcp
</code></pre>

<p>Od Anthropic jsou k dispozici také konektory na <strong>Google
služby</strong> – Gmail, Google Calendar a další. Aktivuješ je přes
Connectors v nastavení na webu Claude.</p>

<p>MCP server přidáš buď přes plugin (jednodušší), nebo ručně
vytvořením souboru <code>.mcp.json</code> v kořenu projektu. Technicky jde
buď o lokální proces (bývá to node, python, php, cokoliv), nebo
o vzdálený server, na který se připojí přes URL. U serverů postavených
na Node.js na Windows bez WSL narazíš na stejný problém jako u LSP
s příponou <code>.cmd</code> – ale Claude Code to umí opravit, když ho
na to upozorníš.</p>

<p><strong>Pozor na pluginy od neznámých autorů.</strong> MCP pluginy
obsahují přímé instrukce pro Claude Code. Plugin od nedůvěryhodného
autora může Claudovi podstrčit škodlivé instrukce. Drž se oficiálních
pluginů z Anthropic marketplace a pluginy třetích stran si projdi, než je
nainstaluješ.</p>

<h2 id="kdy-to-zacne-fungovat-jako-kouzlo">Kdy to začne fungovat jako
kouzlo</h2>

<p>Možná si říkáš: to je spousta nastavování. Nestačí prostě
otevřít Claude Code a začít?</p>

<p>Stačí. Stejně jako stačí programovat v Notepadu bez verzování a bez
testů. Jde to. Nedoporučuju to.</p>

<p>Fígl je v tom, že se kroky navzájem <strong>násobí</strong>. Claude
s CLAUDE.md ví, co má dělat. S testy ví, jestli to udělal správně.
S LSP rozumí kódu na úrovni typů. S hooky dodržuje standard. Se skills
zná postupy. S MCP sahá na reálné služby.</p>

<figure><img src="https://www.umeligence.cz/media/99a44cc9ef.webp" alt=""
width="1400" height="933"></figure>

<p>Odeber cokoliv z toho a kvalita spadne víc, než bys čekal.</p>

<p>A mimochodem – tyhle principy nejsou jen o Claude Code. Vibecoding
v Cursoru, Windsurfu nebo při použití GitHub Copilotu stojí na stejných
základech: kontext projektu, testy, statická analýza, linting. Nástroje se
liší, návyky zůstávají.</p>

<h2 id="na-co-si-dat-pozor-v-praxi">Na co si dát pozor v praxi</h2>

<p><strong>Dávej malé úkoly.</strong> „Přepiš celou aplikaci“ je recept
na katastrofu. Jedna fíčura, jeden konkrétní problém. Čím
konkrétnější zadání, tím lepší výsledek.</p>

<p><strong>Říkej proč, ne jen co.</strong> Rozdíl mezi „přidej
validaci“ a „přidej validaci, protože uživatelé posílají prázdné
formuláře a server padá na NPE“ je při psaní pravidel (například
v CLAUDE.md) obrovský. Claude s kontextem dělá výrazně lepší
rozhodnutí.</p>

<p><strong>Dlouhá konverzace = ztráta kontextu.</strong> Jeden úkol, jedna
konverzace. Pro větší úkoly začni s čistým stolem.</p>

<p><strong>Kontroluj výstup.</strong> Vždycky. Claude občas vytvoří kód,
který projde testy a přitom je architektonicky špatně. Čti, co navrhuje,
než potvrdíš. Pak ho to nech kriticky zhodnotit a třeba celé
přepracovat.</p>

        
			]]></content:encoded>
			<pubDate>Mon, 06 Apr 2026 00:00:00 +0200</pubDate>
		</item>
		<item>
			<title>Moje tatérka prodává účetní software za pajcku</title>
			<link>https://www.umeligence.cz/blog/vibecoding-bezpecnost-aplikaci</link>
			<content:encoded><![CDATA[
			
		<p>Moje tatérka si přes víkend navibecodovala webový účetní systém a
nabízí ho cizím lidem za 50 Kč měsíčně. Tetování dělá krásné. Ale
dát jí místo jehly skalpel a říct „operuj, princip je podobný“? To
asi ne.</p>

<h2 id="co-se-stane-kdyz-vibecodovanou-aplikaci-pustite-mezi-lidi">Co se stane,
když vibecodovanou aplikaci pustíte mezi lidi</h2>

<p>Moje tatérka si vybrala účetnictví. Ale úplně stejný příběh se
denně odehrává u seznamek, e-shopů, CRM systémů, rezervačních
platforem. Fenomén vibe codingu, kdy lidé tvoří aplikace pomocí AI čistě
skrze přirozený jazyk, dal náhle komukoliv pocit, že je vývojář. Jsou ale
věci, které by člověk neměl vyrábět na koleni a prodávat cizím lidem.
Padáky. Jaderné reaktory. A software pracující s jejich daty.</p>

<p><strong>Citlivá data pod ochranou zákona.</strong> Jména, e-maily, adresy,
platební údaje, to všechno spadá pod GDPR. Únik dat z děravé aplikace
znamená pokuty v milionech korun pro provozovatele. A katastrofu pro
zákazníky, jejichž data se octnou na internetu.</p>

<p><strong>Bezpečnost, kterou nevidíte.</strong> Šifrování hesel, ochrana
proti SQL injection, správa přihlášení, zabezpečení API klíčů…
Vibecodovaná aplikace vypadá, že funguje. Ale to, že formulář odesílá
data, neznamená, že je odesílá bezpečně.</p>

<p><strong>Údržba navěky.</strong> Objeví se bezpečnostní díra
v knihovně, kterou AI použila? Změní se legislativa? Kdo to opraví, vy,
kteří kódu nerozumíte?</p>

<figure><img src="https://www.umeligence.cz/media/7a87cfbbf8.webp" alt=""
width="1400" height="933"></figure>

<h2 id="bezpecnostni-katastrofy-ktere-se-uz-staly">Bezpečnostní katastrofy,
které se už staly</h2>

<p>Nemyslím si, že moje tatérka má zlý úmysl. Ale cesta do pekla je, jak
známo, dlážděná dobrými úmysly. A vibe coding z ní udělal
dálnici.</p>

<p><strong>Moltbook</strong>, sociální síť, jejíž zakladatel se chlubil,
že „nenapsal jediný řádek kódu“. Přístupový klíč k databázi byl
viditelný přímo ve zdrojovém kódu stránky. Stačilo zmáčknout
<kbd>F12</kbd>. Výsledek: únik 1,5 milionu přístupových tokenů a
35 000 e-mailových adres.</p>

<p><strong>Tea</strong>, seznamovací aplikace pro ženy, navržená jako
bezpečný prostor. Databáze měla nulové zabezpečení. Vůbec žádné.
Výsledek: únik 72 000 fotografií včetně 13 000 občanských průkazů a
pasů, plus přes milion soukromých konverzací. Data skončila na 4chanu.
Ženy, které aplikaci používaly, aby se chránily před nebezpečnými lidmi,
měly najednou své identity volně přístupné přesně těm, před kterými
utíkaly.</p>

<p><strong>Enrichlead</strong>, platforma kompletně napsaná v AI editoru
Cursor, jak zakladatel hrdě oznamoval. Do 72 hodin od spuštění ji někdo
kompletně obešel. Veškeré zabezpečení běželo jen v prohlížeči.
Stačilo ve vývojářských nástrojích změnit jednu hodnotu a měli jste
plný přístup ke všemu zdarma.</p>

<p>To nejsou ojedinělé příběhy. <a
href="https://www.veracode.com/blog/ai-generated-code-security-risks/">Studie
společnosti Veracode</a> testovala kód generovaný <a
href="https://www.umeligence.cz/chatgpt-do-hloubky">jazykovými modely</a> a zjistila, že <strong>45 %
kódu generovaného umělou inteligencí obsahuje bezpečnostní chyby</strong>.
AI generovaný kód má téměř třikrát více zranitelností než kód psaný
lidmi. Důvod je prostý: AI optimalizuje kód tak, aby fungoval, ne aby byl
bezpečný. Bezpečnostní kontrola je pro ni jen další chyba bránící
spuštění programu. A chyby se přece opravují, že.</p>

<div data-id="promo"></div>

<h2
id="dunning-krugeruv-efekt-ve-vibe-codingu-proc-nevidite-to-co-nevidite">Dunning-Krugerův
efekt ve vibe codingu: proč nevidíte to, co nevidíte</h2>

<p>Moje tatérka není hloupá. Naopak, je šikovná a podnikavá. Právě proto
je to tak nebezpečné. Má za sebou úspěšný víkend s AI, aplikace
běží, vypadá profesionálně. Proč by měla pochybovat?</p>

<p>Protože nevidí, co chybí:</p>

<ul>
	<li>Hesla uživatelů uložená v databázi jako holý text bez
	šifrování.</li>

	<li>Přístupové klíče viditelné přímo v kódu stránky.</li>

	<li>Nulová ochrana proti vložení škodlivých příkazů do
	formulářů.</li>

	<li>Žádné zálohování dat.</li>

	<li>Správa přihlášení děravá jako cedník.</li>
</ul>

<p>Sám přes dvacet let <a href="https://www.umeligence.cz/david-grudl">vyvíjím open source
software</a>, na kterém běží statisíce webů. Vím, kolik neviditelné
práce se skrývá v tom, aby aplikace nebyla jen funkční, ale i bezpečná.
To, co vidíš na obrazovce, je deset procent ledovce. Zbytek je pod hladinou, a
právě tam číhají problémy.</p>

<p>Andrej Karpathy, jeden z tvůrců GPT v OpenAI a autor samotného pojmu
vibe coding, k tomu dodává: <em>„Naše práce se přesouvá od psaní kódu
k jeho kontrole. Je to jako s praktikanty: nepustíte je do produkce bez
důkladného prověření.“</em></p>

<p>Jenže kdo prověřuje kód mojí tatérce?</p>

<h2 id="co-si-muzete-navibecodovat-bezpecne-a-kde-konci-legrace">Co si můžete
navibecodovat bezpečně – a kde končí legrace</h2>

<p>Nechci být protivný profík, který říká „nesahejte na to“. Vibe
coding je fascinující a demokratizace tvorby softwaru je skvělá věc. Ale je
potřeba vědět, kde končí pískoviště a začíná dálnice.</p>

<p><strong>Směle do toho:</strong></p>

<ul>
	<li>Osobní web, portfolio, blog, žádná databáze, žádné riziko</li>

	<li>Kalkulačka, plánovač, poznámky pro vlastní potřebu</li>

	<li>Prototyp nápadu pro ověření zájmu (ne pro ostrý provoz!)</li>
</ul>

<p><strong>Tady už potřebuješ profíka:</strong></p>

<ul>
	<li>Aplikace s přihlašováním uživatelů</li>

	<li>Cokoliv s platbami</li>

	<li>Cokoliv pracující s osobními nebo finančními daty cizích lidí</li>

	<li>Cokoliv, co chceš prodávat jako placenou službu</li>
</ul>

<p>Máš skvělý nápad na produkt? Navibecoduj si prototyp. Ukaž ho lidem.
Ověř, jestli o to někdo stojí. A pak si najdi vývojáře, který z toho
udělá bezpečný produkt. Prototyp ti ušetří měsíce práce a spoustu
peněz. Jak na to, i když jsi nikdy neprogramoval? <a
href="https://www.umeligence.cz/blog/claude-code-pro-lidi-co-neprogramuji">Tady je návod na první
kroky</a>.</p>

<p>A pokud jsi na druhé straně, jako zákazník: všímej si ceny. <a
href="https://www.fakturoid.cz">Fakturoid</a> si za nejlevnější tarif
účtuje 399 Kč, a za těmi penězi stojí tým vývojářů, bezpečnostních
expertů a účetních poradců. Když někdo nabízí totéž za cenu flat
white v kavárně, není to výhodná koupě. Je to varování.</p>

<h2 id="zpatky-k-jehle-a-skalpelu">Zpátky k jehle a skalpelu</h2>

<p>Vibe coding je fantastický nástroj. Ale fantastický nástroj
v nesprávných rukou a pro nesprávný účel je recept na průšvih. Až
něčí zákazníci zjistí, že jim unikla data nebo že jejich účty napadl
útočník, nebude to vina umělé inteligence. Bude to vina člověka, který
si myslel, že umí něco, co neumí.</p>

<p>Skalpel totiž neřeže líp jen proto, že ho držíte s větším
sebevědomím :-)</p>

<h2 id="casto-kladene-otazky-o-vibe-codingu-a-bezpecnosti-faq">Často kladené
otázky o vibe codingu a bezpečnosti (FAQ)</h2>

<p><strong>Je vibe coding nebezpečný?</strong> Sám o sobě ne. Je to
skvělý nástroj pro prototypy, osobní projekty a učení. Nebezpečným se
stává ve chvíli, kdy vibecodovanou aplikaci bez odborného bezpečnostního
auditu nasadíte do provozu a nabízíte cizím lidem, zvlášť pokud pracuje
s citlivými daty.</p>

<p><strong>Mohu si navibecodovat vlastní web?</strong> Rozhodně. Osobní web,
blog nebo portfolio patří mezi nejbezpečnější věci k vibecodování.
Neobsahují databázi s cizími daty, nemají přihlašování a nemají
prakticky žádný prostor pro útok. Pusťte se do toho.</p>

<p><strong>Kolik stojí vývoj profesionálního softwaru pracujícího s daty
uživatelů?</strong> Záleží na složitosti, ale i jednoduchá aplikace
s přihlašováním a platbami vyžaduje bezpečnostní audity, právní
konzultace a průběžnou údržbu. Proto profesionální řešení jako
Fakturoid nebo iDoklad stojí stovky korun měsíčně, za těmi penězi stojí
lidé, kteří za produkt skutečně ručí.</p>

        
			]]></content:encoded>
			<pubDate>Tue, 31 Mar 2026 00:00:00 +0200</pubDate>
		</item>
		<item>
			<title>Prvních 30 minut s Claude Code (pro lidi, co nikdy neprogramovali)</title>
			<link>https://www.umeligence.cz/blog/claude-code-pro-lidi-co-neprogramuji</link>
			<content:encoded><![CDATA[
			
		<p>Tři nastavení, bez kterých je Claude Code jako taxikář bez adresy.
Tenhle článek je tvůj checklist pro první půlden vibecodingu: nastavíš si
CLAUDE.md, zapneš Git jako záchrannou síť a naučíš se zadávat úkoly
tak, aby je Claude splnil správně. Předpokládám, že Claude Code už máš
nainstalovaný. Pokud ne, tady je <a
href="https://www.umeligence.cz/blog/jak-nainstalovat-claude-code">návod na instalaci</a>.</p>

<h2 id="jak-nastavit-claude-md-soubor-ktery-zmeni-vsechno">Jak nastavit
CLAUDE.md: soubor, který změní všechno</h2>

<p>Tohle je ta nejdůležitější věc hned na začátek.</p>

<p>CLAUDE.md je textový soubor, který si Claude Code přečte pokaždé, když
s ním začneš pracovat. Představ si ho jako briefing, který dáš novému
kolegovi první den v práci. Bez něj bude Claude předpokládat, že jsi
programátor, a bude se podle toho chovat. Bude mluvit v technickém žargonu a
nebude dostatečně vysvětlovat co dělá. Což je recept na katastrofu, když
nerozumíš kódu.</p>

<p>Řekni Claude Code:</p>

<pre
class="language-text"><code>Do globálního CLAUDE.md si zapiš toto:

# Kdo jsem
Jsem neprogramátor. Neumím číst ani kontrolovat kód.
Všechna technická rozhodnutí mi vysvětli jednoduše, bez žargonu.
Když se mám rozhodnout, ukaž možnosti s pro/proti a doporuč jednu.

# Jak pracuj
- Po každé změně shrň, co jsi udělal a proč, lidsky
- Když se něco rozbije, nejdřív vysvětli co se stalo, pak navrhuj opravu
- Do kódu piš komentáře vysvětlující, co která část dělá
- Změny dělej malé, jednu po druhé

# Bezpečnost
- Neměň architekturu bez mého souhlasu
- Nemaž soubory bez ptaní
- U úkolů s víc kroky mi nejdřív ukaž plán a počkej na schválení
</code></pre>

<p>A průběžně si CLAUDE.md vylaďuj. Všimneš si, že Claude opakovaně
dělá něco, co nechceš? Řekni mu, ať do CLAUDE.md zapíše nové pravidlo.
Tenhle soubor je živý dokument, ne náhrobní kámen.</p>

<p>Ale měj na paměti tohle:</p>

<ul>
	<li><strong>Stručnost.</strong> CLAUDE.md musí být krátký. Když napíšeš
	román, důležité pokyny se ztratí v šumu. Každý řádek si musí
	zasloužit místo. Ptej se: udělal by Claude bez tohoto řádku chybu?</li>

	<li><strong>Konkrétnost.</strong> „Buď opatrný“ Claude ignoruje.
	„Nemaž soubory bez ptaní“ respektuje. Piš příkazy, ne
	zbožná přání.</li>
</ul>

<figure><img src="https://www.umeligence.cz/media/85b6fe18e8.webp" alt=""
width="1400" height="933"></figure>

<h2 id="git-zachranna-sit-bez-ktere-nezacinej">Git: Záchranná síť, bez
které nezačínej</h2>

<p>Git je věc, bez které Claude Code na Windows vůbec nefunguje. Měl by být
součástí instalace, ale pokud ho ještě nemáš, vrať se k <a
href="https://www.umeligence.cz/blog/jak-nainstalovat-claude-code">návodu na instalaci</a>. A pak
řekni Claudovi tohle:</p>

<blockquote>
	<p>„Inicializuj Git repozitář a po každé větší změně vytvoř commit
	se srozumitelnou zprávou v češtině.“</p>
</blockquote>

<p>Nemusíš vědět, co je Git. Nemusíš umět žádné příkazy. Stačí
vědět jedno: Git si pamatuje každou verzi tvých souborů. Je to jako
nekonečné undo, ale pro celý projekt. Když Claude jednoho dne něco
rozbije – a rozbije, to ti garantuju –, řekneš mu „vrať to zpátky,
včera to fungovalo“ a on to vrátí.</p>

<p>Bez Gitu bys plakal. S Gitem řekneš „ups“ a jedeš dál.</p>

<p>A pokud chceš tu historii změn i vidět, ne jen vědět, že existuje,
nainstaluj si <a href="https://desktop.github.com">GitHub Desktop</a>. Je to
aplikace, ve které přehledně uvidíš, co se kdy změnilo, a můžeš se ke
starší verzi vrátit jedním kliknutím.</p>

<div data-id="promo"></div>

<h2 id="zacni-s-necim-malym-ne-s-aplikaci-snu">Začni s něčím malým, ne
s aplikací snů</h2>

<p>Vím, je to lákavé. Máš k dispozici mocný nástroj a chceš rovnou
postavit web nebo aplikaci, o které přemýšlíš už půl roku. Ale stejně
jako bys poprvé za volantem nejel rovnou na dálnici, začni na
parkovišti.</p>

<p>Tři skvělé první úkoly:</p>

<p><strong>Organizace souborů:</strong></p>

<blockquote>
	<p>„Přejmenuj všechny fotky v téhle složce podle data pořízení,
	formát YYYY-MM-DD.“</p>
</blockquote>

<p><strong>Analýza dat:</strong></p>

<blockquote>
	<p>„Otevři tenhle CSV soubor a řekni mi, kolik je tam unikátních
	emailových adres a které se opakují nejčastěji.“</p>
</blockquote>

<p><strong>Jednoduchá automatizace:</strong></p>

<blockquote>
	<p>„Projdi tuhle složku, všechny PDF slouč do jednoho souboru, seřazené
	podle názvu.“</p>
</blockquote>

<figure><img src="https://www.umeligence.cz/media/77698ab72a.webp" alt=""
width="1400" height="933"></figure>

<p>Proč zrovna tohle? Protože u malých úkolů <em>okamžitě vidíš
výsledek</em>. Fotky se přejmenovaly? Funguje. CSV má 342 emailů? Můžeš
si to ověřit ručně. Tohle je tvůj kalibrační test. Zjistíš, jak Claude
přemýšlí, jak reaguje na tvoje instrukce a jak moc musíš kontrolovat
výsledek.</p>

<p>(Spoiler: kontrolovat musíš vždycky. Ale o tom za chvíli.)</p>

<p>Víc ukázek najdeš v článku <a href="https://www.umeligence.cz/blog/co-umi-claude-code">Co
všechno umí Claude Code</a>, zdaleka to není jen programování.</p>

<h2 id="jak-claudovi-zadavat-ukoly-aby-je-splnil-spravne">Jak Claudovi zadávat
úkoly, aby je splnil správně</h2>

<p>Stačí jeden vzorec:</p>

<p><strong>Co chci</strong> + <strong>Proč to chci</strong> + <strong>Jaká
mám omezení</strong></p>

<p>Špatně:</p>

<blockquote>
	<p>„Udělej mi web.“</p>
</blockquote>

<p>Dobře:</p>

<blockquote>
	<p>„Chci jednoduchou stránku pro mou cukrárnu, protože zákazníci pořád
	volají a ptají se na ceny, místo aby se podívali online. Úvodní strana
	s fotkami dortů, ceník a kontaktní formulář. Použij čisté HTML, CSS a
	JavaScript, žádné frameworky. Design světlý, moderní, přizpůsobený
	mobilům.“</p>
</blockquote>

<p>První zadání je jako říct taxikáři „jeď“. Druhé mu dává
adresu, kudy nechceš jet, a že máš alergii na osvěžovač ve tvaru
stromečku.</p>

<p>Pár tipů, co fungují:</p>

<ul>
	<li><strong>Říkej proč, ne jen co.</strong> „Přidej kontrolu toho
	políčka“ je horší než „Přidej kontrolu telefonního čísla, protože
	lidi píšou nesmysly a pak se diví, že jim nepřišla odpověď.“ Když
	Claude zná důvod, udělá lepší rozhodnutí.</li>

	<li><strong>Jeden úkol = jedna konverzace.</strong> Nemíchej „oprav
	formulář“ s „a taky změň barvy a přidej blog“. Když do jedné
	konverzace nacpeš všechno, Claude se v tom začne motat jako turista
	v pražských uličkách. Nemusíš kvůli tomu Claude Code zavírat a
	spouštět znovu – stačí napsat <code>/clear</code> a začneš s čistým
	stolem.</li>

	<li><strong>Když nerozumíš, ptej se.</strong> „Vysvětli mi to jednoduše a
	doporuč, co mám vybrat.“ Nemusíš rozumět kódu. Musíš rozumět
	rozhodnutím, která se dělají.</li>
</ul>

<h2 id="jakou-technologii-zvolit-kdyz-budes-stavet-web">Jakou technologii
zvolit, když budeš stavět web</h2>

<p>Když vibecoduješ, AI generuje dramaticky lepší kód pro některé
technologie než pro jiné, prostě proto, že se na nich víc naučila. Tahle
volba má obrovský dopad na výsledek a neprogramátor ji nemůže udělat
informovaně. Tak tady je tahák:</p>

<p><strong>Jednoduchý web bez přihlašování</strong> (vizitka, portfolio,
landing page):</p>

<p>→ „Použij čisté HTML, CSS a JavaScript. Bez buildování, žádný
npm.“<br>
→ Výsledek hostuj zdarma na Netlify. Nula konfigurace, nula korun.</p>

<p><strong>Interaktivní nástroj jen pro sebe</strong> (dashboard, kalkulačka,
zpracování dat):</p>

<p>→ „Použij Python.“<br>
→ Python je nejčitelnější programovací jazyk a AI v něm generuje
skvělý kód. Pro osobní nástroje úplně stačí. Claude ti vytvoří
skript, který spustíš jedním příkazem.<br>
→ Řekni Claude Code: „Nainstaluj mi nejnovější Python a nastav ho tak,
aby fungoval s češtinou.“ Claude pozná tvůj operační systém a
nainstaluje ho správně.</p>

<p><strong>Cokoliv s uživateli, platbami nebo citlivými daty:</strong></p>

<p>→ Najmi vývojáře. Vážně. Tohle není oblast na experimenty.</p>

<h2 id="kdy-to-muzes-dat-na-internet-a-kdy-radsi-ne">Kdy to můžeš dát na
internet – a kdy radši ne</h2>

<p>Tady přichází otázka, která odděluje nevinné hraní od reálného
průšvihu: <strong>používá tvoje aplikace databázi?</strong></p>

<p>Pokud všechna data žijí v prohlížeči (na počítači toho, kdo
aplikaci otevře), nemáš se čeho bát. Nikdo cizí se k nim nedostane,
protože nikde jinde nejsou. Osobní web, portfolio, kalkulačka, plánovač,
poznámkovník, to všechno klidně hoď na internet. I kdyby kód nebyl
dokonalý, nejhorší, co se stane, je, že stránka bude ošklivá.</p>

<p>Jakmile ale do hry vstoupí databáze (a tím myslím jakýkoliv server, kam
se ukládají data od uživatelů), situace se radikálně mění. Databáze je
sejf. A stavět sejf bez toho, abys rozuměl bezpečnosti, je jako hrát si se
střelným prachem podle návodu na YouTube.</p>

<p>Co se může stát? Nic teoretického. Seznamovací aplikace Tea byla
vytvořená neprogramátory. Únik 72 000 fotek občanských průkazů
skončil na 4chanu. Sociální síť Moltbook se chlubila tím, že „nezapsali
jediný řádek kódu“. Přístupový klíč k databázi byl viditelný
přímo ve zdrojovém kódu stránky. Stačilo zmáčknout F12.</p>

<figure><img src="https://www.umeligence.cz/media/71df6f67af.webp" alt=""
width="1400" height="933"></figure>

<p>Takže jednoduchý tahák:</p>

<p><strong>Klidně dej online:</strong></p>

<ul>
	<li>Osobní web, portfolio, blog</li>

	<li>Kalkulačka, plánovač, poznámky, cokoliv, kde data zůstávají
	v prohlížeči</li>

	<li>Prototyp pro ověření nápadu (ale ne pro ostrý provoz s reálnými
	uživateli)</li>
</ul>

<p><strong>Tady potřebuješ vývojáře:</strong></p>

<ul>
	<li>Cokoliv s přihlašováním uživatelů</li>

	<li>Cokoliv s platbami</li>

	<li>Cokoliv, kde ukládáš data cizích lidí do databáze</li>

	<li>Cokoliv, co chceš prodávat jako službu</li>
</ul>

<p>Máš nápad na aplikaci s databází? Skvělé. Navibecoduj si prototyp,
ukaž ho lidem, ověř, jestli o to někdo stojí. A pak si najdi vývojáře,
který z toho udělá bezpečný produkt. Prototyp ti ušetří měsíce
vysvětlování a spoustu peněz. Ale nikdy ho nenasazuj do ostrého provozu
tak, jak vypadl z AI.</p>

<h2 id="kolik-to-stoji-a-muze-mi-to-rozbit-pocitac">Kolik to stojí a může mi
to rozbít počítač?</h2>

<p><strong>Peníze.</strong> Claude Code funguje v rámci předplatného <a
href="https://www.umeligence.cz/chatboti#claude">Claude</a>, což je konkurent ChatGPT. Základní plán
Pro za 20 dolarů měsíčně ti dá prostor to osahat. Přihlásíš se a
jedeš. Když začneš stavět něco většího, narazíš na strop, ale neboj,
Claude ti řekne něco jako „pokračujeme za dvě hodiny“ a po pauze jedeš
dál. Pokud ti čekání nevyhovuje, je tu tarif Max za 100 dolarů. Pořád
nic oproti sazbám programátorů.</p>

<p><strong>Bezpečnost tvého počítače.</strong> Claude Code pracuje přímo
s tvými soubory, to je fakt. Než udělá cokoliv destruktivního, ukáže ti,
co chce provést, a čeká na tvoje schválení. Ale to neznamená, že můžeš
odklikávat všechno poslepu. Čti, co ti navrhuje. Nerozumíš tomu? Zeptej se
ho, co přesně ta změna udělá.</p>

        
			]]></content:encoded>
			<pubDate>Tue, 31 Mar 2026 00:00:00 +0200</pubDate>
		</item>
		<item>
			<title>Proč vývojáři kritizují AI, která už neexistuje</title>
			<link>https://www.umeligence.cz/blog/ai-pro-vyvojare-kritika</link>
			<content:encoded><![CDATA[
			
		<p>Narážím na příspěvky vývojářů, kteří popisují svou frustrující
zkušenost s AI. A jsou to reálné zkušenosti – to jim neberu. Problém
je, že tihle lidé popisují nástroj, který už v téhle podobě prakticky
neexistuje.</p>

<p>Vzniká tím nebezpečná past: Vyzkoušíš AI špatně → nefunguje →
utvrdíš se, že to je hračka → přestaneš sledovat vývoj. A pak z této
pozice chodíš pod příspěvky ostatních a vysvětluješ jim, že jsou
hlupáci. Svět se ale mezitím posune o několik generací dál a ty sis toho
nevšiml, protože jsi byl zaneprázdněn psaním komentářů.</p>

<h2 id="co-se-zmenilo">Co se změnilo?</h2>

<p>Většina kritiků popisuje workflow z roku 2024: otevřu chatovací okno,
napíšu co chci, doufám. Marně.</p>

<p>Dnešní <a href="https://www.umeligence.cz/ai-agenti">AI agenti</a> fungují úplně jinak. Agent
sedí přímo ve tvém projektu. Čte celou kódovou základnu, spouští testy,
používá git. Když udělá změnu, sám si ověří, jestli prošla buildem.
Konvence má zapsané v konfiguračním souboru, který načte při každém
startu. Na formátování kódu nepotřebuje „hádání" – pouští
reálný formátovač jako hook po každé změně. Složitý úkol rozloží na
podúkoly a deleguje je subagentům běžícím paralelně.</p>

<h2 id="ale-neni-to-zadarmo">Ale není to zadarmo</h2>

<p>Tenhle posun vyžaduje nový typ dovednosti. Potřebuješ vědět, jak agenta
nakonfigurovat. Jak mu předat architekturní pravidla projektu, jak aktivovat
LSP, jak nastavit hooky na linter a formátovač, jak nastartovat agentův loop
tak, aby pracoval iterativně a ne naslepo. Je to skill, který se dá naučit,
ale hlavně o něm musíš vůbec vědět.</p>

<div data-id="promo"></div>

<p>Představ si, že juniorovi první den v práci pošleš email „předělej
tohle a tohle" a odejdeš na oběd. Versus si s ním sedneš, dáš mu kontext
a necháš ho pracovat po krocích. Stejný junior, dramaticky odlišný
výsledek.</p>

<p>S AI je to totéž. Jen je neskutečně rychlá a nikdy se neurazí, když
jí řekneš, že to má celé předělat.</p>

<h2 id="znamena-to-ze-ai-je-dokonala">Znamená to, že AI je dokonalá?</h2>

<p>Ne! To přece neříkám. Revize kódu je pořád nutná. U složitých
architektonických rozhodnutí AI stále potřebuje lidské vedení.</p>

<p>Ale senior, který umí AI řídit, bude dramaticky produktivnější než
senior, který ji odmítá.</p>

<p>…protože si jednou spálil prsty s chatovacím oknem v roce 2024.</p>

<p>Štěstí přeje připraveným. Znamená to průběžně sledovat, jak se
nástroje vyvíjejí. A <a href="https://www.umeligence.cz/kurz-vibecoding">naučit se je
používat</a>.</p>

        
			]]></content:encoded>
			<pubDate>Thu, 19 Mar 2026 00:00:00 +0100</pubDate>
		</item>
		<item>
			<title>„Co o mně víš?“ zeptal jsem se ChatGPT. Odpověď mě vyděsila.</title>
			<link>https://www.umeligence.cz/blog/co-vsechno-o-vas-vi-chatbot</link>
			<content:encoded><![CDATA[
			
		<p>Znáš tu kavárnu, kam chodíš každé ráno, a barista ti bez ptaní
začne dělat tvoje obvyklé flat white s ovesným mlékem? Příjemné, že?
Teď si představ, že ten barista má taky poznámku, že chodíš každý
pátek nejistým krokem, protože to ve čtvrtek nezvládáš s vínem, a že
jsi minulý týden zíral na výstřih blondýně u okna. A tuhle poznámku ti
nikdy neukáže.</p>

<p>Přesně takhle dnes fungují AI asistenti s pamětí.</p>

<figure><img src="https://www.umeligence.cz/media/b21788007c.webp" alt=""
width="1400" height="933"></figure>

<p>Když jsem <a href="https://www.umeligence.cz/blog/chatgpt-memory">poprvé</a> psal o paměti
ChatGPT, byl to takový roztomilý zápisníček. Dnes má paměť každý
velký <a href="https://www.umeligence.cz/chatboti">AI asistent</a>: ChatGPT, Claude, Gemini, Copilot
i Perplexity. Ale přístupy se zásadně liší. Od průhledného deníčku,
do kterého si můžeš sám škrtat, až po profil, který o tobě vzniká na
pozadí a ty se k němu běžně nedostaneš. Rozdíl mezi nimi není v tom,
co si pamatují. Je v tom, co ti z toho ukáží.</p>

<h2 id="proc-si-ai-asistenti-vubec-neco-pamatuji">Proč si AI asistenti vůbec
něco pamatují</h2>

<p>Jazykový model je z podstaty bezstavový. Každá konverzace začíná od
absolutní nuly, jako kdybys pokaždé potkal člověka s totální amnézií,
který má ale doktorát ze všeho. Chatbot bez paměti je zlatá rybka
s encyklopedickými znalostmi: ví všechno možné o světě, ale netuší,
že s tebou mluvil před pěti minutami.</p>

<p>Paměť, o které se tady bavíme, není součást modelu samotného. Je to
přidaná vrstva, něco jako lepicí papírky nalepené na monitoru, které si
model přečte pokaždé, než ti odpoví. A každý výrobce si ty papírky
organizuje po svém. Pokud tě zajímá, jak to funguje pod kapotou, mrkni na <a
href="https://www.umeligence.cz/chatgpt-zaklady#pamet">první kroky s ChatGPT</a>.</p>

<h2 id="zapisnicek-denik-nebo-babisova-slozka">Zápisníček, deník, nebo
Babišova složka?</h2>

<p>Tři velcí hráči, tři úplně odlišné filozofie.</p>

<figure><img src="https://www.umeligence.cz/media/ed4aef99b4.webp" alt=""
width="1400" height="933"></figure>

<p><strong>ChatGPT: sběratel dat.</strong> OpenAI to vzala ve velkém.
Viditelná část paměti je zápisníček: krátké poznámky, které si
ChatGPT ukládá během konverzace. Vejde se tam asi 6 000 tokenů (token je
zhruba kousek slova, představ si dvě stránky A4 textu). Tuhle část najdeš
v <em>Nastavení → Personalizace → <a
href="https://help.openai.com/en/articles/8590148-memory-faq">Spravovat
paměť</a></em> a každou poznámku můžeš smazat.</p>

<p>Jenže to je jen špička ledovce. Od dubna 2025 totiž ChatGPT umí sám
vytahovat kontext z celé tvé historie konverzací – nemusíš nic hledat,
on si vzpomene za tebe. Ovládá to přepínač <em>Odkazovat se na historii
chatu</em>. A bezpečnostní výzkumník <a
href="https://embracethered.com/blog/posts/2025/chatgpt-how-does-chat-history-memory-preferences-work/">Johann
Rehberger zjistil</a>, co přesně se pod tím skrývá: ChatGPT si z tvých
konverzací sestavuje podrobný profil, který se přikládá ke každému
novému chatu. Najdeš v něm odvozené preference (i s poznámkou, nakolik
si jimi je jistý), shrnutí témat, o kterých ses bavil, osobní údaje jako
jméno nebo profesi, souhrn posledních zhruba čtyřiceti konverzací, a
dokonce i technické údaje: typ zařízení, rozlišení obrazovky nebo to,
jestli používáš tmavý režim.</p>

<p>Tenhle profil si ale <strong>nikde nezobrazíš ani neupravíš</strong>.
V nastavení ho nenajdeš. Můžeš se ChatGPT zeptat „co o mně víš?“ a
on ti kus profilu prozradí. <a
href="https://simonwillison.net/2025/May/21/chatgpt-new-memory/">Simon
Willison</a> to udělal a výsledek nazval „memory dossier“, tedy
v podstatě paměťový spis. Na rozdíl od správy paměti, kde máš
u každého záznamu tlačítko na smazání, tady nemáš nic.</p>

<p><strong>Claude: průhledný deníček.</strong> Anthropic zvolila opačný
přístup. Claude jednou za <a
href="https://support.claude.com/en/articles/11817273-use-claude-s-chat-search-and-memory-to-build-on-previous-context">čtyřiadvacet
hodin</a> zpracuje konverzace do čitelného souhrnu, který najdeš
v <em>Settings → Capabilities → View and edit memory</em>. Klíčové
slovo: <em>edit</em>. Můžeš tam přepisovat, mazat, doplňovat, a změny se
projeví okamžitě, bez čekání na další den. Žádný skrytý druhý
level. Co je v paměti, to vidíš. Tečka. Claude se záměrně soustředí na
pracovní kontext. Paměť mají k dispozici všichni uživatelé, i ti na
bezplatném plánu. A na placených plánech Claude navíc umí prohledávat
i tvoje minulé konverzace a dokáže si dohledat kontext, i když si ho
předtím cíleně neuložil.</p>

<p><strong>Gemini: hra s celým ekosystémem.</strong> Google má takzvané
Saved Info, ručně uložené poznámky, které si <a
href="https://support.google.com/gemini/answer/16413516">prohlédneš a
spravuješ</a>, a vedle toho automaticky sestavovaný profil, kterému říká
<em>user context</em>. Ten si nezobrazíš. Hlavní zbraň je ale funkce
Personal Intelligence, spuštěná v lednu 2026: napojení na Gmail, Fotky,
YouTube a Vyhledávání. Žádný jiný chatbot tohle nabídnout nemůže.
Google o tobě ví všechno už dávno – Gemini to teď jen umí použít.
Personalizace z historie konverzací se <a
href="https://9to5google.com/2026/02/26/gemini-past-chats-free/">postupně
rozšiřuje</a> i na bezplatné uživatele, ale v Evropě zatím není
dostupná.</p>

<p><strong>Copilot</strong> od Microsoftu <a
href="https://support.microsoft.com/en-us/topic/microsoft-copilot-privacy-controls-8e479f27-6eb6-48c5-8d6a-c134062e2be6">nabízí</a>
přepínače pro personalizaci a trénování na konverzacích. Co se
průhlednosti týče, je na tom podobně jako ChatGPT: můžeš se zeptat „co
o mně víš?“ a dostaneš shrnutí, ale nemáš k dispozici kompletní
výpis. Na druhou stranu nemá tak agresivní profilování z historie.</p>

<p><strong>Perplexity</strong> si zaslouží zvláštní zmínku, protože jako
jediný ti u odpovědi ukáže, které konkrétní vzpomínky ji ovlivnily.
Takovou průhlednost by si ostatní mohli vzít za vzor.</p>

<p>A <strong>Grok</strong> od xAI? Ten má paměť v zárodku, ale v EU je
celý nedostupný, víc o tom v mém postu o <a
href="https://www.umeligence.cz/blog/not-available-in-europe">nedostupných funkcích v Evropě</a>.</p>

<h2 id="jak-smazat-pamet-chatgpt-a-dalsich">Jak smazat paměť ChatGPT a
dalších</h2>

<p>Dobrou zprávou je, že u většiny služeb máš nad pamětí kontrolu. Jen
musíš vědět, kde hledat. Dobrý začátek: zeptej se svého chatbota
<em>„Co o mně víš?“</em> a <strong>podívej se do nastavení</strong>,
možná budeš překvapený.</p>

<p>U ChatGPT hledej v <em>Nastavení → Personalizace → Spravovat
paměť</em>. Přepínač <em>Odkazovat se na historii chatu</em> ovládá ten
profil z historie. A pro konverzace, které nemají zanechat stopu, tu je <a
href="https://chatgpt.com/?temporary-chat=true">dočasný chat</a>, najdeš ho
v horním menu při založení nové konverzace. U Clauda je to <em>Settings
→ Capabilities → View and edit memory</em>. U Gemini najdeš ruční
poznámky pod <em>Saved Info</em> a automatickou paměť pod <em>Personal
context</em>. U Copilotu hledej v <em>Settings → Personalization</em>.</p>

<p>Zajímavý trend je oddělení paměti po projektech. ChatGPT Projects
i Claude Projects ti umožňují vytvořit samostatné pracovní prostory, kde
si AI pamatuje jen to, co se týká konkrétního projektu, a nic z toho
nepřeteče do tvého hlavního profilu. To se hodí, když nechceš, aby se
tvoje pracovní konverzace o firemní strategii mísily s nočním
filozofováním o smyslu života.</p>

<div data-id="promo"></div>

<p>Claude navíc spustil pokusný <a
href="https://support.claude.com/en/articles/12123587-import-and-export-your-memory-from-claude">přenos
paměti</a>: možnost převzít si paměť z jiného AI asistenta. Zatím je to
v rané fázi a nemusí to vždy fungovat, ale směr je správný: pokud je
tvůj AI profil cenný, měl bys ho mít pod kontrolou celý, včetně toho, kam
s ním půjdeš.</p>

<h2 id="kdo-je-tu-pro-tebe">Kdo je tu pro tebe</h2>

<p>Slyším námitku: „Ale mně ta personalizace pomáhá. Proč bych se měl
bát?“ Nemusíš. Personalizace je skvělá věc, pokud víš, co do ní
vstupuje. Nejde o to, že si AI pamatuje tvoje zvyky. Jde o to, jestli máš
nástroj, kterým si ověříš, co přesně o tobě ví. ChatGPT a Copilot
vyměňují soukromí za pohodlí, Claude dává kontrolu za cenu menší
„magičnosti“, Gemini sází na to, že Google o tobě ví všechno už
dávno. Za mě? Dávám přednost tomu, abych věděl, s čím pracuji. Radši
barista, který mi zápisník ukáže, než ten, co mě zná dokonale – ale
odmítne prozradit, jak na to přišel :-)</p>

<p><em>PS: Chování jednotlivých služeb se liší podle toho, jestli
používáš bezplatný, osobní nebo firemní plán, a samozřejmě se může
kdykoli změnit. Neměl jsem možnost otestovat každou kombinaci. Pokud
narazíš na nepřesnost, <a href="https://www.umeligence.cz/david-grudl">napiš mi</a>, rád to
opravím.</em></p>

        
			]]></content:encoded>
			<pubDate>Tue, 17 Mar 2026 00:00:00 +0100</pubDate>
		</item>
		<item>
			<title>Víš, co znamená ChatGPT? Ani OpenAI si není jistá</title>
			<link>https://www.umeligence.cz/blog/chatgpt-neuveritelny-pribeh-nazvu</link>
			<content:encoded><![CDATA[
			
		<p>Původně se to mělo jmenovat „Chat with GPT-3.5“.</p>

<p>Vážně. Název změnili noc před spuštěním, protože si uvědomili, že
se to špatně vyslovuje a zní to jako náhodně vygenerované heslo do Wi-Fi.
Žádná brandingová agentura, žádný průzkum trhu. Jen pár unavených
inženýrů, kteří <strong>ani nevěřili</strong>, že by jejich výtvor
kohokoliv zaujal.</p>

<p>Ale to je teprve začátek. Název ChatGPT se skládá ze slova <em>Chat</em>
a zkratky <em>GPT</em>, a každé z těch písmen skrývá překvapivý
příběh. <strong>T</strong> jako <em>Transformer</em>, architektura, která se
málem jmenovala CargoNet. <strong>P</strong> jako <em>pre-trained</em>, slovo,
které dnes znamená pravý opak toho, čím je chatbot. <strong>G</strong> jako
<em>generative</em>, sázka, o které si většina výzkumníků myslela, že
je slepá ulička. A samotná zkratka GPT? Ta se v původní studii o GPT
vůbec neobjevila.</p>

<p>Pojďme si projít název, který vyslovují miliony lidí denně, a skoro
nikdo netuší, co vlastně říká.</p>

<figure><img src="https://www.umeligence.cz/media/4fed82481b.webp" alt=""
width="1400" height="933"></figure>

<h2 id="chatgpt-nazev-na-posledni-chvili">ChatGPT – název na poslední
chvíli</h2>

<p>Celý produkt vznikl během desetidenního hackathonu. V OpenAI ho nikdo
nepovažoval za důležitý, šlo o výzkumnou ukázku, sběr dat, nic víc.
Liam Fedus, jeden z tvůrců, to shrnul pro MIT Technology Review:
<em>nepovažovali jsme to za nic převratného.</em></p>

<p>Ještě noc před spuštěním Ilja Sutskever testoval model deseti
těžkými otázkami, spokojený byl jen s polovinou. Tým váhal, jestli to
vůbec spustit. A právě tehdy, na noční poradě 29. listopadu 2022, padlo
i rozhodnutí přejmenovat produkt z „Chat with GPT-3.5“ na „ChatGPT“.
Nick Turley, šéf produktu, později vzpomínal: prostě jsme si uvědomili,
že název zní krkolomně.</p>

<p>Druhý den ráno to šlo ven. A stalo se něco, co nikdo nečekal.</p>

<p>Turley zíral na čísla a myslel si, že je to chyba, tolik lidí přece
nemůže přijít najednou. Na vánočním večírku o pár týdnů později
kolegové tipovali, že zájem opadne.</p>

<p>Neopadl.</p>

<p>Sam Altman později přiznal, že OpenAI je mnohem lepší ve výzkumu než
<a href="https://www.umeligence.cz/blog/openai-nazvy-modelu">ve vymýšlení názvů</a>. Ale občas
stačí pojmenovat věci ve tři ráno.</p>

<p>Tak. To byl Chat, slovo, které přidal někdo v polospánku na poslední
chvíli. Teď ke zbytku. Ke třem písmenům, z nichž každé skrývá ještě
bláznivější historii.</p>

<h2 id="t-jako-transformer-a-malem-cargonet">T jako Transformer (a málem
CargoNet)</h2>

<p>Začněme odzadu, od písmene T. Transformer je motor, na kterém dnes <a
href="https://www.umeligence.cz/blog/jak-funguje-chatgpt">běží všichni chatboti</a>. Vymyslel ho
v roce 2017 tým v Googlu, původně proto, aby zlepšil strojový překlad
z angličtiny do němčiny a francouzštiny.</p>

<p>Jenže technologie potřebovala jméno. Prvním kandidátem byl „Attention
Net“. Příliš nudné. Pak jeden ze spoluautorů navrhl „CargoNet“,
akronym z <strong>C</strong>onvolution, <strong>A</strong>ttention,
<strong>R</strong>ecognition a <strong>Go</strong>ogle (názvy použitých
technologií plus Google). Zbytek týmu to jednomyslně smetl. Když o tom
léta později vyprávěl šéfovi Nvidie Jensenovi Huangovi, ten reagoval
suše: <em>„Moudří lidé.“</em></p>

<figure><img src="https://www.umeligence.cz/media/34ebd8d9e6.webp" alt=""
width="1400" height="933"></figure>

<p>Kdyby tým neměl dobrý vkus, celá dnešní AI revoluce by stála na
technologii, která zní jako doručovací služba.</p>

<p>Finální název <strong>Transformer</strong> navrhl lingvista Jakob
Uszkoreit. Transformace z jednoho jazyka do druhého, tak to původně myslel.
Další ze spoluautorů ale tvrdí, že ambice byly od začátku trochu
větší: <em>„Nešlo nám jen o překlad. Chtěli jsme vytvořit něco
obecného, něco, co dokáže transformovat jakýkoli vstup na jakýkoli
výstup.“</em> Ale že jejich vynález jednou bude psát básně, <a
href="https://www.umeligence.cz/obrazky">generovat obrázky</a> a analyzovat proteiny, to nečekal nikdo
z nich.</p>

<p>Aby ale Transformer mohl fungovat jako základ ChatGPT, musel ho někdo vzít
a naučit rozumět jazyku. A tady vstupuje na scénu písmeno P.</p>

<h2 id="p-jako-pre-trained-polotovar-ze-ktereho-vyrostl-hotel">P jako
Pre-trained – polotovar, ze kterého vyrostl hotel</h2>

<p>Dnes je ChatGPT hotový produkt: napíšeš otázku, dostaneš odpověď. Ale
to „P“ v názvu GPT říká něco úplně jiného. „Pre-trained“
znamená „předtrénovaný“. Ne natrénovaný, předtrénovaný. Jako
polotovar, který si musíš doma dodělat sám.</p>

<p>V roce 2018 to tak opravdu fungovalo: vzal jsi model, který nasál hromadu
textu, a pak sis ho sám dotrénoval na konkrétní úkol: třídění emailů,
rozpoznávání jmen, odpovídání na otázky. Bez toho dotrénování byl
k ničemu. Nikdo nepočítal s tím, že by polotovar mohl být rovnou
hotový pokrm.</p>

<figure><img src="https://www.umeligence.cz/media/0918ff9eb9.webp" alt=""
width="1400" height="933"></figure>

<p>Jenže s každou další generací se děly podivné věci.</p>

<p>GPT-2 v roce 2019 naznačil, že model dokáže plnit úkoly i bez
doladění, prostě jen na základě zadání. Výsledky byly slibné, ale
ještě ne oslnivé. Skutečný zlom přišel s GPT-3, který byl stokrát
větší. A ukázalo se, že velikost mění všechno, jako rozdíl mezi
studentem, který přečetl jednu učebnici, a někým, kdo přečetl celou
knihovnu. Autoři ve studii přímo napsali, že GPT-3 už žádné
dolaďování nepotřebuje. Model dostal úkol jen jako text a zvládl ho.</p>

<p>Všechno se otočilo. Z „natrénuj a dolaď“ vzniklo „natrénuj a
zeptej se“.</p>

<p>Slovo „pre-trained“ zůstalo v názvu jako fosilie, připomínka doby,
kdy nikdo nevěřil, že to může fungovat samo.</p>

<p>Zbývá poslední písmeno: G. A je to možná ta nejpřekvapivější
část celého příběhu.</p>

<div data-id="promo"></div>

<h2 id="g-jako-generative-sazka-proti-proudu">G jako Generative – sázka
proti proudu</h2>

<p>G znamená „generativní“, tedy tvořící. A v roce 2018 to byla
odvážná volba. Umělá inteligence tehdy uměla hlavně kategorizovat:
rozpoznat zvíře na obrázku, rozlišit spam od běžného emailu. Jasný
úkol, jasná odpověď, měřitelný výsledek. A celý obor věřil, že
právě tudy vede cesta ke stroji, který porozumí lidské řeči: dávej mu
úkoly, měř, jak se zlepšuje, a jednou to zvládne.</p>

<figure><img src="https://www.umeligence.cz/media/bb70be35a4.webp" alt=""
width="1400" height="933"></figure>

<p>Alec Radford to otočil. Bylo mu třiadvacet, do OpenAI přišel rovnou po
bakaláři a šéf firmy Sam Altman ho později označil za naprostého génia.
Radford dal svému modelu <a href="https://arxiv.org/abs/1704.01444">miliony
recenzí z Amazonu</a>: přečti si začátek recenze a zkus psát její
pokračování, jako ho psal původní autor. Žádné třídění, žádné
škatulkování, prostě piš dál.</p>

<p>Jenže aby model dokázal smysluplně navázat na cizí větu, nestačilo
skládat obvyklá slova za sebe. Musel postupně pochopit, co vlastně píše.
Všiml si, že v různých typech recenzí se vyskytují různá slova: jiná
v nadšených, jiná ve stížnostech. A podobně s ironií, pochybami,
radami. To mu stačilo.</p>

<p>Nikdo ho neučil rozumět. Učil se tvořit text, a porozumění přišlo
jako vedlejší produkt.</p>

<p>Paradox se skrýval přímo v názvu studie: <a
href="https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf"><em>Zlepšení
porozumění jazyku pomocí generativního tréninku</em></a>. Zlepšení
<em>porozumění</em> pomocí <em>tvoření</em>. Jako pochopit architekturu
tím, že zkusíte postavit dům. Zní to absurdně, ale fungovalo to.</p>

<p>Když Google o pár měsíců později představil vlastní model založený
na opačném principu, uč stroj přímo rozumět, a na většině testů GPT
jasně porazil, mnozí usoudili, že generativní cesta vede do slepé uličky.
GPT-2 a GPT-3 je umlčely.</p>

<p>A historická ironie: slovo „generativní“ bylo v roce 2018 odborným
termínem pro pár stovek výzkumníků. Dnes je „generativní AI“ název
celé epochy.</p>

<p>Radford, nesmírně plachý člověk, prakticky neposkytoval rozhovory.
V prosinci 2024 z OpenAI tiše odešel. Značka, kterou pomohl stvořit,
žije dál bez něj.</p>

<h2 id="gpt-zkratka-ktera-neexistovala">GPT: zkratka, která neexistovala</h2>

<p>Celou dobu mluvím o G, P a T, ale samotná zkratka GPT? Ta v původní
studii z června 2018 vůbec nebyla. Autoři svůj model pojmenovali prostě
„doladěný transformátorový jazykový model“. Altman měl pravdu:
vymýšlení názvů fakt není jejich silná stránka.</p>

<p>Zkratka se poprvé objevila až v únoru 2019, kdy OpenAI představila
GPT-2. A označení „GPT-1“? To nikdy oficiálně neexistovalo, zavedla ho
komunita zpětně, aby odlišila verze.</p>

<p>A pikantní detail: Mark Chen, výzkumník OpenAI, v podcastu prozradil,
že se ani uvnitř firmy neshodnou na tom, co GPT znamená. Polovina říká
„Generative Pre-trained Transformer“, tedy generativní předtrénovaný
transformátor. Druhá polovina tvrdí „Generative Pre<em>trainee</em>“,
cosi jako „generativní praktikant“. Značka za miliardy dolarů, a nikdo
přesně neví, co znamená :-)</p>

<h2 id="sedm-pismen-ktere-nikdo-neplanoval">Sedm písmen, které nikdo
neplánoval</h2>

<p>Zkratka GPT v původní studii neexistovala, a dodnes se v OpenAI
neshodnou, co znamená. Transformer přežil konkurenci CargoNetu díky
estetickému citu jednoho lingvisty. „Pre-trained“ znamená polotovar
v názvu pro hotový produkt. A „generative“? Odvážná sázka
třiadvacetiletého výzkumníka, který šel na to od druhého konce, nechal
stroj tvořit místo rozumět. A ono to přišlo samo.</p>

<p>Samotný název ChatGPT? Vymyšlen noc před spuštěním místo krkolomného
„Chat with GPT-3.5“.</p>

<p>Někdy největší značky na světě vzniknou tak, že se pár lidí
nevyspí.</p>

<hr>

<p><em>P.S. Francouzi v tom názvu slyší příběh výmluvnější než celý
tento článek: kočka (chat) prdla (GPT = žé-pé-té ≈ j'ai pété). Víc
nepotřebují vědět.</em></p>

        
			]]></content:encoded>
			<pubDate>Sat, 07 Mar 2026 00:00:00 +0100</pubDate>
		</item>
		<item>
			<title>Mac Mini za padesát tisíc na AI. Tohle si přečti, než ho koupíš.</title>
			<link>https://www.umeligence.cz/blog/lokalni-ai-mac-mini</link>
			<content:encoded><![CDATA[
			
		<p>„Koupil jsem si Mac Mini,“ chlubí se kamarád. „Budu na něm
provozovat lokální AI!“</p>

<p>Pokud zvažuješ něco podobného, tento článek ti možná ušetří
desítky tisíc. Rozdíl mezi lokální a cloudovou AI totiž existuje – je
dokonce obrovský. Ale je úplně jinde, než si většina lidí myslí.</p>

<h2 id="vzdalene-ai-funguje-kratce">„Vzdálené AI funguje krátce“</h2>

<p>Kamarád to vysvětluje přibližně takhle: „Ty cloudové modely, ChatGPT
a tak, fungují vždycky krátce. Krátká interakce. Kdežto když to budu mít
u sebe, může mi to běžet dlouho. Na velké úlohy.“</p>

<p>Chvíli na něj zírám. Je to, jako by někdo řekl: „Voda z vodovodu
teče jenom chvíli, ale voda z vlastní studny může téct celý den.“
Puštěnou vodu máš z obou tak dlouho, jak chceš. S umístěním serveru to
nemá co dělat.</p>

<figure><img src="https://www.umeligence.cz/media/e9028dd73d.webp" alt=""
width="1400" height="933"></figure>

<p>ChatGPT, Claude, Gemini – tyhle služby běží přesně tak dlouho, jak
potřebuješ. Můžeš s nimi vést dvouhodinový rozhovor, nechat je zpracovat
stostránkový dokument, nebo je nechat přes noc analyzovat data. Žádný
limit „krátkosti“ neexistuje.</p>

<p>Lokální i vzdálený model fungují na totožném principu: pošleš
požadavek, dostaneš odpověď. To je celé. Jediný rozdíl je, kam se tvůj
počítač připojuje – jestli k serveru ve vedlejším pokoji, nebo
k datacentru v Oregonu. Na délku ani kvalitu odpovědi to nemá
žádný vliv.</p>

<h2 id="ale-bude-mi-to-proklikavat-weby">„Ale bude mi to proklikávat
weby!“</h2>

<p>Dobře, argument s délkou neobstál. Kamarád tedy zkouší jinou obhajobu:
„OK, ale já to hlavně chci na to, aby mi to proklikávalo weby a hledalo na
nich chyby.“</p>

<p>Opět marně. Vzdálený model umí procházet weby úplně stejně jako
lokální. Schopnosti AI závisí na modelu samotném a na nástrojích, které
má k dispozici – ne na tom, kde fyzicky běží. Je to jako věřit, že
email odeslaný z notebooku v obýváku dojde někam jinam než email
z kanceláře. Stejný email, stejný výsledek, jen jiná židle pod
zadkem.</p>

<p>Jádro celého nedorozumění je jednoduché: lidé si pletou
<strong>kde</strong> model běží s tím, <strong>co</strong> umí.</p>

<p>Možná si říkáš: „Ale přece jen, mám to u sebe, nejsem závislý na
cizím serveru.“ Fajn, to je legitimní úvaha. Ale to je úplně jiný
argument než „poběží to furt“ nebo „bude to umět víc“.
Nezávislost na poskytovateli je jedna věc – schopnosti modelu
úplně jiná.</p>

<p>A nejde jen o toho kamaráda. Třeba Petr Ludwig nedávno napsal:
<em>„Nechal jsem svoji AI asistentku Lanu přečíst moje dvě knihy a teď
jsem jí zadal poslechnout 200 dílů mého podcastu – do rána to prý
zvládne. Funguje naprosto autonomně.“</em> Ten tón – ta magie kolem
slova „autonomně“ – jako by lokální provoz odemykal něco, co cloud
neumí. Přitom totéž zvládne jakýkoliv cloudový model – stačí mu
předat data a zadat úkol.</p>

<h2 id="co-bezi-na-domacim-hardware">Co běží na domácím hardware?</h2>

<p>Na počítači s „lokální AI“ běží dvě odlišné věci:</p>

<p><strong>Řídící program</strong> – přijme tvůj úkol, zavolá model,
nechá ho rozložit na kroky, zpracuje výsledek, otevře prohlížeč,
prokliká web. Právě tohle dělá třeba Claude Code nebo OpenClaw. Klíčová
informace: řídící program nepotřebuje žádný speciální hardware.
Běží na úplně obyčejném počítači. Klidně i na tom, co máš teď
na stole.</p>

<p><strong>Samotný model</strong> – to je ta výpočetně náročná část.
Chceš, aby jazykový model běžel u tebe a generoval odpovědi? Teprve tady
potřebuješ výkon – a teprve tady se nabízí otázka, jestli ti Mac Mini
za desítky tisíc vůbec stojí za to.</p>

<p>Většina populárních „AI asistentů“, kvůli kterým si lidé kupují
drahý hardware, ten model lokálně vůbec nepouští. Řídící program
běží u tebe, ale na generování odpovědí volá cloudové API –
ChatGPT, Claude, nebo jiný model. Takže se koupíš Mac Mini za padesát
tisíc a odpovědi ti stejně generuje ten server v Oregonu.</p>

<h2 id="kdy-lokalni-ai-dava-smysl">Kdy lokální AI dává smysl</h2>

<p>Nechci být nespravedlivý. Existují přesně tři legitimní důvody, proč
chtít, aby ten model skutečně běžel u tebe:</p>

<p><strong>Tvoje data nesmí opustit počítač.</strong> Pracuješ
s citlivými firemními dokumenty, zdravotními záznamy, nebo materiály pod
NDA? Pak je na místě, aby je zpracovával model běžící čistě u tebe. To
je naprosto validní důvod – a v řadě firem i regulatorní nutnost.</p>

<p><strong>Cenová optimalizace.</strong> Pokud AI používáš tak intenzivně,
že se jednorázová investice do hardwaru vyplatí víc než měsíční
předplatné, jdi do toho. Ale musíš si to opravdu spočítat. A počítat
poctivě.</p>

<p><strong>Chceš dělat věci, které ti cloud odmítne.</strong> Cloudové
služby odmítnou zpracovat určité úlohy, odmítnou odpovědět na určité
otázky. Lokální model nemá žádná taková omezení – dělá přesně
to, co mu řekneš. Žádné „omlouvám se, ale toto nemohu“.</p>

<p>Je fér říct, že lokální modely se v poslední době hodně zlepšují.
Na jednodušší úlohy – třeba základní kódování nebo běžnou práci
s textem – už mohou být docela použitelné. A pak existují
specializované malé modely na konkrétní úlohy, které dávají lokálně
naprostý smysl. Třeba přepis mluveného slova na text – na to
nepotřebuješ Mac Mini za padesát tisíc. To mi frčí na běžné NVIDIA
grafické kartě s 12 GB paměti.</p>

<p>Ale na složitější práci pořád výrazně zaostávají za špičkovými
cloudovými modely. Ty nejlepší modely běží na obrovských serverových
farmách s výkonem, se kterým se žádný stolní počítač nemůže
měřit. Kupuješ si slabší motor za vyšší cenu – jako bys platil za
první třídu v letadle, které letí pomaleji.</p>

<h2 id="tak-kolik-to-stoji">Tak kolik to stojí?</h2>

<p>Pojďme to rychle přeběhnout – ať víme, o čem se tu bavíme. Mac
Mini s čipem M4 a maximálními 32 GB RAM: 29 tisíc. S M4 Pro a 48 GB:
54 tisíc. A pokud chceš 64 GB RAM, tedy maximum: 60 tisíc korun. (Ano, za
stolní počítač bez monitoru, klávesnice a myši.)</p>

<p>Paměť je přitom klíčový parametr – a nejde jen o její velikost.
Záleží i na tom, jak rychle dokáže komunikovat s procesorem. Procesor
totiž většinu času čeká, než mu paměť pošle další kus dat pro model.
Apple Silicon má díky sdílené paměti slušnou propustnost (kolem
273 GB/s u M4 Pro), což je důvod, proč se Mac Mini pro lokální AI vůbec
doporučuje. Nové Ryzeny Strix Halo se sdílenou pamětí se mu v tomhle
začínají blížit. Běžné PC s klasickou DDR5 pamětí má propustnost jen
75–100 GB/s – a to je na plynulou práci s většími modely málo.</p>

<div data-id="promo"></div>

<p>A mimochodem – kolik textu model najednou „vidí“, takzvané <a
href="https://www.umeligence.cz/blog/nejdulezitejsi-vec-o-chatgpt">kontextové okno</a>, taky šíleně
žere paměť. U většího modelu může kontext o 128 tisících tokenech
spolknout klidně přes 18 GB. <strong>Takže do RAM se musí vejít celý
model, jeho cache, operační systém, a pokud má ten Mac Mini ještě
proklikávat weby, tak i prohlížeč se vším všudy.</strong></p>

<p>S 32 GB rozjedeš jen prťavé modely. Se 64 GB už slušnější, ale
pořád ne na úrovni, kterou asi očekáváš. Doporučil bych alespoň 128 GB
a více, ale to už se bavíme o Mac Studio s cenovkou (hodně) nad
100 tisíc.</p>

<p>A dále – jazykový model, model na generování obrázků a model na
rozpoznávání obrazu jsou tři různé modely. Každý zabírá místo
v paměti. Nemůžeš je mít všechny najednou, takže je musíš
prohazovat – nahrát jeden, pustit úlohu, vyhodit ho, nahrát další.
V cloudu tohle řešit nemusíš.</p>

<p>Pro srovnání: platím <a href="https://www.umeligence.cz/chatboti#claude">Claude</a> MAX za 90 eur
měsíčně, což je asi 2 250 korun. Za rok 27 tisíc. Jinými slovy: za
cenu Mac Mini s 64 GB (60 tisíc) bych měl přes dva roky přístupu
k jednomu z nejsilnějších modelů na světě. Bez starostí s údržbou a
bez kompromisů v kvalitě.</p>

<h2 id="jedna-otazka-misto-padesati-tisic">Jedna otázka místo padesáti
tisíc</h2>

<p>Pokud tě zajímá, jak reálně vypadá práce s AI na vlastním hardwaru,
podívej se na <a
href="https://www.youtube.com/watch?v=atDqnXmOJT8">přednášku o agentic
codingu na GPU</a> z ai4dev.cz nebo si přečti zkušenosti těch, <a
href="https://x.com/tomaskafka/status/2027915774174638211">co to
zkusili</a>.</p>

<p>Než utratíš desítky tisíc za hardware kvůli „lokální AI“, zkus si
poctivě odpovědět: <strong>co přesně ti lokální model dá, co vzdálený
ne?</strong> Odpověď není jednoduchá – ale právě proto stojí za to si
ji rozmyslet dřív než po nákupu.</p>

<p>A kamarád? Ten má aspoň na stole designový kousek za padesát tisíc. To
se taky počítá :-)</p>

        
			]]></content:encoded>
			<pubDate>Sun, 01 Mar 2026 00:00:00 +0100</pubDate>
		</item>
		<item>
			<title>Einstein jedl a kadil v Praze</title>
			<link>https://www.umeligence.cz/blog/ai-neni-jen-generovani-tokenu</link>
			<content:encoded><![CDATA[
			
		<blockquote>
	<p><strong>14. července 1789.</strong> Paříž. Ráno. Jeden z klíčových
	momentů lidských dějin.</p>

	<p>V Paříži posnídalo několik set tisíc lidí. Někteří měli chléb,
	jiní ne, tahle nerovnoměrná distribuce snídaní bude za okamžik hrát
	zásadní roli. Po snídani většina z nich vykonala potřebu, jak bývá po
	snídani zvykem.</p>

	<p>Někteří lidé, kteří ráno jedli a poté kadili, vyrazili k pevnosti
	zvané Bastila, kde byli uvězněni jiní lidé, kteří taktéž jedli a
	kadili, byť v podstatně horších podmínkách. (Kvalita kálení ve
	francouzských věznicích 18. století si zaslouží samostatnou studii,
	kterou tento článek nemá ambici být.) Onu pevnost dobyli. Pak šli
	na oběd.</p>

	<p>Jedl a kadil i král, a to na zámku ve Versailles. Jednoho dne mu usekli
	hlavu a poté již nikdy nejedl. O kálení se historici taktně
	nezmiňují.</p>
</blockquote>

<p>Co kdyby TAKHLE vypadala přednáška o Velké francouzské revoluci?</p>

<blockquote>
	<p><strong>12. dubna 1961, Bajkonur.</strong> Člověk jménem Gagarin snídal.
	Údajně dostal čaj a tvaroh. Pak kadil.</p>

	<p>Následně se nechal vystřelit v těsné kovové kouli do vesmíru.
	Obletěl Zemi za 108 minut, během kterých ani nejedl, ani nekadil, a bylo mu
	za to uděleno nejvyšší státní vyznamenání. Psali o něm noviny po
	celém světě.</p>
</blockquote>

<figure><img src="https://www.umeligence.cz/media/98a3ae16e6.webp" alt=""
width="1400" height="933"></figure>

<p>Koupil by sis na takový TED talk o počátcích kosmonautiky lístek?</p>

<blockquote>
	<p>Rok 1905. V Bernu žije šestadvacetiletý úředník patentového úřadu
	jménem Einstein. Ráno vstane a snídá. Pak jde do práce, kde osm hodin
	posuzuje patentové přihlášky, během toho několikrát kadí. V poledne
	obědvá. Večer večeří a kadí. Jde spát.</p>

	<p>Někdy v noci taky něco píše: čtyři články o světle, atomech,
	relativitě a E = mc². Je to spolehlivý, předvídatelný cyklus, který trvá
	celý rok. Historici mu říkají <em>rok zázraků</em>, německy
	<em>Wunderjahr</em>, což zní spíš jako lék na zácpu.</p>

	<p>Později se Einstein přestěhoval do Prahy. Jedl v Café Louvre, kadil na
	Karlo-Ferdinandově univerzitě a mezi tím tak nějak vymyslel obecnou teorii
	relativity.</p>
</blockquote>

<p>Představ si, že takhle popisuje Einsteinův rok zázraků profesor Krtouš
z pražského Matfyzu. Hmm…</p>

<h2 id="prednaskovy-sal-minuly-tyden">Přednáškový sál, minulý týden</h2>

<p>Sedím na přednášce o jazykových modelech. Přednášející
srozumitelně a věcně vysvětluje, jak to celé funguje. Ústřední
myšlenka: jazykový model „<em>jenom</em> generuje další slovo
s největší pravděpodobností“. Publikum mu nadšeně visí na rtech.
A mně to celou dobu vrtá hlavou.</p>

<p>Model produkuje tokeny, jeden za druhým. Člověk produkuje hovna, jedno za
druhým. Obojí je pravda. Obojí popisuje tu nejspodnější vrstvu celého
příběhu. Jako v té úvodní historce o francouzské revoluci. Jenže nad
naším trávením se postupně navrstvilo pár věcí. Kultura. Věda.
Revoluce. Obecná teorie relativity. A nad generováním tokenů taky.</p>

<div data-id="promo"></div>

<p>Dneska máme <a href="https://www.umeligence.cz/blog/reasoning">reasoning modely</a>, které
přemýšlejí krok za krokem, zvažují alternativy a opravují vlastní chyby.
Máme <a href="https://www.umeligence.cz/ai-agenti">AI agenty</a>, kteří dostanou zadání, sami
naplánují postup, napíšou a spustí kód, vyhodnotí výsledek, a pokud
nevyjde, začnou znovu.</p>

<p>Ale mluvit v roce 2026 o jazykových modelech jako o „generátoru
dalšího slova“ – to je jako přednáška profesora Krtouše zaměřená
na to, že když zapil třešně mlíkem, jevil se Einsteinovi univerzitní
záchod relativně mnohem dál.</p>

<p>Popisovat složitou věc jednou vrstvou jde vždycky:</p>

<ul>
	<li>📊 AI je pokročilá statistika</li>

	<li>🧠 mozek je pokročilá chemie</li>

	<li>💻 počítač je hromada tranzistorů</li>

	<li>🎵 hudba je vibrace vzduchu</li>

	<li>💕 láska je hormonální porucha</li>

	<li>🔥 oheň je rychlá oxidace</li>

	<li>💀 Hamlet je chlap, který mluví sám k sobě a pak zabije tchána</li>

	<li>⌨️ programování je psaní textu do souborů</li>
</ul>

<p>Všechno je to pravda. Ze žádné z těch definic ale nepochopíš, proč
u Hamleta brečíš.</p>

        
			]]></content:encoded>
			<pubDate>Thu, 26 Feb 2026 00:00:00 +0100</pubDate>
		</item>
		<item>
			<title>Nejdůležitější věc, kterou ti nikdo o ChatGPT neřekne</title>
			<link>https://www.umeligence.cz/blog/pamet-chatgpt-kontextove-okno</link>
			<content:encoded><![CDATA[
			
		<p>Smál jsem se 😄 Rok 2023, Midjourney frčí naplno a lidi na internetu
hrdě ukazují, jak do ChatGPT nasázeli celou jeho dokumentaci (stránku po
stránce) a nechávají si generovat prompty pro obrázky. Geniální nápad.
Jenže já věděl něco, co oni ne. ChatGPT si z toho pamatoval tak poslední
stránku. A po pátém vygenerovaném promptu zapomněl i tu. Tehdy měl
paměť pouhých 1 800 slov. A co dnes? Budete se divit!</p>

<p>Tohle je nejdůležitější věc, kterou ti nikdo o ChatGPT neřekne. Má
ze všech velkých chatbotů <strong>nejkratší paměť na konverzaci</strong>.
Jasně, povídat si s ním můžeš do nekonečna, nikdy tě nezastaví. Ale to
je právě ta zákeřnost. Když je konverzace dlouhá, starší zprávy tiše
ignoruje. Bez varování. Prostě je nevidí. A pokračuje dál, jako by nikdy
nezazněly.</p>

<p>Tři roky to říkám na každé přednášce. Tři roky se dívám, jak lidi
dělají pořád ty samé chyby. A pořád je to šokuje. Když o tom napíšu
na internetu, lidi se se mnou hádají do krve, prý má ChatGPT přece mnohem
větší paměť. Dnes je každý na vrcholu Dunning-Krugerovy křivky a má
pocit, že ví všechno.</p>

<figure><img src="https://www.umeligence.cz/media/71d5f76200.webp" alt=""
width="1400" height="933"></figure>

<p>Přitom ta čísla jsou přímo <a
href="https://help.openai.com/en/articles/11909943-gpt-52-in-chatgpt">na webu
OpenAI</a>. Pravda, nekřičí je do světa, ale ani je netají. A vždycky
jste je mohli najít na <a
href="https://www.umeligence.cz/chatboti#srovnani-kontextovych-oken">Uměligenci</a>.</p>

<h2 id="tri-myty-o-pameti-chatgpt">Tři mýty o paměti ChatGPT</h2>

<p>Iluze, na které narážím od roku 2023 dodnes, a nic se na nich
nezměnilo:</p>

<p><em>„Nahraju tam pět PDF a ono mi to zanalyzuje.“</em> Nahrát je
můžeš. Ale kontext se prvním zaplní a chatbot na zbylé dokumenty kašle.
Dostaneš iluzi analýzy, ne analýzu.</p>

<p><em>„Má přece obrovskou paměť, je to AI!“</em> Nemá. Je na tom
nejhůř ze všech. To, co konkurence dává zadarmo, nemá ChatGPT ani
v nejdražším tarifu.</p>

<p><em>„ChatGPT vidí všechny moje konverzace.“</em> Nevidí. Každé
vlákno je úplně samostatný svět. Co jsi psal ve vedlejším vlákně, pro
něj neexistuje.</p>

<p>Možná namítneš: „Ale ChatGPT si přece pamatuje, že bydlím
v Hradišti a mám rád pizzu Hawaii!“ Ano, to je funkce <a
href="https://www.umeligence.cz/blog/chatgpt-memory">Memory</a>, takový zápisníček s poznámkami
o tobě. ChatGPT umí taky prohledávat starší vlákna. Claude má něco
podobného. Ale nic z toho nenahrazuje kontextové okno – to jsou jen
útržky nalepené na novou konverzaci. Lístek na lednici, ne
pracovní stůl.</p>

<h2 id="kolik-slov-se-vejde-do-pameti">Kolik slov se vejde do paměti?</h2>

<p>Protože tenhle článek píšu pro lidi a ne pro AI inženýry, budeme se
bavit o délkách textů měřených v českých slovech. Chatboti počítají
v <a href="https://www.umeligence.cz/chatgpt-do-hloubky#tokeny">tokenech</a> a jedno české slovo
zabírá zhruba 2,2 tokenu.</p>

<p>Představ si konverzaci s kamarádem v hospodě. A tenhle kamarád má tak
upito, že si pamatuje jen posledních pár minut. Zbytek mu vypadne z hlavy, a
neřekne ti to. Prostě se tváří jako by nic a pokračuje. Ty tomu věříš,
protože mluví sebevědomě. Téhle paměti se odborně říká
<strong>kontextové okno</strong> a jeho velikost určuje, kolik textu chatbot
„vidí“ najednou.</p>

<p>ChatGPT v bezplatné verzi dnes pojme asi <strong>7 000 českých
slov</strong>. Placená verze Plus kolem <strong>15 000 slov</strong>. To zní
jako hodně, dokud si neuvědomíš, že se do toho počítají i odpovědi
chatbota.</p>

<p>A víš, jak to vypadalo na začátku? První ChatGPT v roce 2022 pojal
asi <strong>1 800 slov</strong>. Míň než Vaculíkovy <em>Dva tisíce
slov</em> – on by tu petici klidně podepsal před zrakem policajtů,
protože by nevěděl, co podepisuje.</p>

<table class="table">
	<tbody>
		<tr>
			<th>Chatbot</th>

			<td> </td>

			<th><span title="počítáme 2,2 tokenu na české slovo">≈ českých
			slov</span></th>
		</tr>

		<tr>
			<td>GPT-3.5 (první ChatGPT)</td>

			<td>2022</td>

			<td><span title="4 000 tokenů">~1 800</span></td>
		</tr>

		<tr>
			<td>GPT-4 Plus</td>

			<td>2023</td>

			<td><span title="8 000 tokenů">~3 600</span></td>
		</tr>

		<tr>
			<td>GPT-4o Plus</td>

			<td>2024</td>

			<td><span title="16 000 tokenů">~7 000</span></td>
		</tr>

		<tr>
			<td>GPT-5 Plus</td>

			<td>2025</td>

			<td><span title="32 000 tokenů">~15 000</span></td>
		</tr>
	</tbody>
</table>

<p>Za tři roky se paměť zčtyřnásobila. Zní to jako pokrok, dokud se
nepodíváš, co nabízí konkurence.</p>

<h2 id="brutalni-srovnani">Brutální srovnání</h2>

<p>Takže jak si stojí ChatGPT ve srovnání s konkurencí?</p>

<table class="table">
	<tbody>
		<tr>
			<th>Chatbot</th>

			<th><span title="počítáme 2,2 tokenu na české slovo">≈ českých
			slov</span></th>
		</tr>

		<tr>
			<td>ChatGPT 5.2 Free</td>

			<td><span title="16 000 tokenů">~7 000</span></td>
		</tr>

		<tr>
			<td>ChatGPT 5.2 Go / Plus / Business</td>

			<td><span title="32 000 tokenů">~15 000</span></td>
		</tr>

		<tr>
			<td><a href="https://www.umeligence.cz/chatboti#claude">Claude 4.6</a></td>

			<td><span title="200 000 tokenů">~90 000</span> <em>(velmi brzy <span
			title="1 000 000 tokenů">~450 000</span>)</em></td>
		</tr>

		<tr>
			<td><a href="https://www.umeligence.cz/chatboti#gemini">Gemini 3 Pro</a></td>

			<td><span title="1 000 000 tokenů">~450 000</span></td>
		</tr>

		<tr>
			<td><a href="https://www.umeligence.cz/chatboti#grok">Grok 4.2</a></td>

			<td><span title="256 000 tokenů">~120 000</span></td>
		</tr>

		<tr>
			<td><a href="https://www.umeligence.cz/chatboti#deepseek">DeepSeek 3.2</a></td>

			<td><span title="256 000 tokenů">~120 000</span> <em>(velmi brzy <span
			title="1 000 000 tokenů">~450 000</span>)</em></td>
		</tr>

		<tr>
			<td>ChatGPT 5.2 Thinking</td>

			<td><span title="128 000 tokenů">~58 000</span> <em>(teprve od
			8/2025)</em></td>
		</tr>
	</tbody>
</table>

<p>Pro lepší představu to dám do grafu:</p>

<figure><img src="https://www.umeligence.cz/media/2cc9e2841b.webp" alt=""
width="1400" height="797"></figure>

<p>Podívej se na to ještě jednou. ChatGPT v bezplatné verzi: sedm tisíc
slov. Gemini Pro: <strong>čtyři sta padesát tisíc</strong>. Do kontextového
okna Gemini se v angličtině vejdou všechny díly Harryho Pottera. Do ChatGPT
seminárka. To je jako srovnávat kapesní diář s městskou knihovnou.</p>

<p><em>(Ať jsme fér, Gemini v bezplatné verzi má jen 15 000 slov, jako
placený ChatGPT Plus, ale existuje snadný způsob, jak ty statisíce slov
získat úplně zadarmo, na <a href="https://www.umeligence.cz/kurz-chatgpt">školeních</a> to vždycky
ukazuju.)</em></p>

<p>Claude nabízí 90 000 slov <strong>v jakékoliv verzi</strong>, včetně
bezplatné. Už roky. A právě teď přichází s verzí co dorovnává
Gemini. DeepSeek dosud nabízel 120 000 slov, a je celý zdarma. Každým dnem
má vyjít verze, která také dorovná Gemini.</p>

<p>Všimni si jedné věci: ChatGPT GPT 5 Thinking sice má 58 000 slov (pro
vstup). Ale tenhle režim je novinka (<a
href="https://web.archive.org/web/20250723120417/https://openai.com/chatgpt/pricing/">stav
dříve</a>) a navíc musíš vědět, že si ho máš zapnout. OpenAI
slavnostně dohání Claude na devadesáti tisících v době, kdy ostatní
přecházejí na milion tokenů.</p>

<p>Možná si říkáš: „No a co, 15 000 slov je přece dost, ne?“
Záleží, co děláš. Pokud si jen povídáš o počasí, asi ano. Ale
jakmile nahraješ dokument a k tomu vedeš konverzaci, 15 000 slov zmizí
rychleji, než bys čekal. A uvědom si, že ta slova zahrnují i odpovědi
chatbota. Tvůj reálný prostor je nanejvýš poloviční.</p>

<h2 id="proc-je-zrovna-chatgpt-nejhorsi">Proč je zrovna ChatGPT
nejhorší?</h2>

<p>Když jsi nejpopulárnější, nemusíš se tolik snažit. Lidi tě stejně
používají. ChatGPT je suverénně nejpoužívanější chatbot na světě, a
když jsi McDonald's, nemusíš mít nejlepší burger ve městě. Stačí, že
jsi na každém rohu.</p>

<p>A ChatGPT je, co se týče paměti, trvale pozadu. Nejde jen o bezplatnou
verzi se 7 000 slovy. I placený Plus nabízí jen 15 000. I nejdražší
tarify za slušný balík mají v běžném režimu kolem 58 000 slov,
pořád méně, než co Claude dává každému zadarmo. Jedinou výjimku
představuje režim Thinking, jenže o tom většina uživatelů ani neví.
A není to tím, že by to neuměli. Přes API nabízí OpenAI vývojářům
kontextová okna o stovkách tisíc tokenů. Technicky to zvládají, jen to
nedají běžným uživatelům na webu.</p>

<p>Konkurence to musí řešit jinak. Musí nabídnout víc, aby přetáhla
uživatele. A tak nabízí neuvěřitelně velká kontextová okna.</p>

<p>Abych byl fér: ChatGPT nabízí šíři funkcí, kterou nikdo jiný nemá.
Generování obrázků, video, hlasový režim, Canvas, agenty, <a
href="https://www.umeligence.cz/blog/deep-search">Deep Research</a>. Je to švýcarský nožík AI
světa. Ale šíře funkcí ti nepomůže ve chvíli, kdy chatbot zapomene,
o čem jste se bavili.</p>

<div data-id="promo"></div>

<h2 id="proc-jsou-velka-okna-tak-draha">Proč jsou velká okna tak drahá?</h2>

<p>Představ si, že seš na konferenci a musíš si udržet přehled o tom,
kdo co řekl. S deseti lidmi je to v pohodě, zapamatuješ si, že Pepa mluvil
o rozpočtu a Mařka o marketingu. Se stovkou musíš u každé nové věty
zvážit, jak souvisí se vším, co řekl kdokoliv jiný. S tisícem? Nemáš
šanci. Potřeboval bys mozek velikosti konferenčního sálu.</p>

<p>Přesně takhle funguje architektura Transformer, na které jsou postavené
všechny dnešní chatboty. Při zpracování textu udržuje vazbu
<strong>každého slova s každým ostatním</strong>. Složitost roste
s druhou mocninou: zdvojnásobíš okno a výpočetní nároky narostou
čtyřnásobně. Proto měl první ChatGPT paměť pouhé 4 000 tokenů, a
proto je milionové okno tak obrovský inženýrský úspěch. Víc o tom, jak
to celé funguje pod kapotou, popisuju v <a
href="https://www.umeligence.cz/blog/jak-funguje-chatgpt">Jak funguje ChatGPT</a>.</p>

<p>Ale rád bych něco dodal: tohle je z mého pohledu překonaná věc.
U kontextu o milionech slov fakt není potřeba udržovat vazbu mezi slovem
„nové“ z jedné knihy a slovem „auto“ z úplně jiné. Dostali jsme
se do rozměrů, se kterými autoři původního Transformeru v roce
2017 vůbec nepočítali.</p>

<h2 id="co-s-tim">Co s tím?</h2>

<p>Teď, když víš, jak malou paměť ChatGPT ve skutečnosti má, dávají
najednou smysl všechny ty nezdařené pokusy:</p>

<ul>
	<li>To PDF, co ti chatbot „zanalyzoval“? Zapomněl polovinu, než dopsal
	odpověď.</li>

	<li>Ta konverzace, kde najednou začal ignorovat tvoje zadání? Vypadlo mu
	z okna.</li>

	<li>Ten moment, kdy po půl hodině diskuse odpovídal naprosté nesmysly?
	Nevěděl, o čem se bavíte.</li>
</ul>

<p>Na školeních to slýchám pravidelně: někdo nahraje třicetistránkovou
smlouvu a ptá se na článek 47. ChatGPT mu s klidným svědomím odpoví
nesmysl, protože článek 47 už z kontextu dávno vypadl. A odpověď zní
tak sebevědomě, že tomu člověk uvěří.</p>

<p>Pár praktických rad:</p>

<ul>
	<li><strong>Zakládej nové vlákno</strong> pro každé téma. Neplýtvej
	pamětí na věci, které spolu nesouvisí.</li>

	<li><strong>Průběžně žádej o shrnutí</strong>: „Shrň, na čem jsme se
	domluvili.“ I když starší zprávy vypadnou, shrnutí tam zůstane.</li>

	<li><strong>Velké dokumenty dávkuj.</strong> Místo celého
	padesátistránkového PDF nahraj jen relevantní stránky. Chatbot zpracuje
	pět stránek líp než padesát, ze kterých si pamatuje dvě.</li>

	<li>Pro seriózní práci s dokumenty zvaž Claude nebo Gemini. Se statisíci
	slovy je to jiný svět.</li>
</ul>

<p>Vzpomínáš na ty lidi s dokumentací Midjourney z roku 2023? Teď víš,
proč ten pátý prompt vypadal, jako by dokumentaci nikdy neviděl. Protože ji
opravdu neviděl. ChatGPT na ni dávno zapomněl. A od té doby se toho zas tak
moc nezměnilo.</p>

<p>Až příště budeš plánovat delší práci s chatbotem, vzpomeň si na
toho kamaráda z hospody. Toho, co si pamatuje jen posledních pár minut.
A zvaž, jestli nechceš raději kamaráda, co si pamatuje celou
párty :-)</p>

        
			]]></content:encoded>
			<pubDate>Wed, 18 Feb 2026 00:00:00 +0100</pubDate>
		</item>
</channel>
</rss>
