Generátor testovacích dát v PHP - Návrh, entity a jadro
Vítam vás v tutoriálu o tvorbe generátora náhodných dát do databázy. Tento doplnok, ak chcete script, nám vygeneruje rôzne (náhodné) kombinácia dát a tie odošle do databázy. Tutoriál počíta s tým, že rozumiete databázový ovládačmi PDO a OOP. Predstavme si, že robíme aplikáciu, ktorá pracuje s databázou - častý model. Aby aplikácia fungovala, potrebujete ju otestovať, ale kde dáta zohnať? Predstavme si, že tvoríme tabuľku používateľov, potrebujeme na nejakých užívateľoch aplikáciu otestovať a na tento účel sa bude hodiť náš script, ktorý databázovú tabuľku (užívatelia) naplní náhodnými dátami a to úplne automaticky.
Pre lepšiu predstavu si ukážme obrázok z posledného dielu, na ktorom sú vidieť riadky v tabuľke užívateľov. Tieto riadky sama vygenerovala aplikácie podľa jej nastavenia (na obrázku ich je len niekoľko, naozaj ich je v databáze 500).
K vygenerovanie testovacích používateľov stačí napísať tento kód:
$generator->Generate("uzivatele", 500, array( "id" => "Id", "jmeno" => "Jmeno", "prijmeni" => "Prijmeni", "email" => "Email:jmeno,prijmeni", "telefon" => "Telefon", "datum_narozeni" => "Datum:1970-01-01,2013-01-01", "expirace_platebni_karty" => "Datum:2015-01-01, 2030-01-01"));
Návrh
Aby malo všetko väčší zmysel, budeme skript tvoriť objektovo. Pred tvorbou budeme rozmýšľať nad návrhom. Náš script bude veľmi robustný a bude veľmi dobre rozšíriteľný. Možno ste si zatiaľ pod pojmami robustný a dobre rozšíriteľný nič nepredstavili. Tak sa zase pozrime na databázovú tabuľku. Čo budeme generovať? Mená, priezvisko, dáta, čísla, emailové adresy, ... A aby to bolo ešte zábavnejšie, budeme vedieť dáta aj prepájať s ďalšími tabuľkami. Teraz už vieme, čo znamená to robustný, s tým mierne súvisí aj ľahko rozšíriteľný. Prečo by si každý programátor, ktorý script použije, nemohol dopísať svoje vlastné dátové entity? Čo keď náš script nebude vedieť generovať PSČ a on ho práve bude potrebovať?
V článku budem používať 2 pojmy.
- Entita - jednotka, ktorá nám bude generovať hodnoty (napr. Meno, priezvisko, tel. Číslo, ...)
- Jadro - jadro nášho scriptu, ktoré jednak bude pracovať so samotnou databázou, všetkými tabuľkami a samozrejme s entitami.
Entita
Entita musí vždy odovzdať nejaké informácie jadru. Možno si poviete čo viac než náhodnú hodnotu budeme potrebovať? Ale uvedomte si, že Id je entita a keď v databázovej tabuľke používate primárne kľúče, tak viete, že pri vkladaní do databázy sa tieto kľúče neuvádza. Rovnako tak prečo by naša jadro malo zložito zisťovať, aký SQL typ má použiť, keď mu to entity môžu ľahko povedať.
Pre Entitu si napíšeme rozhrania IRandomizableItem. Rozhranie bude mať dve bezparametrické metódy: - RandomValue () - Vracia náhodnú hodnotu
- ColumnSqlType () - Vracia string s SQL typom (napr. "Varchar (55)").
Týmto entita však nebude končiť.
Zoberme si, že aj prepojenie stĺpcov bude vlastne entita (ostatne prečo nie, tiež pracuje sa stĺpci a hodnotami), preto všetkým entitám odovzdáme v metóde Load niekoľko parametrov, s ktorými by mohli pracovať. Tým prvým bude rozpracovaná položka, ktorá sa odošle do databázy. Druhý parameter budú parametre, ktoré budú odovzdané pri definícii dátového typu (k tomu sa dostaneme neskôr). Tretí parameter bude polia všetkých tabuliek a ich hodnôt a štvrtý, posledný, parameter bude definícia stĺpcov všetkých tabuliek.
Každá Entita musí mať metódu Load, ale kvôli tomu, že nie každá entita potrebuje všetky parametre volania, metódu Load nemôžeme zaradiť do rozhrania. Nakoniec bude mať každá entita verejné atribúty $ insertToDefinition a $ insertToTable. $ InsertToDefinition hovorí, či sa stĺpec vkladá do definície tabuľky pri vytváraní a $ insertToTable zas či sa má hodnota vkladať do tabuľky pri vkladaní dát. Kvôli týmto atribútom parametre odovzdávame v Load () a nie v konstruktoru. Rozhranie bude vyzerať nasledovne:
interface IRandomizableItem { function RandomValue(); function ColumnSqlType(); }
Teraz si poďme implementovať prvú entitu. Začnime treba menom. Vytvorme si triedu Jmeno, ktorá implementuje rozhranie IRandomizableItem.
Poznámka: Odporúčam používať autoloader.
Triede nastavíte verejné atribúty $ insertToDefinition a $ insertToTable na true, meno sa bude vkladať všade. Naša trieda bude mať pole mien az toho vyberie náhodnú položku. Pridajme si statické pole $ dataJmena s nejakými hodnotami:
static $dataJmena = array("Michal", "David", "Ondřej", "Matěj", "Ludík", "Josef", "Adam", "Tomáš");
Metóda RandomValue () teda vyberie jednu náhodnú hodnotu a metóda ColumnSqlType () vráti tento string:
varchar(64) COLLATE utf8_czech_ci
Nakoniec nezabudnite implementovať metódu Load (), tá v prípade mená nebude brať žiaden parameter a nebude nič robiť. Celá trieda by mohla vyzerať nasledovne:
class Jmeno implements IRandomizableItem { static $dataJmena = array("Michal", "David", "Ondřej", "Matěj", "Ludík", "Josef", "Adam", "Tomáš"); public $insertToDefinition = true; public $insertToTable = true; function load() {} function RandomValue() { return self::$dataJmena[rand(0,count(self::$dataJmena) - 1)]; } function ColumnSqlType() { return "varchar(64) COLLATE utf8_czech_ci"; } }
Podobné budú nasledujúce entity, od tých sa ale teraz vzdialime k jadru.
Jadro
Pridajme si do projektu súbor SampleDataGenerator.php a pridajme si do neho rovnomennú triedu. Do súboru si nakoniec priraďte aj triedu DatabaseManager, čo bude pre nás taký na mieru prispôsobený databázový wrapper a triedu DataRow, čo je akási jednotka pre vytváranie riadok, ostatne to bude práve objekt, ktorý bude entitám odovzdaný v prvom parametri.
SampleDataGenerator
Náš SampleDataGenerator bude mať dve verejné pole - $ Tables a $ TableColumns. $ Tables bude asociatívne pole, ktoré bude mapovať názov tabuľky na pole DataRow. $ TableColumns bude tiež slovníkové poľa v podobnom formáte, ktoré bude mapovať názov tabuľky na asociatívne pole [stĺpec] => dátový typ stĺpca. Čiže TableColumns bude mať ešte vnorená slovníková poľa.
SampleDataGenerator bude mať metódy Generate () a Save (). Save () neprijíma žiaden parameter a iba odosiela na databázu otázky. Generate () prijíma tri parametre: názov tabuľky, počet riadkov, stĺpce. Názov tabuľky a počet riadku je myslím celkom jasný, parameter stĺpca bude opäť slovníkové poľa vo formáte [názov stĺpca] => (string) jeho typ s parametrami. Tu si ešte všimnite typu s parametrami, ten sa bude zapisovať vo formáte typ: parametre. Parametre budú vždy vo stringu a nebude pre ne platiť žiadny separátor. Každá entita si však môže "nadefinovať" a implementovať vlastný. No, ak ste sa v tejto znáške odborných pojmov trochu stratili, nevadí, vlastne sme si povedali, ako sa script bude používať. Ukážeme si ešte kód:
$generator = new SampleDataGenerator(); $generator->Generate("uzivatele", 500, array( "id" => "Id", "jmeno" => "Jmeno", "prijmeni" => "Prijmeni", "email" => "Email:jmeno,prijmeni", "telefon" => "Telefon", "datum_narozeni" => "Datum:1970-01-01,2013-01-01", "expirace_platebni_karty" => "Datum:2015-01-01, 2030-01-01")); $generator->Save();
Na prvom riadku si vytvoríme generátor, ľahké. Potom mu povieme, nech vytvorí tabuľku uzivatele a tú naplní 500 ľuďmi, pričom má naplniť stĺpce id, meno, priezvisko, email, telefón a dáta v minulosti a budúcnosti. Všimnite si emailu, tam v parametroch odovzdávam názvy stĺpcov, z ktorých potom bude email čerpať. Ďalej sú zaujímavé dáta, tým špecifikujeme rozsah od kedy do kedy. Výsledné náhodné dátum bude v tomto rozmedzí. Nakoniec to pošleme do databázy. Najskôr ale vytvoríme triedu reprezentujúci riadok - DataRow.
DataRow
DataRow bude potrebovať stĺpce tabuľky, kam patrí a všetky tabuľky. Tieto hodnoty mu odovzdáme v konstruktoru. Nakoniec budeme potrebovať atribút $ values čo bude slovníkové poľa vo formáte [názov stĺpca] => [hodnota stĺpca pre položku].
public $tables; public $columns; public $values = array(); function __construct($columns, $tables) { $this->columns = $columns; $this->tables = $tables; }
DataRow bude mať tiež metódu Generate (), ktorá nebude prijímať žiaden parameter a vygeneruje hodnoty riadku. Metóda prejde cyklom foreach stĺpca ($ this-> columns), z nich v tomto cykle dostane meno a jeho typ.
foreach ($this->columns as $columnName => $columnType)
Typ stĺpca, ako už sme si povedali, obsahuje názov a parametre. Názov je názov našej entity a parametre sú voliteľné parametre. Tieto dve časti sú oddelené dvojbodkou. Hodnoty dosadíme do premenných $ generatorName a $ paramsForGenerator, pričom $ paramsForGenerator bude mať predvolenú hodnotu prázdny string.
$generatorParts = explode(":", $columnType); $generatorName = $generatorParts[0]; $paramsForGenerator = ""; if (isset($generatorParts[1])) { $paramsForGenerator = $generatorParts[1]; }
Asi vás napadne, ako ale vytvoriť inštanciu entity zo stringu? PHP má na toto krásnu syntax, jednoducho mu povieme new $ nazevPromenne (parametre) a on vytvorí novú inštanciu triedy, ktorej názov obsahuje premenná $ nazevPromenne, pričom môžeme samozrejme doplniť parametre. Ako už vieme, parametre sa entitám neodovzdávajú v konstruktoru ale v metóde load ().
$generator = new $generatorName(); $generator->load($this, $columnName, $paramsForGenerator, $this->tables, $this->columns);
Mali by sme overiť, či užívateľ neposiela scriptu injekciu a nechce vytvoriť inštanciu niečoho, čo nie je entita. Overíme to jednoducho, entity musí implementovať rozhranie IRandomizableItem, to overíme operátorom instanceof, ktorá vráti pravdivostnú hodnotu, či ľavý operand implementuje pravý operand. Ak táto podmienka platiť nebude (inštancia nie je entita) ukončíme prácu skriptu a vypíšeme chybovú hlášku, k tomu sa dobre hodí funkcia die ().
if(!$generator instanceof IRandomizableItem) { die("Chyba: Pokus o PHP injekci"); }
Nakoniec, ak stĺpec chce, aby bola jeho hodnota vkladaná do databázy (napríklad entita Id to nechce) tak ju tam vložíme.
if ($generator->insertToTable) { $this->values[$columnName] = $generator->Randomvalue(); }
Teraz máme riadok kompletný. Vráťme sa k samotnému generátora. Implementujeme vytváranie riadkov kódu. V SampleDataGeneratoru vytvorte metódu Generate () s parametrami table, count, columns. V prvom rade si uložíme stĺpca generované tabuľky.
$this->tableColumns[$table] = $columns;
Teraz cyklom začneme vytvárať jednotlivé riadky. V každej iterácii vytvoríme nový riadok, odovzdáme mu potrebné parametre a zavoláme na neho Generate (). Nakoniec riadok pridáme do tabuľky.
for ($i = 0; $i < $count; $i++) { $random = new DataRow($columns, $this->tables); $random->Generate(); $this->tables[$table][] = $random; }
Tak už vieme aj vygenerovať dáta. To je pre tento diel všetko, ešte je pred nami kus práce. Nabudúce sa pozrieme na špecializovaný databázový wrapper a prvýkrát si vygenerujeme tabuľku plnú mien.