Seznamte se s metatable – řešením pro jednoduché ukládání a vybírání strukturovaných dat v PHP napsaným v čistém PHP.
Pokud pracujete s PHP, existují v základu dvě možnosti, jak ukládat data. První z nich je pracovat se soubory, druhou použít nějakou databázi. Vývoj programátora v PHP se většinou ubírá tím směrem, že nejdříve používá první způsob (soubory) a poté své znalosti a dovednosti obohatí o druhý způsob (databáze).
Výhoda ukládání do souborů tkví v tom, že je (v závislosti na datech v nich ukládaných) jednoduché
se soubory pracovat. Ale to je z výhod asi tak všechno. Jestliže se pro práci se soubory
rozhodneme používat nějaké ty základní funkce jako fopen()
, fread()
, fwrite()
atd. a nebudeme řešit případy, kdy
je soubor otevřen z více instancí PHP interpretu, brzo narazíme. A to tvrdě. Problém
je v tom, že věci selžou tehdy, když se to nejméně hodí, a poté je už pozdě. Server
spadne zrovna v půlce zápisu důležitého souboru, dvě instance interpretu se snaží
zapisovat na stejné místo v souboru apod. Buď v souboru budou data v nekonzistentním
stavu, takže se aplikace bude chovat divně, nebo budou naprosto znehodnocena. Ani
jedno není dobré. Tématem atomicity operací se soubory se zabývá hodně článků, proto
odkazuji na Google.
Databáze naopak více přístupů ke stejným datům řeší elegantně, pád databázového serveru by také neměl znamenat, že všechna data ztratíme. Starost o toto všechno místo nás má na práci databáze. A seznam pokračuje – řazení, výběry jen určitých dat, práce s nimi, počítání… Toto všechno databáze řeší. Nutno poznamenat, že databáze nemusí být pouze typu klient-server, existují i souborové – viz SQLite. Ale pro některé druhy práce s daty by bylo cpát je databázi jako jít s kanónem na vrabce. Taky všechny databáze, které jsou dostupné pro PHP, potřebují nějaké rozšíření, žádná databáze nemá rozhraní pro přístup k sobě v jádře PHP (opravte mě, pokud se mýlím). Je potřeba říci, že většina, ne-li všechny, hostingů nabízející PHP k němu nabízí i databázi, a tudíž ho provozuje s potřebnými rozšířeními.
Disclaimer: Autor článku je hlavním vývojářem metatable.
A co je tedy metatable? Jako je výše zmiňovaná SQLite řešením na pomezí mezi „klasickou“ databází (za „klasickou“ považujme databázi s klient-server architekturou) a soubory, které se spíše blíží k databázím, tak metatable je řešení na pomezí databází a souborů tíhnoucí spíše k souborům. Jaký je výsledek? metatable nabízí ukládání dat do souborů atomicky, transakčně (jako databáze), ale zase můžete zapomenout na nějaké promyšlené řazení, počítání atd. metatable je opravdu jen o ukládání a získávání dat zpět. Také nepotřebuje žádná rozšíření – je napsána v čistém PHP. Název metatable pochází z toho, že první úmysl byl ji použít na ukládání metadat. Část table je inspirována tím, jakým způsobem jsou data adresována – viz Data v podání metatable.
Sem s ní
metatable je uvolněna pod svobodnou licencí (nová BSD licence) a hostovaná na Google Code. Můžete ji získat jednak stažením vytvořeného archivu se zdrojovým kódem a/nebo přímo z repozitáře. metatable využívá distribuovaný verzovací systém Mercurial. Pokud ho máte v systému nainstalovaný, naklonování repozitáře spočívá ve spuštění jednoduchého příkazu:
$ hg clone http://metatable.googlecode.com/hg/ metatable
Ať už v archivu nebo repozitáři najdete soubor metatable.php
, který
obsahuje veškerý potřebný kód (opravdu, jen jeden soubor), a adresář
examples/
, který v sobě ukrývá některé příklady.
Data v podání metatable
Data v souborech jsou uložena různě, jsou využívány rozličné formáty a každý se hodí pro něco jiného. Databáze, relační databáze, zase mají své relace a vztahy mezi nimi. (Je za tím hodně akademických výzkumů a mnoho začátečníků se v tom ztrácí.) A metatable všechno ukládá do velké hashmapy (můžete znát též jako hash, mapa, dictionary (česky slovník), table (česky tabulka – termín používaný v jazyku Lua), ale u PHP asi nejpoužívanější název – asociativní pole). Ve své podstatě se jedná o strukturu uložení dat, kdy ke každému klíči je přiřazena jedna hodnota. (Přičemž klíč je u metatable jedinečný.)
metatable je jedna velká hashmapa. Jako klíč je použita dvojice řetězců, označujme je jako řádek (row) a sloupec (column, col). metatable tedy mapuje tuto dvojici na hodnotu – (row : string, col : string) → value : any. Hodnota může být několika typů. metatable nyní rozpoznává a umí uložit nativní typy PHP řetězec (string), celočíselný typ (integer) a logický typ (boolean). Nyní lze ostatní typy ukládat v serializované podobě pomocí typu string, protože metatable řetězce nijak neinterpretuje.
Práce s metatable
Rozhrazní metatable by snad nemohlo být jednodušší. Po většinu času si vystačíte se čtyřmi základními metodami – pro otevření metatable souboru, pro čtení dat, pro jejich zápis (či přepis, či vymazání) a pro uzavření souboru. O všechno ostatní se stará vnitřní implementace metatable a vy už nemusíte.
Základem tedy je vytvořit si nějakou takovou tabulku:
<?php
require_once "metatable.php";
$table = metatable::open("table.metatable");
Řetězec za require_once
si upravte tak, aby směřoval na soubor s kódem
metatable. Příklad udělá jediné – otevře soubor
table.metatable
jako metatable tabulku. (Pokud soubor
neexistuje, bude při uložení tabulky vytvořen.) Ovšem ne vždycky se to musí podařit
– mohou být špatně nastavena práva k souboru, nemusí se vůbec jednat o
metatable soubor apod. Proto je potřeba kontrolovat, co tato statická metoda
vrací. Možnosti jsou dvě: úspěšně vytvořenou instanci metatable
, nebo
FALSE
.
metatable::open()
příjímá jako druhý nepovinný argument
celé číslo, které specifikuje některé věci týkající se vytvářené instance. Jedná se o
bitovou kombinaci příznaků, které má instance mít. V době psaní článku existují čtyři
příznaky:
READONLY
– tabulka bude otevřena pouze pro čtení (defaultně vypnuto)READWRITE
– otevřeno pro čtení i zápis (zapnuto)STRINGS_GC
– garbage collection řetězců (zapnuto)AUTOCLOSE
– automatické uzavření a uložení tabulky při garbage collection instancemetatable
(vypnuto)
Řekněme, že chceme otevřít tabulku s tím, že z ní budeme moci číst a do ní zapisovat, provede při zavírání garbage collection řetězců a bude se uzavírat automaticky při rušení instance:
$table = metatable::open("table.metatable",
metatable::READWRITE | metatable::STRINGS_GC | metatable::AUTOCLOSE);
Oproti tomu pro otevření pouze pro čtení můžeme použít:
$table = metatable::open("table.metatable", metatable::READONLY);
Příznak READONLY
pozitivně ovlivňuje výkon, pokud z tabulky opravdu
potřebujeme jenom číst. Taky je chování metatable
trochu jiné. Pokud
předáme příznak READONLY
, ostatní postrádají smysl. A byl-li by předán
READWRITE
a READONLY
zároveň, metoda
metatable::open()
se ani nepokusí otevřít soubor a rovnou vrátí
FALSE
. Také očekávejte jiné chování při neexistenci souboru. Pokud
soubor není v souborovém systému a my ho chceme otevřít pouze pro čtení,
metatable::open()
navrátí FALSE
, zatímco v
READWRITE
stavu vše proběhne (pokud se nenaskytne nějaká jiná překážka)
v pořádku a soubor bude vytvořen při ukládání.
Když máme vytvořenou instanci, můžeme se vrhnout na zadávání dat. O to se stará
instanční metoda set()
. Přijímá tři argumenty, kde první dva určují
právě dvojici (row, col), na kterou se namapuje třetí parametr – tedy
hodnota. metatable má omezení, které určuje, že délka názvu řádku ani
sloupce nesmí přesáhnout 124 bytů (znaků – v jednobytových kódováních) a názvy nesmí
obsahovat znak s ASCII kódem 0
. Pokud bude řetězec delší, bude oříznut a
vy se o tom nedozvíte, takže pozor. Následující kód přiřadí dvojici
("row", "col") postupně různé hodnoty:
// 1
$table->set("row", "col", 0);
// 2
$table->set("row", "col", TRUE);
// 3
$table->set("row", "col", "hello, world!");
// 4
$table->set("row", "col", NULL);
// 5
Instanční metoda get()
narozdíl od set()
data nemění, ale
pouze vybírá, a tak může být použita, pokud je tabulka
otevřena s příznakem READONLY
. Argumenty jsou stejné, až na
třetí, který get()
nemá. Pro získání dvojice ("row", "col") by
get()
vypadal následovně:
$table->get("row", "col");
Návratová hodnota je dvourozměrné pole, kde první dimenze je řádek, druhá
sloupec. Konkrétní obsah tohoto pole bude záviset na tom, jaká data zrovna tabulka
obsahuje. Jak by vypadal v jednotlivých částech našeho kódu se set()
?
var_dump()
nám to ozřejmí:
array (0) { }
array(1) { ["row"]=> array(1) { ["col"]=> int(0) } }
array(1) { ["row"]=> array(1) { ["col"]=> bool(true) } }
array(1) { ["row"]=> array(1) { ["col"]=> string(13) "hello, world!" } }
array (0) { }
Je vidět, že nastavíme-li hodnotu na NULL
, záznam bude vymazán.
get()
také podporuje žolíka *
zastupujícího jeden a více
znaků. Může být použit jak v názvu řádku, tak sloupce. Zavoláním
$table->get("*", "*");
vybereme všechna data z tabulky.
Chceme-li práci s tabulkou ukončit, prostě ji uzavřeme:
$table->close();
V závislosti na tom, s jakými příznaky je metatable::open()
zavoláno,
proběhnou potřebné úkony.
Posledními dvěma veřejnými metodami metatable
jsou index()
a unindex()
. Index je předvypočítané umístění nějaké skupiny záznamů v
tabulce. Indexuje se pomocí začátku názvu řádku. Pokud tedy např. často přistupujeme
k záznamům na řádku "row"
, můžeme si pro ně vytvořit index –
$table->index("row");
. Což nám sníží nutnost číst soubor a hledat,
kde se který záznam nachází. unindex()
přijímá stejné argumenty, akorát
index smaže.
K čemu to je?
Už víte, co to metatable je, jak se s ní pracuje, ale asi vás napadají otázky typu: k čemu to ksakru je? A není to znovuvynalézání kola? Nejdříve odpovím na tu druhou. Kolik takovýchto kol jste již viděli? Já zatím ani jedno.
Uplatnění metatable je tam, kde jsou soubory na ukládání krátké a databáze zbytečně velké. Soubory jsou ideální na ukládání velkých objemných dat, se kterými nepotřebujeme nic dělat nebo by bylo těžké s nimi něco provádět v databázi (binární data). Řekněme, že stavíme webovou galerii. Fotky budou ukládány v souborech. Ale to povětšinou nestačí, je potřeba mít možnost k nim přiřadit ještě různé popisky, nadpisy, kdo to fotil, proč to fotil, kde to fotil atp. – prostě metadata, aneb data popisující data.
Jednou a velice dobrou možností je použít databázi. Ale pro nějakou malou galérku pro mě a pár kamarádů je to přeci jenom zbytečné. Teď nastupuje na řadu metatable. Se svou dynamicky rostoucí hashmapou (a to jak do délky, tak do šířky) je na řešení dobrým kandidátem.
Mějme kupř. metatable fotky.metatable
. Jako název řádku bude vždy nějaký
jednoznačný identifikátor fotky. Tak k nějaké takové fotce přiřaďme soubor, ve kterém
se nachází: $table->set($id, "file", "/fotky/fotka.jpg");
. A můžeme
pokračovat s dalšími sloupci jako název fotky, datum vytvoření atp. K výběru všech
dat o nějaké fotce pak zavoláme $table->($id, "*");
.
A co takové štítkování fotek? Není problém.
$table->set($id, "stitek:programovani", TRUE);
přiřadí fotce štítek
(pomocí „přiřazení“ NULL
by se štítek odebral). Seznam ID fotek s jedním
štítkem získáme skrz array_keys($table->get("*", "stitek:programovani"));
.
Závěr
metatable se nesnaží nahradit ani ukládání dat do souborů, ani do databáze. Berte ji jako doplněk k předchozím dvěma. Je napsána čistě v PHP, což sráží její výkon oproti kompilovaným rozšířením, ale zase se můžete spolehnout, že půjde i bez jakéhokoli z nich. Formát souboru by se v budoucnu neměl nijak dramaticky měnit, tudíž tabulku, kterou vytvoříte dnes, dokážete přečíst i za pár let.
Výzva na konec. Zaujala-li vás metatable, jedná se o open-source projekt, tudíž můžete sami přispět – hlaste chyby, ovlivněte budoucí vývoj!