Schema Generator - generujte svoji databázi z entit
4. června 2018
Schema Generator je nástroj, který dokáže zanalyzovat vaše entity a vytvořit z nich strukturu databáze. V současné chvíli podporuje Lean Mapper a MySQL, ale generátor je navržen univerzálně, takže časem může přibýt i podpora jiných knihoven a databází.
Jak generátor pracuje si ukážeme na Quick Startu z Lean Mapperu. Quick Start a Schema Generator nainstalujeme pomocí Composeru:
composer create-project leanmapper/quickstart cd quickstart composer require inlm/schema-generator
Teď
si vytvoříme skript, pomocí kterého budeme generátor spouštět.
Pojmenujeme si ho třeba schema-generator.php:
require __DIR__ . '/vendor/autoload.php';
$integration = new Inlm\SchemaGenerator\Integrations\LeanMapperIntegration(
$schemaFile = __DIR__ . '/schema.neon',
$migrationsDirectory = __DIR__ . '/migrations/structures',
$entityDirectories = __DIR__ . '/app/Model/Entity',
$options = NULL,
$customTypes = NULL,
$ignoredTables = array('migrations'),
$connection = new LeanMapper\Connection(...),
$mapper = new LeanMapper\DefaultMapper
);
$integration->createMigration();Ve skriptu vytváříme tzv. integraci – jedná se o objekt, který zjednodušuje „standardní způsob použití“. Pokud chceme něco trochu jiného, můžeme generátor vytvořit a nakonfigurovat ručně – není to nic složitého. Pojďme si vysvětlit význam jednotlivých voleb:
$schemaFile– jedná se o soubor, ve kterém si bude generátor ukládat informace o struktuře databáze a nastaveních, aby mohl generovat změnové SQL příkazy. Není to jediný způsob, v případě potřeby můžeme strukturu načítat přímo z databáze (viz např. DibiAdapter)$migrationsDirectory– složka, ve které budou uloženy soubory s SQL migracemi. Generované migrace jsou kompatibilní s nextras/migrations$entityDirectories– jedna či více složek, ve kterých bude generátor hledat entity$options– jedná se o výchozí volby, které budou přiřazeny každé vytvářené tabulce. Jde např. oENGINE,COLLATE, apod.$customTypes– mapování mezi vlastními datovými typy a databází. Pro MySQL je v generátoru předpřipraven např. typmoney, který se přeloží jakoDECIMAL(15, 4). Můžete uvést libovolné vlastní typy včetně value objektů.$ignoredTables– seznam ignorovaných tabulek v databázi, např.nextras/migrationssi v databázi vytváří tabulkumigrationsa ukládá do ní seznam aplikovaných migrací$connection– připojení k databázi$mapper– aplikací používaný mapper – bez něj nelze dodržet správné jmenné konvence
Na
konci voláme metodu createMigration() – ta zajistí spuštění
generátoru a vygenerování SQL příkazů do souboru.
Po spuštění
(php -f schema-generator.php) skript vypíše:
Generating schema Generating diff Generating migrations - created table borrower - created table author - created table book - created table tag - created table borrowing - created table book_tag Saving schema Done.
Pokud vše proběhlo bez chyby, na disku se nám objeví:
- složka
migrations/structuresa v ní SQL soubor s aktuální datumem a časem (např.2018/06/2018-06-02-145118.sql) - soubor
schema.neona v něm aktuální databázová struktura a výchozí nastavení
Oba soubory můžete commitnout do vašeho projektu.
Vygenerovaný SQL soubor ve složce
migrations/structures má následující obsah:
SET foreign_key_checks = 1;
SET time_zone = "SYSTEM";
SET sql_mode = "TRADITIONAL";
CREATE TABLE `borrower` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` TEXT NOT NULL,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
CHARACTER SET=utf8mb4
COLLATE=utf8mb4_czech_ci;
CREATE TABLE `author` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` TEXT NOT NULL,
`web` TEXT NULL,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
CHARACTER SET=utf8mb4
COLLATE=utf8mb4_czech_ci;
CREATE TABLE `book` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`author_id` INT UNSIGNED NOT NULL,
`reviewer_id` INT UNSIGNED NULL,
`pubdate` TEXT NOT NULL,
`name` TEXT NOT NULL,
`description` TEXT NULL,
`website` TEXT NULL,
`available` TINYINT(1) UNSIGNED NOT NULL,
KEY `book_fk_author_id` (`author_id`),
KEY `book_fk_reviewer_id` (`reviewer_id`),
PRIMARY KEY (`id`),
CONSTRAINT `book_fk_author_id` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `book_fk_reviewer_id` FOREIGN KEY (`reviewer_id`) REFERENCES `author` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE=InnoDB
CHARACTER SET=utf8mb4
COLLATE=utf8mb4_czech_ci;
CREATE TABLE `tag` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` TEXT NOT NULL,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
CHARACTER SET=utf8mb4
COLLATE=utf8mb4_czech_ci;
CREATE TABLE `borrowing` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`book_id` INT UNSIGNED NOT NULL,
`borrower_id` INT UNSIGNED NOT NULL,
`date` TEXT NOT NULL,
KEY `borrowing_fk_book_id` (`book_id`),
KEY `borrowing_fk_borrower_id` (`borrower_id`),
PRIMARY KEY (`id`),
CONSTRAINT `borrowing_fk_book_id` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `borrowing_fk_borrower_id` FOREIGN KEY (`borrower_id`) REFERENCES `borrower` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE=InnoDB
CHARACTER SET=utf8mb4
COLLATE=utf8mb4_czech_ci;
CREATE TABLE `book_tag` (
`book_id` INT UNSIGNED NOT NULL,
`tag_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`book_id`, `tag_id`),
KEY `tag_id` (`tag_id`),
CONSTRAINT `book_tag_fk_book_id` FOREIGN KEY (`book_id`) REFERENCES `book` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `book_tag_fk_tag_id` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE=InnoDB
CHARACTER SET=utf8mb4
COLLATE=utf8mb4_czech_ci;Generátor
se snaží vytvářet co nejoptimálnější strukturu. Všimněte si např.
sloupečku book.available – v entitě má sloupeček typ
bool, do databáze ho generátor přeložil jako
TINYINT(1), stejně tak je primárním klíčům typu
INT automaticky doplněn příznak UNSIGNED.
Generátor také automaticky vytváří spojovací tabulky (např.
book_tag) a v nich složený primární klíč.
Datové
typy můžete libovolně doladit přímo v entitě – ukážeme si to na
entitě Tag. Položka $name má v entitě typ
string a generátor ji automaticky přeložil na TEXT.
To je v případě tabulky tag zbytečné plýtvání – bohatě
by nám stačilo 20 znaků a typ VARCHAR. Uvedeme tedy u položky
$name příznak m:schemaType:
/**
* @property int $id
* @property string $name m:schemaType(varchar:20)
*/
class Tag extends \LeanMapper\Entity
{
}A spustíme znovu náš skript.
Generating schema Generating diff Generating migrations - updated column tag.name Saving schema Done.
Ve
složce migrations/structures se nám objevil další SQL
soubor:
SET foreign_key_checks = 1;
SET time_zone = "SYSTEM";
SET sql_mode = "TRADITIONAL";
ALTER TABLE `tag`
MODIFY COLUMN `name` VARCHAR(20) NOT NULL;V příznaku
m:schemaType můžete uvést jak požadovaný databázový datový
typ, tak název vašeho vlastního typu např.
m:schemaType(money).
Ještě si v rychlosti zodpovíme několik „často pokládaných dotazů“.
Musím při vývoji spouštět vygenerované SQL příkazy v databázi ručně?
Nemusíš. Právě z tohoto důvodu
vyžaduje integrace ve svém konstruktoru objekt
LeanMapper\Connection. Stačí tedy jen místo
$integration->createMigration() zavolat
$integration->updateDevelopmentDatabase() a generátor sám
databázi upraví.
Můžu si před vytvořením migrace zobrazit seznam změn?
Ano. Integrace disponuje metodou
showDiff().
Měl bych generátor spouštět na produkčním serveru?
To nedoporučuji, generátor by měl být spouštěn výhradně jen lokálně. Předpokládaný způsob použití je následující:
- vývojář začně pracovat na požadované feature
- v průběhu vývoje upravuje entity a spouští
$integration->updateDevelopmentDatabase(), aby si zaktualizoval svou testovací databázi - po dokončení featury spustí
$integration->createMigration()a vygenerovanou SQL migraci s upraveným souboremschema.neona ostatními změnami commitne a pošle k začlenění
Mám existující projekt, na který chci generátor nasadit. Jak mám postupovat?
Nejprve musíš
vytvořit soubor schema.neon s otiskem aktuální databáze. Od
verze 0.6.2 můžeš použít
$integration->initFromDatabase() – to vytvoří i prvotní
SQL migraci. Alternativně můžeš generátor sestavit ručně. Jak na to lze
najít v příkladech.
Následně
budeš muset upravit položky entit (a případně i výchozí
$options a $customTypes) tak, aby přesně odpovídaly
struktuře databáze. Pro ověření, zda existují nějaké rozdíly můžeš
použít $integration->showDiff().
Existují
kromě příznaku m:schemaType
i nějaké další?
Ano, seznam všech podporovaných příznaků a anotací najdeš v dokumentaci.
Jak
do m:schemaType zapsat value objekt?
V případě,
že máš v anotaci jako typ položky nějaký value objekt, není potřeba
příznak m:schemaType zapisovat, stačí jen value objekt uvést
v poli $customTypes, které se předává do integrace:
use App\Model\HexColor;
$customTypes = array(
HexColor::class => 'char(6)'
);
/**
* @property int $id
* @property HexColor $color
*/
class Product extends LeanMapper\Entity
{
// ...
}V současné době je generátor ve verzi 0.6.x, při používání tedy můžete narazit na chyby a některé věci se do vydání verze 1.0 mohou ještě výrazně změnit.