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/migrations
si v databázi vytváří tabulkumigrations
a 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/structures
a v ní SQL soubor s aktuální datumem a časem (např.2018/06/2018-06-02-145118.sql
) - soubor
schema.neon
a 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.neon
a 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.