IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 10 - Generátor testovacích dat v PHP - Návrh, entity a jádro

V minulé lekci, Dokončení knihovny Image v PHP, jsme si dokončili knihovnu Image.

Vítám vás v tutoriálu o tvorbě generátoru náhodných dat do databáze. Tento doplněk, chcete-li skript, nám vygeneruje různé (náhodné) kombinace dat a ty odešle do databáze. Tutoriál počítá s tím, že rozumíte databázovému ovladači PDO a OOP.

Představme si, že děláme aplikaci, která pracuje s databází – častý model. Aby aplikace fungovala, potřebujete ji otestovat, ale kde data sehnat? Představme si, že tvoříme tabulku uživatelů, potřebujeme na nějakých uživatelích aplikaci otestovat a k tomuto účelu se bude hodit náš skript, který databázovou tabulku (uživatelů) naplní náhodnými daty a to zcela automaticky.

Pro lepší představu si ukažme obrázek, na kterém jsou vidět řádky v tabulce uživatelů. Tyto řádky sama vygenerovala aplikace podle jejího nastavení (na obrázku jich je jen několik, doopravdy jich je v databázi 500):

Vygenerovaní uživatelé - Knihovny pro PHP

Tu je možno vidět i počet záznamů v tabulkách:

Vygenerované tabulky - Knihovny pro PHP

K vygenerování testovacích uživatelů stačí napsat tento kód:

$generator->generate("users", 500, array(
    "id" => "Id",
    "name" => "Name",
    "surname" => "Surname",
    "email" => "Email:name,surname",
    "phone" => "Phone",
    "birthdate" => "Date:1970-01-01,2013-01-01",
    "card_expiration" => "Date:2015-01-01, 2030-01-01")
);

Jednoduché, že?

Návrh

Aby mělo vše větší smysl, budeme skript tvořit objektově. Před tvorbou budeme přemýšlet nad návrhem. Náš skript bude velmi robustní a bude velmi dobře rozšířitelný. Možná jste si zatím pod pojmy robustní a dobře rozšířitelný nic nepředstavili. Tak se zase podívejme na databázovou tabulku. Co budeme generovat? Jména, příjmení, data, čísla, emailové adresy, ... A aby to bylo ještě zábavnější, budeme umět data i propojovat s dalšími tabulkami. Teď již víme, co znamená to robustní, s tím mírně souvisí i snadno rozšířitelný. Proč by si každý programátor, který script použije, nemohl dopsat své vlastní datové entity? Co když náš script nebude umět generovat PSČ a on ho zrovna bude potřebovat?

V lekci budu používat dva pojmy:

  1. Entita - jednotka, která nám bude generovat hodnoty (např. jméno, příjmení, tel. číslo, ...)
  2. Jádro - jádro našeho skriptu, které jednak bude pracovat se samotnou databází, všemi tabulkami a samozřejmě s entitami.

Entita

Entita musí vždy předat nějaké informace jádru. Možná si říkáte, co víc než náhodnou hodnotu budeme potřebovat? Ale uvědomte si, že ID je entita a když v databázové tabulce používáme primární klíče, tak víme, že při vkládání do databáze se tyto klíče neuvádí. Stejně tak, proč by naše jádro mělo složitě zjišťovat, jaký SQL typ má použít, když mu to entity mohou snadno říct.

Pro entitu si napíšeme rozhraní IRandomizableItem. Rozhraní bude mít dvě bezparametrické metody:

  • randomValue() - Vrací náhodnou hodnotu
  • columnSqlType() - Vrací string s SQL typem (např. varchar(55)).

Tímto entita však nebude končit.

Vezměme si, že i propojení sloupců bude vlastně entita (ostatně proč ne, také pracuje se sloupci a hodnotami), proto všem Entitám předáme v metodě load() několik parametrů, se kterými by mohli pracovat. Tím prvním bude rozpracovaná položka, která se odešle do databáze. Druhý parametr budou parametry, které budou předány při definici datového typu (k tomu se dostaneme později). Třetí parametr bude pole všech tabulek a jejich hodnot a čtvrtý, poslední, parametr bude definice sloupců všech tabulek.

Každá Entita musí mít metodu load(), ale kvůli tomu, že ne každá entita potřebuje všechny parametry volání, metodu load() nemůžeme zařadit do rozhraní. Nakonec bude mít každá entita veřejné atributy $insertToDefinition a $insertToTable. Atribut $insertToDefinition říká, jestli se sloupec vkládá do definice tabulky při vytváření a $insertToTable zas zda se má hodnota vkládat do tabulky při vkládání dat. Kvůli těmto atributům parametry předáváme v load() a ne v konstruktoru. Rozhraní bude vypadat následovně:

interface IRandomizableItem {
    function randomValue();
    function columnSqlType();
}

Nyní si pojďme implementovat první entitu. Začněme třeba jménem. Vytvořme si třídu Name, která implementuje rozhraní IRandomizableItem.

Doporučuji používat autoloader.

Třídě nastavíte veřejné atributy $insertToDefinition a $insertToTable na true, jméno se bude vkládat všude. Naše třída bude mít pole jmen a z toho vybere náhodnou položku. Přidejme si statické pole $dataJmena s nějakými hodnotami:

static $dataNames = array("Michal", "David", "Ondřej", "Matěj", "Ludík", "Josef", "Adam", "Tomáš");

Metoda randomValue() tedy vybere jednu náhodnou hodnotu a metoda columnSqlType() vrátí tento string:

varchar(64) COLLATE utf8_czech_ci

Nakonec nezapomeňte implementovat metodu load(), ta v případě jména nebude brát žádný parametr a nebude nic dělat. Celá třída by mohla vypadat následovně:

class Name implements IRandomizableItem {
    static $dataNames= array("Michal", "David", "Ondřej", "Matěj", "Ludvík", "Josef", "Adam", "Tomáš");
    public $insertToDefinition = true;
    public $insertToTable = true;

    function load() {}

    function randomValue() {
        return self::$dataJmena[rand(0,count(self::$dataNames) - 1)];
    }
    function columnSqlType() {
        return "varchar(64) COLLATE utf8_czech_ci";
    }
}

Podobné budou následující entity, od těch se ale teď vzdálíme k jádru.

Jádro

Přidejme si do projektu soubor SampleDataGenerator.php a přidejme si do něj stejnojmennou třídu. Do souboru si nakonec přiřaďte i třídu DatabaseManager, což bude pro nás takový na míru přizpůsobený databázový wrapper a třídu DataRow, což je jakási jednotka pro vytváření řádek, ostatně to bude právě objekt, který bude entitám předán v prvním parametru.

Třída SampleDataGenerator

Náš SampleDataGenerator bude mít dvě veřejné vlastnosti $tables a $columns. Vlastnost $tables bude asociativní pole, které bude mapovat název tabulky na pole DataRow. Vlastnost $tableColumns bude také slovníkové pole v podobném formátu, které bude mapovat název tabulky na asociativní pole [sloupec] => datový typ sloupce. Čili vlastnost $tableColumns bude mít ještě vnořená slovníková pole.

Třída SampleDataGenerator bude mít metody generate() a save(). Metoda save() nepřijímá žádný parametr a pouze odesílá na databázi dotazy. Metoda generate() přijímá tři parametry: název tabulky, počet řádků a sloupce. Název tabulky a počet řádku je myslím docela jasný, parametr sloupce bude opět slovníkové pole ve formátu [název sloupce] => (string) jeho typ s parametry. Zde si ještě všimněte typu s parametry, ten se bude zapisovat ve formátu typ:parametry. Parametry budou vždy ve stringu a nebude pro ně platit žádný separátor. Každá entita si však může "nadefinovat" a implementovat vlastní. No, pokud jste se v této snůšce odborných pojmů trochu ztratili, nevadí, vlastně jsme si řekli, jak se skript bude používat. Ukážeme si ještě kód:

$generator = new SampleDataGenerator();
$generator->generate("users", 500, array(
    "id" => "Id",
    "name" => "Name",
    "surname" => "Surname",
    "email" => "Email:name,surname",
    "phone" => "Phone",
    "birthdate" => "Date:1970-01-01,2013-01-01",
    "card_expiration" => "Date:2015-01-01, 2030-01-01")
);
$generator->save();

Na prvním řádku si vytvoříme generátor, snadné. Poté mu řekneme, ať vytvoří tabulku users a tu naplní 500 uživateli, přičemž má naplnit sloupce id, name, surname, email, phone a data v minulosti a budoucnosti. Všimněte si emailu, tam v parametrech předávám názvy sloupců, ze kterých poté bude email čerpat. Dále jsou zajímavá data, těm specifikujeme rozsah od kdy do kdy. Výsledné náhodné datum bude v tomto rozmezí. Nakonec to pošleme do databáze. Nejdříve ale vytvoříme třídu reprezentující řádek - DataRow.

Třída DataRow

Třída DataRow bude potřebovat sloupce tabulky, kam patří a všechny tabulky. Tyto hodnoty mu předáme v konstruktoru. Nakonec budeme potřebovat atribut $values což bude slovníkové pole ve formátu [název sloupce] => [hodnota sloupce pro položku]:

public $tables;
public $columns;
public $values = array();

function __construct($columns, $tables) {
    $this->columns = $columns;
    $this->tables = $tables;
}

Třída DataRow bude mít také metodu generate(), která nebude přijímat žádný parametr a vygeneruje hodnoty řádku. Metoda projde cyklem foreach sloupce ($this->columns), z nich v tomto cyklu dostane jméno a jeho typ:

foreach ($this->columns as $columnName => $columnType)

Typ sloupce, jak již jsme si řekli, obsahuje název a parametry. Název je název naší entity a parametry jsou volitelné parametry. Tyto dvě části jsou oddělené dvojtečkou. Hodnoty dosadíme do proměnných $generatorName a $paramsForGenerator, přičemž proměnná $paramsForGenerator bude mít výchozí hodnotu prázdný string:

$generatorParts = explode(":", $columnType);
$generatorName = $generatorParts[0];

$paramsForGenerator = "";
if (isset($generatorParts[1])) {
    $paramsForGenerator = $generatorParts[1];
}

Asi vás napadne, jak ale vytvořit instanci entity ze stringu? PHP má na toto krásnou syntaxi, jednoduše mu řekneme new $nazevPromenne(parametry) a on vytvoří novou instanci třídy, jejíž název obsahuje proměnná $nazevPromenne, přičemž můžeme samozřejmě doplnit parametry. Jak již víme, parametry se entitám nepředávají v konstruktoru ale v metodě load():

$generator = new $generatorName();
$generator->load($this, $columnName, $paramsForGenerator, $this->tables, $this->columns);

Měli bychom ověřit, jestli uživatel neposílá skriptu injekci a nechce vytvořit instanci něčeho, co není entita. Ověříme to jednoduše, entity musí implementovat rozhraní IRandomizableItem, to ověříme operátorem instanceof, která vrátí pravdivostní hodnotu, zdali levý operand implementuje pravý operand. Pokud tato podmínka platit nebude (instance není entita) ukončíme práci skriptu a vypíšeme chybovou hlášku, k tomu se dobře hodí funkce die():

if(!$generator instanceof IRandomizableItem) {
    die("Chyba: Pokus o PHP injekci");
}

Nakonec, pokud sloupec chce, aby byla jeho hodnota vkládaná do databáze (třeba entita ID to nechce) tak ji tam vložíme:

if ($generator->insertToTable) {
    $this->values[$columnName] = $generator->Randomvalue();
}

Nyní máme řádek kompletní. Vraťme se k samotnému generátoru. Implementujeme vytváření řádků kódu. V třídě SampleDataGenerator vytvořte metodu generate() s parametry table, count a columns. V prvé řadě si uložíme sloupce generované tabulky:

$this->tableColumns[$table] = $columns;

Nyní cyklem začneme vytvářet jednotlivé řádky. V každé iteraci vytvoříme nový řádek, předáme mu potřebné parametry a zavoláme na něj metodu generate(). Nakonec řádek přidáme do tabulky:

for ($i = 0; $i < $count; $i++) {
    $random = new DataRow($columns, $this->tables);
    $random->Generate();
    $this->tables[$table][] = $random;
}

Tak už umíme i vygenerovat data. To je pro tento díl vše, ještě je před námi kus práce. Zdrojové kódy budou ke stažení, až si vše doděláme :)

Příště, v lekci Generátor testovacích dat v PHP - Tvorba wrapperu, se podíváme na specializovaný databázový wrapper a poprvé si vygenerujeme tabulku plnou jmen :)


 

Předchozí článek
Dokončení knihovny Image v PHP
Všechny články v sekci
Knihovny pro PHP
Přeskočit článek
(nedoporučujeme)
Generátor testovacích dat v PHP - Tvorba wrapperu
Článek pro vás napsal Michal Žůrek - misaz
Avatar
Uživatelské hodnocení:
8 hlasů
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.
Aktivity