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ě:
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 <, >,
<= a >= 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 (<=>).
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
"042" a " 42"? Jak je seřadí?
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á "042" a " 42"? 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 <
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 'Y-m-d H:i:s', 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!
Konečně! Boucháme šampaňské, trháme konfety a rituálně
pálíme učebnice procedurálního programování. PHP 8.5 přináší
legendární Pipe Operátor |>. Svatý grál všech, kdo
se po nocích modlí k bohům funkcionálního programování a tajně závidí
hipstrům v Elixiru nebo F#.
Už žádné vnořování funkcí do sebe jako ruská matrjoška. Žádné
pomocné proměnné typu $tmp1, $tmp2,
$tmp47. Píšeme tok dat zleva doprava, přesně tak, jak
přirozeně myslíme!
Marketingové oddělení PHP by vám na slidech ukázalo tohle:
„Wow! To je čistota! To je elegance!“ křičí dav a hází
podprsenky na pódium.
Jenže pak se probudíte a zjistíte, že realita je úplně jiná. Vítejte
v pekle závorek, anonymních funkcí a výkonnostního masochismu. Pojďme se
podívat, proč je tahle novinka skvělá asi tak jako nepromokavý
čajový sáček.
Case Study: Jak napsat
to samé, ale složitěji
Představte si klasický scénář: Chcete normalizovat text pro anglické
titulky. Postup: oříznout mezery, rozsekat na slova, každému slovu dát
velké počáteční písmeno a zase to slepit dohromady.
Na první pohled je to zenová zahrada. Vidíte, jak data tečou shora dolů
jako vodopád. Váš mozek vrní blahem, protože nemusí luštit závorky od
středu ven. Je to jako číst recept: „Vezmi vstup, ořízni ho, rozsekej na
slova, uprav, slep.“ Nádhera. Feng-shui v praxi.
TAKLE TO ALE NEFUNGUJE!
Ten krásný příklad výše je totiž sprostá syntaktická lež. PHP
parser by se při pokusu o zpracování tohoto kódu zakuckal a umřel
s výkřikem SYNTAX ERROR.
Hned si vysvětlíme proč.
Jak se to dělalo dříve
Tradiční nečitelnou hrůzu se zanořováním jsme si už ukázali. Místo
toho se podívejme na „sedlácký“ styl, který všichni tajně
používáme, když se nikdo nedívá:
// Tradiční způsob s pomocnými proměnnými - jasný, funkční, nudný (110 znaků)
$_ = trim($input); // odstranění mezer
$_ = preg_split('/\s+/', $_); // rozdělení na slova
$_ = array_map(ucfirst(...), $_); // zvětšení písmen
$processed = implode(' ', $_); // spojení zpět
Jasný, čitelný. Každý junior ví, co se děje. Pomocná proměnná
$_ sice nevyhraje soutěž krásy, ale funguje, nic nestojí a
nepřekáží.
A mimochodem, tento kód má 110 znaků. Zapamatujte si
to číslo.
První facka: ...
není Partial Application
Placeholder ... funguje JENOM tehdy, když pipujete do
prvního a zároveň jediného parametru!
Takže zatímco trim(...) je v pohodě, cokoliv
složitějšího narazí do zdi. PHP (na rozdíl od jazyků, kde to dělají
pořádně) neumí říct „tady je funkce a tohle je díra pro
argument“.
A protože většina funkcí v PHP má pořadí parametrů vybrané
generátorem náhodných čísel (needle/haystack chaos), musíte použít arrow
funkce. Připravte si prsty, budete psát hodně fn,
$_ a šipek:
$processed = $input
|> trim(...) // Jediný moment, kdy to funguje hezky
|> fn($_) => preg_split('/\s+/', $_) // Arrow fce. Proměnná.
|> fn($_) => array_map(ucfirst(...), $_) // Vnořená arrow funkce, mňam.
|> fn($_) => implode(' ', $_);
Druhá facka: Závorkové peklo
Spustíte to a čekáte potlesk. Místo toho na vás PHP interpreter
vyplázne: Fatal error: Arrow functions on the right hand side of |> must be parenthesized.
Pardon? Musím dávat arrow funkce do závorek? Proč? Kvůli precedenci
operátorů. Parser je zmatený jak lesní včela, takže mu musíte každou
arrow funkci explicitně zabalit, aby pochopil, kde končí a kde začíná
další trubka.
Takže váš „elegantní“ kód nyní vypadá takto:
$processed = $input
|> trim(...)
|> (fn($_) => preg_split('/\s+/', $_)) // Závorka. Arrow fce. Proměnná. Závorka.
|> (fn($_) => array_map(ucfirst(...), $_)) // Další závorky...
|> (fn($_) => implode(' ', $_)); // Proč si to děláme?
Gratuluji! Váš kód je nyní:
O 53 % delší než varianta s pomocnými proměnnými (nyní má
169 znaků).
Vizuálně připomíná LISP po lobotomii (samá závorka).
Má overhead z vytváření closure při každém kroku.
Pokrok nezastavíš!
K.O.: Reference? Zapomeňte
Tohle je část, kde se smích mění v pláč a skřípění zubů.
Představte si, že chcete v textu něco nahradit a zajímá vás, kolikrát
k nahrazení došlo (parametr &$count u
str_replace).
V klasickém „zastaralém“ kódu byste prostě předali proměnnou
odkazem.
Ale v arrow funkcích uvnitř roury? Smůla. Arrow funkce (fn)
v PHP nemůžou měnit vnější proměnné.
Podívejte se na tuhle past:
$count = 0;
// Očekáváme, že se $count zvýší...
$processed = $input
|> (fn($_) => str_replace('!', '', $_, count: $count)) // Zrada!
|> trim(...)
...
echo $count; // Výsledek je vždy 0. Nula. Zero.
Co se stalo?
Nic. Žádná chyba. Žádná Notice. Žádný
Warning. PHP prostě mlčky vzalo kopii nuly, poslalo ji do funkce,
ta ji vesele inkrementovala uvnitř své bubliny a pak ji zahodila do koše.
Vaše původní proměnná $count zůstala nedotčena.
Pokud to chcete opravit, musíte použít starou syntaxi
function($_) use (&$count) { return ... }, čímž jste právě
ztratili poslední zbytky důstojnosti a elegance.
Co se děje pod kapotou?
(Spoiler: Nic hezkého)
Možná si říkáte: „No dobře, je to hnusné, ale určitě je to
super rychlé, optimalizované makro, ne?“
(Nikdo soudný si tohle neříká, ale předstírejme to.)
Pipe operátor není chytré makro, které by kód přepsalo
(fn($_) => ...) na prosté volání. Každý ten krok reálně
vytváří novou instanci objektu Closure. Pro každou operaci. Pro
každý řádek.
Takže místo prostého zavolání funkce PHP interně dělá tohle:
Vytvoř objekt Closure.
Zavolej ho.
Zahoď ho (a nech garbage collector, ať si to užije).
Opakuj pro další řádek.
Je to, jako byste si na cestu do ledničky pro pivo pokaždé objednali Uber.
Dostanete se tam, ale je to zbytečně drahé, trvá to déle a sousedi si
o vás budou myslet svoje.
Typehintujeme, nebo ne?
Tohle je hamletovská otázka moderního PHP. V řetězci pipe operátorů
se musíte rozhodnout:
Varianta A: Jsem poctivý masochista a napíšu
(fn(string $_): array => ...).
Výsledek: Kód je tak dlouhý, že se nevejde na dva monitory vedle
sebe. Upíšete si ruce a kolegové vás budou nenávidět při každém Code
Review.
Varianta B: Jsem lenoch a punker a napíšu jen
(fn($_) => ...).
Výsledek: Vaše IDE přestane našeptávat a přísně nastavená
statická analýza (PHPStan) začne křičet „Mixed type everywhere!“
Je to volba mezi karpálním tunelem a programováním naslepo.
Verdikt
Pipe operátor v PHP 8.5 je jako drahý, designový odšťavňovač, co
jste si koupili v lednu v záchvatu zdravého životního stylu. Vypadá hezky
na lince, všem o něm vyprávíte, ale když ho máte reálně použít,
zjistíte, že umytí těch sítek trvá třikrát déle než snězení celého
pomeranče i se slupkou.
Pipe operátor dává smysl pouze tehdy, když:
Všechny funkce berou jeden parametr. ✅
Nebo vás nezajímá výkon. ✅
Nebo milujete závorky. ✅
Čili… je to skvělé do tutoriálů a na konference!
Doporučení: Zůstaňte u pomocných proměnných. Jsou levné,
fungují, neodstíní reference a nemusíte kvůli nim psát
(fn($_) => ...) desetkrát za sebou.
P.S.: V PHP 8.6 to vyřeší Partial Function
Application. Ale utěšovat se tím teď, to je jako říct hladovému
člověku: „Vydrž, příští rok už ten řízek bude
i s masem.“
(Aktualizováno v září 2025) Claude Code je nový nástroj,
který přináší schopnosti umělé inteligence Claude do virtuálního programátora, který sedí
vedle vás a pomáhá vám s vašimi projekty. Claude je obdoba ChatGPT a jeho
největší konkurent. Na rozdíl od běžného chatování v prohlížeči,
kde musíte kopírovat kód tam a zpět, Claude Code pracuje přímo s vašimi
soubory a na vašem počítači.
S Claude Code můžete:
Říct Claudovi, co potřebujete naprogramovat, a on to udělá přímo ve
vašem počítači
Nechat si vysvětlit složitý kód, který jste našli na internetu
Převést vaše nápady do funkčních programů, aniž byste museli znát
všechny technické detaily
Rychle vytvářet skripty a nástroje pro běžné úkoly
Získat pomoc s laděním a opravou chyb ve vašem kódu
Přitom nepotřebujete být expert na umělou inteligenci ani na
programování – Claude Code je navržený tak, aby s ním mohl pracovat
každý, kdo se naučí, jak zapnout příkazovou řádku. Ale nutno říct, že
čím lepší jste programátor a čím víc svému oboru rozumíte, tím
lepších výsledků dosáhnete.
Tento první článek je určen pro úplné začátečníky a provede vás
instalací Claude Code. V dalších dílech se podíváme na praktické
využití, ale nejprve musíme zvládnout ten první krok – zprovoznění. Po
dokončení tohoto návodu budete mít vše připraveno a můžete si s Claude
Code začít hrát.
Seznámení s příkazovou
řádkou
Claude Code běží z příkazové řádky (terminálu), což znamená, že
ho ovládáte zadáváním textových příkazů místo klikání na tlačítka.
Pokud jste nikdy nepracovali s příkazovou řádkou, představte si to jako
hraní textových adventur z 80. let – napíšete příkaz, stisknete Enter
a počítač odpoví. Jen místo „jdi na sever“ budete psát příkazy jako
„vyrob mi web“. Je to takový retro zážitek!
Pro Windows: Spusťte příkaz cmd
Pro macOS: Otevřete Terminal (najdete ho v Applications >
Utilities nebo stiskněte Cmd+Space a napište „Terminal“)
Pro Linuxu: Otevřete váš oblíbený terminál (obvykle
Ctrl+Alt+T)
Instalace Node.js a npm
Protože Claude Code je napsaný v Node.js (což je prostředí pro
spouštění JavaScriptových aplikací na vašem počítači), musíme nejprve
nainstalovat tento nástroj. Node.js potřebujeme nainstalovat bez ohledu na to,
jaký operační systém používáte.
Po instalaci otevřete terminál a ověřte instalaci příkazem
node --version – měli byste vidět číslo verze.
Instalace Claude Code
A konečně nainstalujeme Claude Code. V příkazové řádce zadejte:
npm install -g @anthropic-ai/claude-code
Může se stát, že to skončí nějakou chybou s oprávněním.
Důležité je neřešit chyby tím, že použijete
sudo – místo toho konzultujte troubleshooting
na webu.
Spouštění Claude Code
Důležité je, že Claude Code musíte spustit ve složce, kde chcete
tvořit nový projekt nebo upravovat existující. Musíte se nejprve do této
složky navigovat pomocí příkazu cd (change directory).
Příklad:
cd C:\Moje projekty\novy-skvely-projekt
Claude Code pak jednoduše spustíte pomocí claude
Claude Code spuštění
A jak vidíte z obrázku, Claude Code si hned říká o propojení
s webovým účtem kvůli placení.
Jak se za Claude Code platí?
Claude Code je zahrnut v předplacených plánech Claude – funguje to
podobně jako Netflix, platíte měsíčně a můžete nástroj používat podle
limitů vašeho plánu. Začněte s Claude Pro za $20/měsíc a pokud
zjistíte, že narážíte na limity, můžete kdykoliv upgradovat.
Claude Code vás z terminálu rovnou naviguje do Anthropic konzole. Tam se přihlaste
nebo zaregistrujte, vyberte vhodný plán a poté zpátky do terminálu
překopírujte klíč, který vám web vygeneruje.
Autentizace
Po získání klíče a nastavení předplatného můžete Claude Code
začít naplno využívat! V dalších článcích této série se podíváme
na praktické využití tohoto nástroje.
Co dál?
Nyní máte Claude Code připravený k použití. V příštích
článcích si ukážeme:
Základní příkazy a ovládání Claude Code
Jak pracovat s existujícími projekty
Vytváření nových projektů od nuly
Claude Code představuje revoluci ve způsobu, jak programujeme. S tímto
nástrojem můžete realizovat své nápady rychleji než kdy dříve –
stačí jen vědět, jak ho správně používat.
„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:
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í 'let'
return price; // Používáme globální proměnnou 'price'
}
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 < 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:
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:
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 = 'https://api.example.com';
// vs.
function processOrder(items) {
// Toto NEJSOU konstanty, jen náhodou je nepřepisujeme
const total = items.reduce((sum, item) => 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 'react';
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
Znáte to – vytvoříte dotaz WHERE street = '', 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 = '' OR foo IS NULL;
Daleko horší je, že NULL se chová neintuitivně při porovnání:
SELECT * FROM tbl WHERE foo = ''; -- nezahrne NULL
SELECT * FROM tbl WHERE foo <> ''; -- taky nezahrne NULL
-- musíme použít
SELECT * FROM tbl WHERE foo IS NULL;
SELECT * FROM tbl WHERE foo <=> 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 ('') 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 != ''),
-- Nepovinné pole - buď NULL nebo neprázdný text
nickname VARCHAR(255)
CONSTRAINT nickname_not_empty
CHECK (nickname IS NULL OR nickname != '')
);
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) != ''),
...
);
Nyní neprojdou ani tyto pokusy o obejití validace:
INSERT INTO users (email) VALUES (' '); -- 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->addText('billing_street')
->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šší.
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(
'unpaid', -- původně 'waiting_payment'
'processing', -- beze změny
'completed', -- původně 'shipped'
'cancelled' -- 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 'status' 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(
'waiting_payment', -- původní hodnota
'processing', -- zůstává stejná
'shipped', -- původní hodnota
'cancelled', -- zůstává stejná
'unpaid', -- nová hodnota (nahradí waiting_payment)
'completed' -- nová hodnota (nahradí shipped)
);
2. Nyní můžeme bezpečně aktualizovat existující data:
UPDATE orders SET status = 'unpaid' WHERE status = 'waiting_payment';
UPDATE orders SET status = 'completed' WHERE status = 'shipped';
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(
'unpaid',
'processing',
'completed',
'cancelled'
);
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 'waiting_payment' i 'unpaid',
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ř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->age = 25; // OK
$person->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 => $value >= 0 ? $value : throw new InvalidArgumentException;
}
}
$person->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->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 "$this->first $this->last";
}
set(string $value) {
[$this->first, $this->last] = explode(' ', $value, 2);
}
}
}
$person = new Person;
$person->fullName = 'James Bond';
echo $person->first; // vypíše 'James'
echo $person->last; // vypíše 'Bond'
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.
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->name = $name; // První nastavení - vše OK
}
}
$user = new User;
$user->setName('John'); // Paráda, máme jméno
echo $user->name; // "John"
$user->setName('Jane'); // 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.
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.