Vítejte u třetího dílu seriálu o tvorbě dynamických doplňků na webové stránky. Dnes se podíváme na zoubek databázím.
Databáze je úložiště dat na serveru. Naše skripty z něj můžou jak číst, tak do něj i zapisovat. Obslužné programy se automaticky starají o to, aby nedocházelo ke kolizím při současném přístupu více uživatelů. Kromě toho poskytují nejrůznější pomocné funkce pro práci s daty.
V tomto seriálu se zaměříme na databázi MySQL, která se v současné době používá asi nejčastěji. Zkratka SQL znamená "structured query language", což se obvykle překládá jako "strukturovaný dotazovací jazyk" a pomocí něj s databází komunikujeme (po HTML a PHP je to tedy už třetí jazyk, který je potřeba se naučit). Termínem "dotaz" (query) se rozumí jakýkoli příkaz pro databázi, ne jenom skutečné otázky jako třeba "kolik je v téhle tabulce řádků?". V dalším textu ale tohle slovo stejně používat nebudu, takže je to jedno.
MySQL (a všechny ostatní databáze používající jazyk SQL) je tzv. relační databáze. Data jsou v ní uspořádána v tabulkách. Řádky představují jednotlivé záznamy a sloupce pak datové položky uvnitř záznamů. Představit si to můžete třeba takhle:
SPZ | typ | majitel |
---|---|---|
LNC 2022 | Škoda 120 | Jiří Nebozíz |
1U0 8729 | Tatra 815 | Sopwith Camel |
1P2 3230 | Škoda Felicia | Jarmila Nováková |
PZA 4876 | Fiat 500 | Kuo Ču Nguyen |
Řádky můžeme celkem jednoduše přidávat, mazat a přepisovat a jejich počet obvykle není nijak omezen. Strukturu tabulky, tedy počet a jména sloupců, si zvolíme jednou při vytváření tabulky a pak už s nimi většinou nehýbeme (teoreticky to sice jde, ale moc často se to nedělá).
Jinak než do tabulek data ukládat nejdou, takže i kdybychom potřebovali uložit třeba jenom jedno jediné číslo, musíme si na to vyrobit tabulku 1x1.
Slovo "relační" znamená, že mezi jednotlivými tabulkami můžou vznikat vztahy neboli relace. Pokud máme např. výše uvedenou tabulku automobilů a k ní třeba ještě tabulku lidí se jménem, adresou a datem narození, můžeme si mezi nimi představit vztah "člověk vlastní auto". Ale je to jenom pomyslné, mezi tabulkami nevedou žádné ukazatele nebo jiné fyzické vazby.
Až na výjimky se dá databáze najít prakticky na každém serveru, včetně většiny freehostingů. Jediné, co potřebujete udělat, je databázi si nějak aktivovat a získat k ní přihlašovací údaje. Tohle je obecně na každém serveru jiné, proto vám nemůžu podrobně popsat každé kliknutí. Prostě zapátrejte v administračním rozhraní po slovech jako "databáze" nebo "MySQL" a postupujte podle pokynů, které tam najdete (jako příklady budu používat servery IC a Endora, které znám). Jakmile projdete touhle první fází, funguje všechno ostatní všude stejně.
Každý skript, který chce pracovat s databází, se k ní nejdřív musí připojit. Když říkám každý, myslím tím opravdu úplně každý PHP soubor - nestačí to jenom jednou. Naštěstí známe příkazy include a require, takže připojovací kód nemusíme pokaždé opisovat. Funkce pro připojení vypadá takhle:
mysql_connect(jméno hostitele, přihlašovací jméno, heslo)
V praxi to může vypadat třeba takhle:
$spojeni=mysql_connect('localhost','moje_jmeno','StrasneTajneHe5l0'); if (!$spojeni) die('Nepodařilo se připojit k databázi.');
Funkce die() vypíše hlášku v závorce (podobně jako třeba echo, můžete tam dát i HTML kód) a okamžitě ukončí skript. Teoreticky bychom to dělat nemuseli, mohli bychom jenom přeskočit všechny další přístupy k databázi a vykreslit aspoň zbytek stránky, ale jestli z databáze taháme "užitečný náklad" (to, kvůli čemu sem návštěvníci přišli), nestálo by to za námahu.
Protože mysql_connect() i die() jsou funkce a protože PHP vyhodnocuje logické výrazy jenom tak dlouho, dokud není výsledek jasný, můžeme výše uvedené dva příkazy zkrátit na jeden:
$spojeni=mysql_connect(...) or die(...);
Teoretická odbočka. Vyhodnocení příkazu "A = B or C" probíhá takto: nejdřív se zjistí hodnota B. Když je true (nebo obecně cokoli nenulového), bylo by B or C určitě true, ať už je C jakékoli, proto se C vyhodnocovat vůbec nebude a do A se rovnou vloží true. Pokud je B false (nula), záleží výsledek oru na hodnotě C, takže se musí zjistit taky. Konec odbočky.
Podobné úsporné zápisy používají Céčkem odkojení programátoři poměrně často, proto počítejte s tím, že je v cizích kódech můžete každou chvíli potkat. Já je moc často nepoužívám.
Ještě jedna důležitá věc. Jak vidíte, heslo k databázi se píše přímo do zdrojáku a nedá se nijak zamaskovat. Z toho vyplývají dvě věci: za a), zdroják je za normálních okolností naprosto bezpečně skrytý, server ho ven nepouští. A za b), dávejte si velký pozor na funkce pro přímé čtení a zobrazování obsahu souborů, jako jsou show_source(), highlight_file(), file(), file_get_contents(), fread() a podobně. Jestli se nepojistíte proti PHP injekci (viz minulý díl), může se vám kdokoli dostatečně mazaný k heslu dostat.
Ať už máme databázi jednu nebo několik, musíme systému říct, se kterou z nich chceme pracovat:
mysql_select_db('jmeno_databaze',$spojeni);
$spojeni je proměnná, kterou nám vrátila funkce mysql_connect() (tohle jméno budu používat i v celém dalším textu). Jméno databáze jsme se dozvěděli při jejím vytváření - buď jsme si ho zvolili sami nebo nám ho server přidělil.
Tyhle dva kroky (připojení a výběr) je potřeba zopakovat na začátku každého skriptu, který má s databází pracovat. Nejlepší je dát si je do jednoho malého skriptu, který pak includneme, resp. requirneme (strašné slovo), na začátek všech ostatních. Hlavní výhoda je v tom, že když potřebujeme změnit přihlašovací údaje, nemusíme je pracně přepisovat na x místech.
Tím máme úvodní administrativu za sebou, odteďka můžeme používat příkazy jazyka SQL.
Poznámka: existuje ještě příkaz mysql_close(), který spojení s databází ukončí. Praktické využití má snad jedině v případě, kdybychom uprostřed skriptu potřebovali přelézt z jednoho databázového systému do jiného, protože normálně se spojení na konci skriptu ukončuje automaticky (stejně jako se třeba mažou proměnné a jiná pomocná data). Takže se tím vůbec nemusíme zabývat.
Konečně se dostáváme k onomu slavnému SQL. Příkazy (dotazy) se databázi zadávají pomocí následující funkce:
$vysledek = mysql_query('příkaz jazyka SQL',$spojeni);
$spojeni je naše známá návratová hodnota z mysql_connect, tady má význam vstupního parametru a určuje, pro kterou databázi je příkaz určen.
Do proměnné $vysledek se uloží to, co nám databáze vrátí. U příkazů typu "vytvoř tabulku" nebo "vymaž řádek" tam bude jenom jednoduché true/false podle toho, jestli se to povedlo, ale u dotazů jako "dej mi všechny řádky z téhle tabulky" tam najdeme data, která se z databáze přečetla (případně false, kdyby se operace nepovedla).
Už víme, kudy se k databázi dostaneme. Teď na chvíli odložíme PHP stranou a zaměříme se čistě jenom na samotný SQL. Ničeho se nebojte, je to jazyk velice jednoduchý a srozumitelný. Jeho syntaxe vychází z angličtiny a příkazy se podobají normálním větám, takže jestli umíte anglicky, máte napůl vyhráno. Jestli ne, nevadí, budu překládat.
Veškerá data v databázi musí být uložena v tabulkách, takže tímhle musíme začít. Příkaz se jmenuje CREATE TABLE (čili "vytvoř tabulku") a pro výše uvedenou tabulku aut by mohl vypadat třeba takhle:
CREATE TABLE auta ( spz CHAR(8) PRIMARY KEY, typ VARCHAR(30) NOT NULL, majitel TINYTEXT );
Tím jsme řekli, že chceme vytvořit tabulku jménem `auta`, ve které budou sloupce `spz`, `typ` a `majitel`. U každého sloupce je potřeba uvést datový typ (tedy co se do něj bude ukládat) a dále mohou následovat další upřesňující informace: že je to klíč, že nesmí být prázdný apod..
Pro přehlednost tady do dvou tabulek shrnu úplně všechno, s čím se během celého seriálu možná potkáme. Zatím si je jen tak prolítněte; vrátit se sem můžete později, až něco budete potřebovat. Konkrétní použití a podrobnější popisy si stejně probereme na příkladech.
Celá čísla: | |
---|---|
TINYINT | osmibitové číslo |
SMALLINT | 16bitové číslo |
MEDIUMINT | 24b číslo |
INT | 32b |
BIGINT | 64b |
Když za typem uvedeme upřesňující slovo UNSIGNED, čísla budou bez znaménka (tedy přirozená), s rozsahem 0..2počet bitů. Pokud neuvedeme nic, čísla jsou celá se znaménkem a rozsahem posunutým o polovinu do záporna (tedy např. místo 0..255 bude -127..128). | |
Texty: | |
TINYTEXT | max. 255 B |
TEXT | max. 64 KB |
MEDIUMTEXT | max. 224 B |
LONGTEXT | max. 232 B |
VARCHAR(max. počet znaků) | počet znaků jaký si zvolíte, max. 64 KB |
CHAR(počet znaků) | pevný počet znaků jaký si zvolíte, max. 255 |
V ASCII textech platí uvedené maximální délky přímo pro znaky (1 znak = 1 B). V kódování Unicode je potřeba počítat s tím, že jeden znak může zabrat víc bytů. | |
Ostatní: | |
DATE | datum jako textový řetězec ve tvaru 'rrrr-mm-dd', rozsah od '1000-01-01' do '9999-12-31' |
TIME | čas jako textový řetězec ve tvaru 'hh:mm:ss' |
DATETIME | prakticky je to zřetězení data a času: 'rrrr-mm-dd hh:mm:ss' |
TIMESTAMP | Navenek se chová stejně jako Datetime, ale interně je to dword s počtem sekund uplynulých od 1.1.1970. Rozsah mu končí v lednu 2038, takže tenhle typ moc nedoporučuji a uvádím ho jenom pro úplnost. |
BLOB a spol. | Obecná data v binárním tvaru, použití analogické k typům TEXT. Nebudeme je potřebovat. |
Upřesňující informace k datovým typům: | |
---|---|
UNIQUE | Říká, že nesmí existovat víc řádků, které mají v této položce stejnou hodnotu (s výjimkou hodnoty NULL). Smysl to má pouze u klíčů. |
NOT NULL | Tahle hodnota nesmí být prázdná - nepůjde do ní vložit hodnota NULL. |
PRIMARY KEY | Tím se určí, že se tenhle sloupec (v každé tabulce max. jeden) bude používat jako klíč. Vhodné pro nějaké relativně krátké identifikační kódy, podle kterých budeme řádky nejčastěji hledat. Primární klíč je vždy NOT NULL a UNIQUE; když to neřekneme my, dostane tyhle vlastnosti implicitně. |
AUTO_INCREMENT | Jen pro čísla. Při vkládání řádku dejte této položce hodnotu NULL a systém jí automaticky přidělí hodnotu o 1 větší než dal minulému řádku (přírůstek se teoreticky dá změnit, ale tím se teď nebudeme zatěžovat). Výborná věc pro pohodlnou tvorbu unikátních identifikačních klíčů. |
DEFAULT hodnota | Výchozí hodnota, kterou položka dostane, když ji při vkládání řádku neuvedeme. Nefunguje na Texty, Bloby a položky s auto_incrementem. Jen pro úplnost, prakticky to nevyužijeme. |
OK, jdeme na to. Naše první databázové udělátko bude jednoduché počítadlo přístupů.
Začít musíme vytvořením tabulky. Co všechno v ní budeme potřebovat? Určitě číslo, do kterého uložíme stav počítadla, to je jasné. Počty návštěv se u začínající stránky pohybují zhruba v řádu tisíců, později desetitisíců a jestli se rozjedete opravdu hodně, můžete se dostat třeba až k milionům. Jako datový typ tedy zvolíme něco dostatečně velkého, např. INT UNSIGNED.
Pro jedno počítadlo by tohle stačilo. Ale co kdybychom jich časem začali potřebovat víc, třeba kdybychom chtěli počítat přístupy na každé podstránce zvlášť? V takovém případě bude potřeba jednotlivá počítadla nějak odlišit. Můžeme jim přidělit číslo nebo třeba textové jméno, to je jedno, hlavně aby mělo každé svoje jedinečné označení. Pro ilustraci použijeme písmeno: CHAR(1). Příkaz pro vytvoření tabulky tedy bude vypadat takto:
CREATE TABLE pocitadla ( id CHAR(1), hodnota INT UNSIGNED )
Tím nám vznikne tabulka jménem pocitadla, ve které budou dva sloupce: id a hodnota. Jméno id je víceméně tradice; pro identifikační klíče ho používají prakticky všichni, tak ho použijeme i my. Ale není to nutné - jestli chcete, pojmenujte si ho jinak.
Praktické provedení v PHP:
$OK=mysql_query("CREATE TABLE pocitadla (id CHAR(1), hodnota INT UNSIGNED)",$spojeni); if ($OK) echo 'OK, tabulka je vytvořená'; else echo 'Pozor, chyba - tabulku se nepodařilo vytvořit!';
Při takovéhle jednorázové akci celkem nemá cenu se piplat s kontrolou úspěšnosti, protože si tabulku můžeme ručně zkontrolovat v administračním rozhraní, které servery obvykle poskytují. Vlastně i ten příkaz pro vytvoření můžeme pustit přímo tam a ne v PHP. Je to na vás.
Dobrá, máme tabulku, ale zatím prázdnou. Co dál?
Příkaz se jmenuje (překvapivě) INSERT, tedy "vlož". Syntaxe vypadá takto:
INSERT INTO tabulka VALUES (první, druhá, třetí, ... poslední) vlož do hodnoty
V závorce vypíšeme požadované hodnoty pro všechny položky (sloupce) vkládaného řádku, ve stejném pořadí, v jakém byly uvedeny při CREATE TABLE.
V našem případě bude vložení počítadla vypadat takhle:
INSERT INTO pocitadla VALUES ('A',0)
Počítadlo jsem si pojmenoval "A" a dal mu počáteční hodnotu 0. Zápis v PHP už si domyslete sami, návratová hodnota z mysql_query() bude opět true (povedlo se) nebo false (chyba). Tabulka tedy dopadla takhle:
id | hodnota |
---|---|
'A' | 0 |
Tím je počítadlo připraveno k použití. Budeme s ním dělat celkem dvě věci: zobrazovat jeho aktuální hodnotu a zvyšovat ji o 1 při každém načtení stránky (filtrování opakovaných přístupů ze stejného počítače si necháme na jindy).
SQL na to má příkaz SELECT ("vyber"), který z dané tabulky vybere podtabulku o zadaných rozměrech a vlastnostech. Možností, jak příkaz přesně nasměrovat na data, která chceme, je nepřeberně. Úplně nejzákladnější syntaxe vypadá takhle:
SELECT * FROM tabulka vyber z
Takový příkaz nám dá kompletně celý obsah tabulky. Hvězdička znamená "všechny sloupce", nepřítomnost jakýchkoli omezujících podmínek znamená, že do výběru padnou úplně všechny řádky. Na jednořádkovou tabulku s počítadlem by nám už tohle teoreticky stačilo, ale podíváme se ještě na další možnosti.
SELECT * FROM tabulka WHERE podmínka vyber z kde
Tím se výběr řádků zúží pouze na ty, které splňují danou podmínku. Pro naše počítadlo by podmínka mohla být WHERE id='A' (pozor: narozdíl od PHP, znak "=" tady znamená "rovná se" a ne "přiřaď"). To už by nám určitě stačilo, ale ještě to není úplně dokonalé. Identifikační kód z tabulky číst nepotřebujeme, protože ho nehodláme zobrazovat; stačit nám bude jenom sloupec s hodnotou. Sloupce se filtrují takto:
SELECT sloupec1, sloupec2, ... sloupecN FROM tabulka
Tím tedy máme všechno, co potřebujeme. Příkaz pro načtení hodnoty počítadla A by mohl vypadat takhle:
SELECT hodnota FROM pocitadla WHERE id='A'
Teď už je přístup přes PHP samozřejmě nezbytný, protože v něm budeme zpracovávat to, co z databáze vypadne:
$vysledek=mysql_query("SELECT hodnota FROM pocitadla WHERE id='A'",$spojeni);
Důležitá je proměnná $vysledek. Do té systém uloží data, která z databáze načetl, a to ve formě tabulky (my jsme sice přečetli jenom jedno jediné číslo, ale i tak ho PHP vidí jako tabulku o velikosti 1×1). Kdyby se nic nenačetlo, protože třeba v tabulce nic není nebo žádný řádek nesplňuje zadané podmínky, vrátí se prázdná tabulka (ale pozor, logicky vzato je to pořád true!). V případě vážné chyby (syntaktická chyba v příkazu, nefunkční nebo nepřipojená databáze apod.) dostaneme false.
Z návratových tabulek se nedá číst přímo, ale je na to celá sada speciálních funkcí:
Plus pár dalších, které nebudeme potřebovat.
Takže pokračujeme: hodnotu počítadla máme načtenou v proměnné $vysledek, teď si ji vytáhneme do obyčejného čísla:
$radek=mysql_fetch_array($vysledek); if ($radek) //Povedlo se? Kdyby ne, vyšla by logická nula. $cislo=$radek['hodnota']; //OK, máme to - přečti číslo. else $cislo='?'; //Nepovedlo se - dej tam nějakou chybovou hlášku. echo 'Jsi náš '.$cislo.'. návštěvník!';
Mohli bychom použít i funkci mysql_fetch_row, v tom případě by třetí řádek vypadal takhle: $cislo=$radek[0] (jak vidíte, číslování se vztahuje na výslednou podtabulku, ne na původní tabulku, kde máme hodnotu na druhém místě).
Tím máme další krok hotový, zbývá už jenom připočítat tuhle návštěvu k uložené hodnotě.
Příkaz se jmenuje UPDATE (přeložitelné jako "změň", "uprav" nebo "aktualizuj"). Syntaxe:
UPDATE tabulka SET sloupec1=hodnota1, sloupec2=hodnota2 atd. WHERE podmínka uprav nastav kde
Nová hodnota vybraného sloupce může být jakýkoli výraz kompatibilního typu a může se v něm objevit i původní hodnota, např. cislo=10*cislo-5.
Filtrovací část s WHERE funguje stejně jako u Selectu. Když ji neuvedete, změna se provede na všech řádcích tabulky.
Přičtení jedničky k počítadlu je celkem jednoduchá věc:
UPDATE pocitadla SET hodnota=hodnota+1 WHERE id='A'
Po zabalení do funkce mysql_query příkaz vrací buď true nebo false podle toho, jestli se úprava povedla.
Tím máme počítadlo hotové, finální sesypání do jednoho skriptu už nechám na vás.
Pro úplnost doplním ještě jednu věc:
Kdyby vás nějaká tabulka omrzela a chtěli jste ji zlikvidovat (při počátečních experimentech se to stává celkem často), dělá se to tímhle příkazem:
DROP TABLE tabulka zahoď tabulku
Samozřejmě je potřeba dávat pozor, co se maže - není tady žádný čudlík "zpět", takže případný přehmat by šel napravit jenom v případě, že databázi máte někde zálohovanou.
Tohle jsem možná měl naťuknout na samém začátku. Dynamické stránky (*.php) se načítají mnohem pomaleji než statické (*.htm, *.html). Rozdíl může dosahovat až několika vteřin, během kterých místo dynamické stránky vidíte prázdné okno, zatímco statická už se dávno vykresluje. Je to dáno tím, že HTML se do prohlížeče posílá rovnou, ale PHP musí server napřed přechroustat.
Pokud svůj web máte čistě dynamický nebo čistě statický, může vám to být celkem jedno. Ale jestli používáte rámy (frameset) a v jednom máte obsah statický a v jiném dynamický, je to horší - když je jedna část stránky načtená a na druhou se pořád ještě čeká, vypadá to dost blbě.
Co s tím? První, celkem očividné řešení je udělat obsah všech rámů stejně rychlý - aspoň na úvodní stránce, kde se mají zobrazit současně, potom už je to jedno. Jednoduchým přepsáním koncovky z htm na php se stránka příslušně zpomalí a je vystaráno. Jestli ale dáváte přednost rychlosti statických stránek, je tu druhé řešení: umístit dynamické prvky do nenápadného iframu s neviditelným okrajem (frameborder="0"). Výsledkem je rychle načtená statická stránka, do které po pár vteřinách naskočí obsah iframu. Pro důležitý obsah se to nehodí, ale pro počítadla přístupů a podobné drobnosti je to naprosto ideální.
To by pro dnešek mohlo být asi tak všechno...
No jo, jasně - tady je: dev.mysql.com (nezaměňujte dev za www, tím byste se dostali na komerční část stránek, odkud se k manuálům dá dopídit snad jedině přes vyhledávací okénko). V tabulce uprostřed stránky si vyberte svůj oblíbený jazyk a datový formát, klikněte a jste tam.
A to už je opravdu všechno. Příště se do databází ponoříme trochu důkladněji a vytvoříme si anketu.