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é.
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.
SQL, vzniklé v 70. letech minulého století, představovalo revoluční
průlom v interakci člověka s počítačem. Jeho design byl navržen tak,
aby se dotazy formulovaly a četly co nejvíce jako běžná angličtina.
Například, dotaz na jména a platy zaměstnanců v SQL může vypadat takto:
SELECT name, salary FROM employee – jednoduché a srozumitelné,
že ano? Tím se databáze staly dostupné širší veřejnosti, nejen
počítačovým nerdům.
Ačkoli tento záměr byl chvályhodný, brzy se ukázalo, že na psaní SQL
dotazů jsou stejně potřeba experti. Navíc vzhledem k tomu, že nikdy
nevzniklo univerzální aplikační rozhraní, stalo se slovní ovládání pro
moderní programování spíše břemenem. Programátoři dnes komunikují
s databázemi psaním komplikovaných generátorů SQL příkazů, které
databáze následně zpětně dekódují.
A tady na scénu vstupuje GPT. Tyto pokročilé jazykové modely
přinášejí v éře umělé inteligence obdobnou revoluci, jakou SQL
znamenalo pro databáze ve své době. Umožňují interakci s databázemi na
úplně nové úrovni, o 50 let pokročilejší. Přestože zatím neexistuje
databáze, která by byla přímo ovládaná jazykovým modelem, můžeme si od
GPT nechat generovat SQL. A funguje to skvěle.
Jak na to? Stačí, když do ChatGPT zkopírujete strukturu vaší databáze,
tedy dump s příkazy CREATE TABLE, a uvedete i název
databázového serveru, který používáte. Následně můžete klást otázky
přímo v češtině.
Pokud si chcete použití této techniky ještě více ulehčit, použijte
předpřipravený SQL
Wizard, kam stačí jen překopírovat strukturu databáze a můžete se
rovnou ptát bez dalších řečí.
Pojďme si to prakticky ukázat. Jako příklad použijeme dvě databázové
tabulky, které jsou provázané skrze cizí klíč. Tabulka users
bude obsahovat informace o uživatelích a tabulka orders bude
ukládat informace o objednávkách, které uživatelé udělali. Zde jsou
definice těchto tabulek:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT,
created_at DATE,
amount DECIMAL(10, 2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
Definice zkopírujte do ChatGPT a hned se můžete zcela přirozeně a
v češtině zeptat například na všechny objednávky od uživatele Jana
Nováka. Konkrétně zadám: „Napiš SQL, které vypíše všechny objednávky
uživatele Jana Nováka.“ A GPT odpoví například takto:
SELECT orders.order_id, orders.created_at, orders.amount
FROM orders
JOIN users ON orders.user_id = users.id
WHERE users.name = 'Jan Novák';
Takový SQL dotaz dokáže samozřejmě spousta programátorů napsat
rychleji, než by se přihlásili do ChatGPT 🙂 Ale zkusme maličko
složitější příklad: „Vypiš uživatele, kteří mají jen jednu
objednávku.“
SELECT users.id, users.name, users.email
FROM users
JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.name, users.email
HAVING COUNT(orders.order_id) = 1;
Nebo „Vypiš uživatele, kteří nemají žádnou objednávku.“
SELECT users.*
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.order_id IS NULL;
Tady se ukazuje diametrální rozdíl v přístupu. Zatímco GPT říkám,
jaká data chci získat, v SQL musím popsat způsob, jak je získat. A to
vyžaduje znalosti a zkušenosti.
Díky GPT se můžete soustředit na to, co chcete získat, místo
toho, jak to získat, což je zvláště cenné pro ty, kteří nejsou
odborníky na SQL.
Na závěr si ukážeme, jak lze využít GPT k vytvoření databázových
trigerů. Například můžete napsat: „Přidej do tabulky users sloupec
order_count, který bude evidovat počet objednávek. Inicializuj ho na hodnotu
odpovídající aktuálnímu počtu objednávek každého uživatele. A vytvoř
trigger, který při vytvoření nebo smazání objednávky bude hodnotu
aktulizovat.“
GPT vygeneruje dotazy pro přidání a inicializaci sloupce
order_count:
ALTER TABLE users ADD COLUMN order_count INT DEFAULT 0;
UPDATE users SET order_count = (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id);
a požadované triggery, které udržují jeho hodnotu aktuální:
CREATE TRIGGER update_order_count_after_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
UPDATE users
SET order_count = order_count + 1
WHERE id = NEW.user_id;
END;
CREATE TRIGGER update_order_count_after_delete
AFTER DELETE ON orders
FOR EACH ROW
BEGIN
UPDATE users
SET order_count = order_count - 1
WHERE id = OLD.user_id;
END;
GPT nabízí způsob, jak efektivně a intuitivně pracovat s databázemi,
i těm, kteří nejsou odborníky na SQL. Je to revoluční nástroj, který
tentokrát opravdu zpřístupňuje pokročilé databázové operace
široké veřejnosti. Stále je však důležité mít na paměti, že každý
výstup by měl být pečlivě kontrolován, aby se zajistila správnost a
bezpečnost dat.
Pokud jste připraveni posunout své dovednosti, přijďte na školení ChatGPT.
Toto setkání vás naučí, jak z něj vytáhnout maximum pro váš osobní
i profesní život. Nezáleží na tom, zda jste začátečník nebo
pokročilý uživatel, školení bude pro vás velkým přínosem.
Pojďme jednou provždy rozlousknout tuhle věčnou otázku,
která rozděluje komunitu programátorů. Rozhodl jsem se ponořit do temných
vod regulárních výrazů, abych přinesl odpověď (spoiler: ano, je to
možné).
Takže, co vlastně HTML dokument obsahuje? Jde o mix textu, entit, značek,
komentářů a speciální značky doctype. Prozkoumejme nejprve každou
ingredienci zvlášť.
Entity
Základem HTML stránky je text, který tvoří obyčejné znaky a
speciální sekvence zvané HTML entity. Ty mohou být buď pojmenované, jako
je pro nezlomitelnou mezeru, nebo číselné, a to buď
v desítkovém   nebo šestnáctkovém
  formátu. Regulární výraz, který zachytí HTML
entitu, by vypadal takto:
(?<entity>
&
(
[a-z][a-z0-9]+ # pojmenovaná entita
|
\#\d+ # desítkové číslo
|
\#x[0-9a-f]+ # hexadecimální číslo
)
;
)
Všechny regulární výrazy jsou zapsány v režimu extended, ignorují
velikost písmen a tečka představuje jakýkoliv znak. Tj. modifikátor
six.
Značky
Tyto ikonické prvky dělají HTML tím čím je. Tag začíná
<, následuje název tagu, možná sada atributů a uzavírá se
> nebo />. Atributy mohou mít volitelnou
hodnotu a ta může být uvozena do dvojitých, jednoduchých nebo žádných
uvozovek. Regulární výraz zachytávající atribut by vypadal takto:
(?<attribute>
\s+ # alespoň jeden bílý znak před atributem
<a href="#fns" class="footnote">[\s"'<>=`/]</a>+ # název atributu
(
\s* = \s* # rovnítko před hodnotou
(
" # hodnota uzavřená ve dvojitých uvozovkách
(
<a href="#fn" class="footnote">["]</a> # libovolný znak kromě dvojité uvozovky
|
(?&entity) # nebo HTML entita
)*
"
|
' # hodnota uzavřená v jednoduchých uvozovkách
(
<a href="#fn" class="footnote">[']</a> # libovolný znak kromě uvozovky
|
(?&entity) # nebo HTML entita
)*
'
|
<a href="#fns" class="footnote">[\s"'<>=`]</a>+ # hodnota bez uvozovek
)
)? # hodnota je volitelná
)
Všimněte si, že se odvolávám na pojmenovanou skupinu entity
definovanou dříve.
Elementy
Element může představovat jak samostatná značka (tzv. prázdný
element), tak značky párové. Existuje pevný výčet jmen prázdných
elementů, podle kterých je rozeznáme. Regulární výraz pro jejich
zachytávání by vypadal takto:
(?<void_element>
< # začátek značky
( # název elementu
img|hr|br|input|meta|area|embed|keygen|source|base|col
|link|param|basefont|frame|isindex|wbr|command|track
)
(?&attribute)* # volitelné atributy
\s*
/? # volitelné /
> # konec značky
)
Ostatní značky jsou tedy párové a zachytí je tento regulární výraz
(používám v něm odvolávku na skupinu content, kterou teprve
nadefinujeme):
(?<element>
< # počáteční značka
(?<element_name>
[a-z]<a href="#fns" class="footnote">[\s/>]</a>* # název elementu
)
(?&attribute)* # volitelné atributy
\s*
> # konec počáteční značky
(?&content)*
</ # koncová značka
(?P=element_name) # zopakujeme název elementu
\s*
> # konec koncové značky
)
Speciálním případem jsou elementy jako <script>,
jejichž obsah se musí zpracovávat odlišně od ostatních elementů:
(?<special_element>
< # počáteční značka
(?<special_element_name>
script|style|textarea|title # název elementu
)
(?&attribute)* # volitelné atributy
\s*
> # konec počáteční značky
(?> # atomická skupina
.*? # nejmenší možný počet jakýchkoliv znaků
</ # koncová značka
(?P=special_element_name)
)
\s*
> # konec koncové značky
)
Líný kvantifikátor .*? zajistí, že se výraz zastaví
u první ukončovací sekvence, a atomická skupina zajistí, že toto
zastavení bude definitivní.
Komentáře
Typický HTML komentář začíná sekvencí <!-- a končí
sekvencí -->. Regulární výraz pro HTML komentáře může
vypadat takto:
(?<comment>
<!--
(?> # atomická skupina
.*? # nejmenší možný počet jakýchkoliv znaků
-->
)
)
Líný kvantifikátor .*? opět zajistí, že se výraz zastaví
u první ukončovací sekvence, a atomická skupina zajistí, že toto
zastavení bude definitivní.
Doctype
Jde o historický relikt, který dnes existuje jen proto, aby přepnul
prohlížeč do tzv. standardního režimu. Obvykle vypadá jako
<!doctype html>, ale může obsahovat i další znaky. Zde
je regulární výraz, který jej zachytí:
(?<doctype>
<!doctype
\s
<a href="#fn" class="footnote">[>]</a>* # jakékoliv znaky kromě '>'
>
)
Dejme to dohromady
Když máme hotové regulární výrazy zachytávající každou část HTML,
je čas vytvořit výraz pro celý HTML 5 dokument:
\s*
(?&doctype)? # volitelný doctype
(?<content>
(?&void_element) # prázdný element
|
(?&special_element) # speciální element
|
(?&element) # párový element
|
(?&comment) # komentář
|
(?&entity) # entita
|
<a href="#fn" class="footnote">[<]</a> # znak
)*
Všechny části můžeme spojit do jednoho komplexního regulárního
výrazu. Tohle je
on, superhrdina mezi regulárními výrazy se schopností parsovat
HTML 5.
Závěrečné poznámky
I když jsme si ukázali, že HTML 5 lze parsovat pomocí regulárních
výrazů, uvedený příklad k ničemu užitečný není. Nepomůže
vám se zpracováním HTML dokumentu. Vyláme si zuby u nevalidního dokumentu.
Bude pomalý. A tak dále. V praxi se používají spíš reguláry jako je
tento (pro hledání URL obrázků):
<img.+?src=["'](.+?)["'].*?>
Ale to je opravdu velmi nespolehlivé řešení, která vede k chybám.
Tento regexp chybně matchuje
třeba custom tagy jako například
<imgs-tag src="image.jpg">, custom atributy jako
<img data-src="custom info">, nebo selže, když atribut bude
obsahovat uvozovku <img src="mcdonald's.jpg">. Proto je
doporučeno používat specializované knihovny. Ve světě PHP máme
smolíčka, protože rozšíření DOM podporuje pouze pravěké ztrouchnivělé
HTML 4. Naštěstí PHP 8.4 slibuje parser pro HTML
5.
Video od Microsoftu, které mělo být oslnivou ukázkou
možností Copilota, je spíš tragikomickou prezentací úpadku
programátorského řemesla.
Mluvím o tomto videu.
Má demonstrovat možnosti GitHub Copilota, mimo jiné jak pomocí něj napsat
regulární výraz pro vyhledávání značek <img>
s třídou hero-image. Jenže už původní kód, který
upravují, je děravý jako švýcarský sýr, já bych se za něj styděl.
Copilot se nechá strhnout a místo opravy pokračuje ve stejném duchu.
Výsledkem je regulární výraz, který nezamýšleně matchuje i jiné
třídy, jiné značky, jiné atributy a tak dále. Ba co víc, selže, pokud je
atribut src uveden před class.
Píšu o tom, protože tato demonstrace fušeřiny, zejména vzhledem
k oficiální povaze videa, je zarážející. Jak je možné, že si toho
nevšiml ani jeden z prezentujících či jejich kolegů? Nebo si toho všimli
a řekli si, že o nic nejde? To by bylo ještě smutnější. Výuka
programování vyžaduje preciznost a důslednost, bez nichž se snadno mohou
propagovat nesprávné praktiky. Video mělo být oslavou programátorského
umění, ale já v něm vidím ponurou ukázku, jak se úroveň
programátorského řemesla propadá do propasti nedbalosti.
Ale ať nejsem jenom negativní: je tam hezky ukázáno, jak funguje Copilot
a v něm GPT, takže si je určitě pusťte 🙂
Chase ve svém příspěvku popisuje zkušenost se zaváděním mezer na
svém pracovišti a negativní dopady, které to mělo na spolupracovníky se
zrakovým postižením.
Jeden z nich byl zvyklý používat šířku tabulátoru 1, aby se vyhnul
velkým odsazením při použití obřího písma. Druhý používá šířku
tabulátoru 8, protože mu nejlépe vyhovuje na ultraširokém monitoru. Pro oba
však představuje kód s mezerami vážný problém, musí je převádět na
tabulátory před čtením a zase zpátky na mezery před komitováním.
Pro nevidomé programátory, kteří používají braillské displeje,
představuje každá mezera jednu braillskou buňkou. Pokud je tedy výchozí
odsazení 4 mezery, odsazení 3. úrovně plýtvá 12 cennými braillskými
buňkami ještě před začátkem kódu. Na 40buňkovém displeji, který se
u notebooků používá nejčastěji, je to více než čtvrtina dostupných
buněk, které jsou promrhány bez jakékoliv informace.
Nám se může přizpůsobení šířky odsazení zdát jako zbytečnost,
jsou ale mezi námi programátoři, pro které je naprosto nezbytné. A to
prostě nemůžeme ignorovat.
Tím, že budeme v našich projektech používat tabulátory, dáváme jim
možnost tohoto přizpůsobení.
Nejdříve
přístupnost, pak osobní preference
Jistě, nejde přesvědčit každého, aby se přiklonil na jednu či druhou
stranu, jde-li o preference. Každý má své. A měli bychom být rádi za
možnost volby.
Zároveň však musíme dbát na to, abychom zohledňovali všechny. Abychom
respektovali odlišnosti a používali přístupné prostředky. Jakým je
například znak tabulátor.
Myslím, že Chase to vystihl dokonale, když ve svém příspěvku uvedl,
že „…neexistuje žádný protiargument, který by se jen blížil k tomu
převážit potřeby přístupnosti našich spolupracovníků“.
Accessible first
Stejně jako při navrhování webů se vžila metodika „mobile first“,
kdy se snažíme zajistit, aby každý, bez ohledu na zařízení, měl
s vaším produktem skvělou user experience – měli bychom usilovat o
„accessible first“ prostředí tím, že zajistíme, aby každý měl
stejnou možnost pracovat s kódem, ať už v zaměstnání nebo na opensource
projektu.
Pokud se tabulátory stanou výchozí volbou pro odsazování, odstraníme
jednu bariéru. Spolupráce pak bude příjemná pro každého, bez ohledu na
jeho schopnosti. Pokud budou mít všichni stejné možnosti, můžeme
maximálně využít společný potenciál ❤️
SameSite cookies poskytují mechanismus, jak rozpoznat, co
vedlo k načtení stránky. Jestli to bylo prokliknutí odkazu na jiném webu,
odeslání formuláře, načtení uvnitř iframe, pomocí JavaScriptu atd.
Rozlišit, jak byla stránka načtena, je totiž naprosto zásadní kvůli
bezpečnosti. Závažná zranitelnost Cross-Site
Request Forgery (CSRF) je tu s námi už dlouhých dvacet let a teprve
SameSite cookie nabízí systémovou cestu, jak ji řešit.
Útok CSRF spočívá v tom, že útočník naláká oběť na stránku,
která nenápadně v prohlížeči oběti vykoná požadavek na webovou
aplikaci, na které je oběť přihlášena, a ta se domnívá, že požadavek
vykonala oběť o své vůli. A tak pod identitou oběti provede nějaký
úkon, aniž by ta o tom věděla. Může jít o změnu nebo smazání dat,
odeslání zprávy atd. Aby aplikace útoku zabránila, musí rozlišit, jestli
požadavek vznikl povolenou cestou, např. odesláním formuláře v ní
samotné, nebo nějak jinak. SameSite cookie tohle umí.
Jak to funguje? Řekněme, že mám web běžící na nějaké doméně a
vytvořím na něm tři různé cookies s atributy SameSite=Lax,
SameSite=Strict a SameSite=None. Název ani hodnota
nehrají roli. Prohlížeč si je uloží.
Když libovolnou URL na mém webu otevřu přímým zadáním do adresního
řádku nebo kliknutím na záložku, prohlížeč všechny tři cookie
odešle.
Když se na libovolnou URL na mém webu dostanu jakkoliv ze stránky
z téhož webu, prohlížeč všechny tři cookie odešle.
Když se na libovolnou URL na mém webu dostanu ze stránky z jiného
webu, prohlížeč pošle jen cookie s atributem None a
v určitých případech i Lax, viz tabulka:
Kód na jiném webu
Odeslané cookie
Link
<a href="…">
None + Lax
Form GET
<form method="GET" action="…">
None + Lax
Form POST
<form method="POST" action="…">
None
iframe
<iframe src="…">
None
AJAX
$.get('…'), fetch('…')
None
Image
<img src="…">
None
Prefetch
<link rel="prefetch" href="…">
None
…
None
SameSite cookies dokáží rozlišit jen několik málo případů, ale jde
právě o ty podstatné pro ochranu před CSRF.
Pokud mám třeba na webu v administraci formulář nebo nějaký odkaz pro
smazání položky a ten byl odeslán/odkliknut, tak nepřítomnost cookie
vytvořené s atributem Strict znamená, že se tak nestalo na
mém webu, ale že požadavek přišel odjinud, tedy že jde o CSRF útok.
Cookie pro odhalení CSRF útoku vytvářejte jako tzv. session cookie bez
atributu Expires, platnost je pak v podstatě nekonečná.
Doména vs site
„Na mém webu“ není to stejné jako „na mé doméně“, nejde
o doménu, ale o web site (proto i název SameSite). Site sice často
odpovídá doméně, ale třeba u služby github.io odpovídá
subdoméně. Požadavek z doc.nette.org na
files.nette.org je same-site, zatímco požadavek z
nette.github.io na tracy.github.io je už cross-site.
Tady je to hezky
vysvětlené.
<iframe>
Z předchozích řádků již vyplynulo, že pokud je stránka z mého webu
načtená uvnitř <iframe> na jiném webu, nepošle jí
prohlížeč Strict ani Lax cookies. Je tu ale ještě
jedna důležitá věc: pokud takto načtená stránka vytvoří
Strict nebo Lax cookie, prohlížeč je
ignoruje.
Tím vzniká možnost se bránit proti podvodnému získávání cookie
neboli Cookie
Stuffing, kde dosud systémová obrana taky chyběla. Trik spočívá
v tom, že podvodník inkasuje provizi za affiliate marketing, ačkoliv
uživatele na web obchodníka nepřivedl. Místo odkazu s affiliate ID, na
který by musel uživatel kliknout, vloží do stránky neviditelný
<iframe> se stejným odkazem a značkuje tak všechny
návštěvníky.
Cookie bez atributu SameSite
Sušenky bez atributu SameSite se vždy posílaly při jakémkoliv same-site
i cross-site požadavku. Stejně jako SameSite=None. Jenže
v blízké budoucnosti začnou prohlížeče považovat příznak
SameSite=Lax za výchozí, takže sušenky bez atributu budou
považovány za Lax. Což je docela nebývale velký BC break
v chování prohlížečů. Pokud chcete, aby se cookie i nadále chovala
stejně a přenášela se při jakémkoliv cross-site požadavku, je potřeba
jí nastavit SameSite=None. (Pokud nevyvíjíte embedované widgety
apod., moc často to nechcete.) Bohužel pro loňské prohlížeče je hodnota
None nečekaná. Safari 12 ji chápe jako Strict,
takže na starších iOS a macOS vzniká ošemetný problém.
A ještě pozor: None funguje jen když je nastaven
s atributem Secure.
Co udělat při útoku?
Utéct! Základní pravidlo sebeobrany, jak v reálném životě, tak na
webu. Obrovskou chybou spousty frameworků je, že při detekci CSRF útoku
zobrazí formulář znovu a napíší něco jako „Token CSRF je neplatný.
Zkuste prosím formulář znovu odeslat“. Tím, že jej uživatel odešle
znovu, je útok dokonán. Taková ochrana postrádá smysl, když vlastně
uživatele vyzvete, aby ji obešel.
Ještě nedávno dělal Chrome v případě cross-site požadavku to, že po
refreshi stránku zobrazil znovu, ale tentokrát cookie s atributem
Strict poslal. Takže refresh vyřadil ochranu před CSRF
založenou na SameSite cookie. Dnes už to naštěstí nedělá, ale je možné,
že to dělají jiné nebo starší prohlížeče. Uživatel také může
stránku „refreshnout“ kliknutím na adresní řádek + enter, což se bere
jako přímé zadání URL (bod 1) a všechny cookie se odešlou.
Takže při detekci CSRF je nejlepší přesměrovat s HTTP kódem
302 jinam, třeba na homepage. Zbavíte se tak nebezpečných POST dat a ani
problematická URL se neuloží do historie.
Nekompatibility
SameSite dlouho nefungovalo ani zdaleka tak, jak by mělo. Především
kvůli chybám v prohlížečích a nedostatkům ve specifikaci, která třeba
vůbec neřešila přesměrování nebo refresh. Samesite cookie se
nepřenášely třeba při uložení nebo tisku stránky, naopak se přenášely
po refreshi, když zrovna neměly atd. Naštěstí dnes už je situace lepší.
Mám za to, že z vážných nedostatků přetrvává v aktuálních verzích
prohlížečů jen ten výše zmíněný u Safari.
Doplnění: kromě SameSite lze velmi čerstvě rozlišit původ
požadavku i hlavičkou Origin,
což je nástupce hlavičky Referer více respektující soukromí uživatelů a
pravopis.
Abych si přiblížil vývoj na lokálním počítači tomu
ostrému, zprovoznil jsem si lokální https. Jak na to?
Nejsnazší cesta
V komentářích padla zmínka o aplikaci mkcert, která následující
postup udělá za vás. Ale pokud máte rádi ruční práci, čtěte
dál :)
Vygenerujeme SSL certifikát
K tomu použijeme program openssl, který určitě na
počítači najdete, třeba jako součást Gitu na
C:\Program Files\Git\usr\bin\openssl.exe nebo jinde. Následující
příkaz vygeneruje klíč do souboru rootCA.key. Bude to po vás
chtít vymyslet nějaké heslo, které si uložte.
openssl genrsa -des3 -out rootCA.key 2048
Další příkaz z klíče vygeneruje kořenový SSL certifikát a uloží
ho do souboru rootCA.pem. Jeho platnost bude 2000 dní (tedy pět
let), ale číslo klidně změňte:
Teď oznámíme operačnímu systému, aby certifikát považoval za
důvěryhodný. Postup pro Mac jsem našel třeba
tady, pro Windows je pěkně obrázek za obrázkem sepsaný
zde a pro české Windows jsem vám jej přeložil:
spustit mmc
v menu Soubor > Přidat nebo odebrat moduly snap-in
doubleclick na Certifikáty v levém okně
zvolte Účet počítače a Další
ponechte Místní počítač a Dokončit
zavřete okno tlačítkem OK
v levém okně zvolte Certifikáty, v pravém Důvěryhodné kořenové
certifikační autority
pravé tlačítko a v kontextovém menu Všechny úkoly >
Importovat
objeví se Průvodce importem certifikátu, dejte Další
vyberte soubor rootCA.pem a odklikejte průvodce na konec
Ufff, jsme v půlce. Pokračujeme v Konzoli:
opět v menu Soubor > Přidat nebo odebrat moduly snap-in
doubleclick na Editor objektů zásad skupiny
stiskněte Dokončit a zavřete okno tlačítkem OK
v levém okně otevřete Místní počítač – zásady > Konfigurace
počítače > Nastavení systému Windows > Nastavení zabezpečení >
Zásady veřejných klíčů
doubleclick na Nastavení ověření cesty certifikátů
zatrhněte Definovat tato nastavení, Povolit ověřování certifikátů
… a Povolit uživatelům důvěřovat certifikátum …
potvrďte tlačítkem OK
Hotovo, zavřete Konzoli.
Všichni už certifikátu věří, akorát Firefox chce ještě
popostrčit:
Klikačku máme za sebou, teď vyrobíme certifikáty pro server. Vytvoříme
soubor server.csr.cnf s tímto obsahem:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[dn]
C = CZ
ST = Random
L = Random
O = Random
OU = Random
emailAddress = example@example.com
CN = localhost
A dále soubor v3.ext, kde bude uveden seznam všech domén,
které na localhostu provozujete:
Jak vidíte, lokální verze webů provozuji na doménách, které končí na
.l, u vás to třeba bude jinak. Ty jednotlivé klíče
DNS.1 apod. je potřeba fakt jako idiot postupně očíslovat.
Následujícím krokem vygenerujeme soubory server.key
aserver.csr:
Všechny dosud vytvořené soubory včetně hesla si někam uložte. Ať se
totiž změní domény, které provozujete lokálně, jen upravíte
v3.ext a posledním příkazem znovu vygenerujete certifikát pro
server.
Konfigurace webového serveru
Zbývá povolit https na serveru. Tj. nakonfigurovat server tak, aby
naslouchal na portu 443, na kterém běží https, a používal vygenerovaný
certifikát.
Používám Apache, do jehož konfiguračního soubor httpd.conf
jsem přidal následující řádky s cestou k souborům
server.key aserver.crt: