Database Schema
Live-Schema-Analyse und DDL-Export für MySQL, PostgreSQL und SQLite.
Einführung
Schema-Dokumentation in Datenbankprojekten ist oft veraltet: die Tabelle wurde geändert, aber das Wiki nicht. Und wer ein Schema von MySQL nach PostgreSQL portieren will, schreibt DDL von Hand um. Schema-Analyse sollte nicht auf Dokumentation angewiesen sein, sondern direkt aus der laufenden Datenbank kommen.
jardistools/dbschema liest live Tabellenstrukturen aus einer PDO-Verbindung und exportiert sie in drei Formaten:
- SQL DDL — dialektkorrektes
CREATE TABLE,CREATE INDEX,ALTER TABLE ... ADD FOREIGN KEYmit automatischer Abhängigkeitssortierung - JSON — strukturierte Ausgabe für Tooling, Diffing und die Jardis Builder-Pipeline
- PHP Array — für programmatische Verarbeitung
Drei Datenbanken, eine API: MySQL/MariaDB, PostgreSQL und SQLite. Automatische Treibererkennung via PDO::ATTR_DRIVER_NAME.
Installation
composer require jardistools/dbschemaGitHub: jardisTools/dbSchema
Erforderlich: ext-pdo + mindestens ein Datenbanktreiber (ext-pdo_mysql, ext-pdo_pgsql, ext-pdo_sqlite).
Grundlegende Nutzung
Schema lesen
use JardisTools\DbSchema\DbSchemaReader;
$pdo = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$reader = new DbSchemaReader($pdo);
// Alle Tabellen
$tables = $reader->tables();
// [['name' => 'users', 'type' => 'BASE TABLE'], ['name' => 'orders', ...]]
// Spalten einer Tabelle
$columns = $reader->columns('users');
// [['name' => 'id', 'type' => 'int', 'primary' => true, 'auto_increment' => true, ...], ...]
// Indexes
$indexes = $reader->indexes('orders');
// Foreign Keys
$foreignKeys = $reader->foreignKeys('orders');
// DB-Typ → PHP-Typ
$reader->fieldType('varchar'); // 'string'
$reader->fieldType('decimal'); // 'float'
$reader->fieldType('timestamp'); // 'datetime'
$reader->fieldType('jsonb'); // 'array'Schema exportieren
use JardisTools\DbSchema\DbSchemaExporter;
$exporter = new DbSchemaExporter($reader);
// SQL DDL
$sql = $exporter->toSql(['users', 'orders']);
// Vollständiges Script mit DROP, CREATE TABLE, INDEX, FK
// JSON (Pretty-Print)
$json = $exporter->toJson(['users', 'orders'], prettyPrint: true);
// PHP Array
$array = $exporter->toArray(['users', 'orders']);Schema-Daten
Spalten-Metadaten
Jede Spalte wird in ein normalisiertes Format gebracht, konsistent über alle drei Datenbanken:
[
'name' => 'email',
'type' => 'varchar', // Normalisiert, lowercase
'length' => 255, // Zeichenlänge (null für nicht-String-Typen)
'precision' => null, // Numerische Präzision
'scale' => null, // Numerische Skalierung
'nullable' => false, // NULL erlaubt?
'default' => null, // Default-Wert als String
'primary' => false, // Teil des Primary Key?
'auto_increment' => false, // Auto-Increment?
'enumValues' => null, // ['draft', 'published'] für ENUM-Spalten
]Gefilterte Spaltenauswahl
// Nur bestimmte Spalten, in der angegebenen Reihenfolge
$columns = $reader->columns('users', ['id', 'name', 'email']);Type-Mapping (DB → PHP)
| PHP-Typ | Datenbank-Typen |
|---|---|
int | int, integer, tinyint, smallint, mediumint, bigint, serial |
string | varchar, char, text, blob, binary, enum, uuid, bytea |
float | decimal, numeric, float, double, real |
bool | boolean, bool |
date | date |
datetime | datetime, timestamp, timestamptz |
time | time, timetz |
array | json, jsonb |
Index-Metadaten
[
'name' => 'idx_user_id',
'column_name' => 'user_id',
'is_unique' => false,
'type' => 'BTREE',
'index_type' => 'index', // 'primary' | 'unique' | 'index'
'sequence' => 1, // Position in Multi-Column-Index
]Foreign-Key-Metadaten
[
'container' => 'orders',
'constraintName' => 'fk_orders_user_id',
'constraintCol' => 'user_id',
'refContainer' => 'users',
'refColumn' => 'id',
'onUpdate' => 'CASCADE',
'onDelete' => 'CASCADE',
'sequence' => 1,
]DDL-Export
Script-Aufbau
Das generierte SQL-Script folgt einer festen Struktur:
- Header-Kommentar mit Zeitstempel und Tabellenliste
BEGIN TRANSACTION/START TRANSACTIONDROP TABLE(in umgekehrter Abhängigkeitsreihenfolge)CREATE TABLE(in Abhängigkeitsreihenfolge, referenzierte Tabellen zuerst)CREATE INDEX(nicht-PK-Indexes)ALTER TABLE ... ADD FOREIGN KEY(nur MySQL/PostgreSQL)COMMIT
Dialekt-Unterschiede
| Feature | MySQL | PostgreSQL | SQLite |
|---|---|---|---|
| Identifier-Quoting | `backtick` | "double-quote" | "double-quote" |
| Auto-Increment | AUTO_INCREMENT | SERIAL / BIGSERIAL | AUTOINCREMENT |
| DROP TABLE | DROP TABLE IF EXISTS | DROP ... CASCADE | DROP TABLE IF EXISTS |
| Foreign Keys | ALTER TABLE ... ADD | ALTER TABLE ... ADD | Nicht unterstützt (inline) |
| Engine | ENGINE=InnoDB | — | — |
Beispiel: Generiertes MySQL DDL
-- Database Schema Export
-- Generated: 2025-04-05 10:30:00
-- Tables (2): users, orders
START TRANSACTION;
-- Drop existing tables
DROP TABLE IF EXISTS `orders`;
DROP TABLE IF EXISTS `users`;
-- Create tables
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`is_active` TINYINT(1) DEFAULT 1,
`balance` DECIMAL(10,2) DEFAULT 0.00,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `orders` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL,
`total` DECIMAL(10,2) NOT NULL,
`status` VARCHAR(20) DEFAULT 'pending',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Create indexes
CREATE INDEX `idx_user_id` ON `orders` (`user_id`);
CREATE INDEX `idx_status` ON `orders` (`status`);
-- Add foreign key constraints
ALTER TABLE `orders` ADD CONSTRAINT `fk_orders_user_id`
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
ON UPDATE CASCADE ON DELETE CASCADE;
COMMIT;JSON/Array-Export
Ausgabe-Format
{
"version": "1.0",
"generated": "2025-04-05 10:30:00",
"tables": {
"users": {
"columns": [
{
"name": "id",
"type": "int",
"nullable": false,
"primary": true,
"auto_increment": true,
"phpType": "int"
}
],
"indexes": [...],
"foreignKeys": [...]
}
}
}Der Array-Export hat dieselbe Struktur, ideal als Input für die Jardis Builder-Pipeline:
$schema = $exporter->toArray(['users', 'orders']);
// Direkt an den Builder übergeben
$builderConfig = DatabaseSchema::fromArray($schema);Abhängigkeitssortierung
Foreign Keys definieren Abhängigkeiten zwischen Tabellen. Der DependencyResolver sortiert Tabellen topologisch (Kahn's Algorithmus), sodass referenzierte Tabellen immer vor referenzierenden erstellt werden:
// order_items → orders → users
// order_items → products
// Ergebnis: users, products → orders → order_itemsZirkuläre Abhängigkeiten werfen eine RuntimeException.
Architektur
Zwei Orchestratoren mit treiberspezifischen Handlern:
DbSchemaReader ← Orchestrator (Treiber-Erkennung)
├── MySqlReader ← information_schema Queries
├── PostgresReader ← information_schema + pg_catalog
└── SqLiteReader ← PRAGMA Statements
DbSchemaExporter ← Orchestrator (Format-Routing)
├── SqlDdlExporter ← DDL-Script-Generierung
│ └── DependencyResolver ← Topologische Sortierung
├── MySqlDialect ← MySQL DDL-Syntax
├── PostgresDialect ← PostgreSQL DDL-Syntax
├── SqLiteDialect ← SQLite DDL-Syntax
└── JsonExporter ← JSON/Array-ExportVerzeichnisstruktur
src/
├── DbSchemaReader.php ← Reader-Orchestrator
├── DbSchemaExporter.php ← Exporter-Orchestrator
├── Reader/
│ ├── MySqlReader.php
│ ├── PostgresReader.php
│ └── SqLiteReader.php
└── Exporter/
├── Ddl/
│ ├── DdlDialectInterface.php
│ ├── DependencyResolver.php
│ └── SqlDdlExporter.php
├── Dialect/
│ ├── MySqlDialect.php
│ ├── PostgresDialect.php
│ └── SqLiteDialect.php
└── Json/
└── JsonExporter.phpAPI-Referenz
DbSchemaReader
| Methode | Signatur | Beschreibung |
|---|---|---|
tables | tables(): ?array | Alle Tabellen |
columns | columns(string $container, ?array $fields = null): ?array | Spalten-Metadaten |
indexes | indexes(string $table): ?array | Index-Metadaten |
foreignKeys | foreignKeys(string $table): ?array | Foreign-Key-Metadaten |
fieldType | fieldType(string $dbType): ?string | DB-Typ → PHP-Typ |
getDriverName | getDriverName(): string | Treibername |
DbSchemaExporter
| Methode | Signatur | Beschreibung |
|---|---|---|
toSql | toSql(array $tables): string | DDL-Script |
toJson | toJson(array $tables, bool $prettyPrint = false): string | JSON-Export |
toArray | toArray(array $tables): array | Array-Export |
Vollständiges Beispiel
Schema-Analyse und Export in allen drei Formaten:
use JardisTools\DbSchema\DbSchemaReader;
use JardisTools\DbSchema\DbSchemaExporter;
$pdo = new PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$reader = new DbSchemaReader($pdo);
$exporter = new DbSchemaExporter($reader);
// Alle Tabellen der Datenbank auflisten
$tables = $reader->tables();
$tableNames = array_column($tables, 'name');
// ['users', 'orders', 'order_items', 'products']
// Schema einer einzelnen Tabelle inspizieren
$columns = $reader->columns('orders');
foreach ($columns as $col) {
echo sprintf(
"%s: %s%s%s\n",
$col['name'],
$col['type'],
$col['nullable'] ? ' NULL' : ' NOT NULL',
$col['primary'] ? ' PK' : '',
);
}
// Foreign Keys analysieren
$fks = $reader->foreignKeys('orders');
foreach ($fks as $fk) {
echo sprintf(
"%s.%s → %s.%s (%s/%s)\n",
$fk['container'], $fk['constraintCol'],
$fk['refContainer'], $fk['refColumn'],
$fk['onUpdate'], $fk['onDelete'],
);
}
// Vollständiges DDL-Script exportieren
$ddl = $exporter->toSql($tableNames);
file_put_contents('schema.sql', $ddl);
// JSON für die Builder-Pipeline
$json = $exporter->toJson($tableNames, prettyPrint: true);
file_put_contents('schema.json', $json);
// Array für programmatische Verarbeitung
$schema = $exporter->toArray($tableNames);
$builderConfig = DatabaseSchema::fromArray($schema);