Skip to content

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 KEY mit 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

bash
composer require jardistools/dbschema

GitHub: jardisTools/dbSchema

Erforderlich: ext-pdo + mindestens ein Datenbanktreiber (ext-pdo_mysql, ext-pdo_pgsql, ext-pdo_sqlite).

Grundlegende Nutzung

Schema lesen

php
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

php
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:

php
[
    '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

php
// Nur bestimmte Spalten, in der angegebenen Reihenfolge
$columns = $reader->columns('users', ['id', 'name', 'email']);

Type-Mapping (DB → PHP)

PHP-TypDatenbank-Typen
intint, integer, tinyint, smallint, mediumint, bigint, serial
stringvarchar, char, text, blob, binary, enum, uuid, bytea
floatdecimal, numeric, float, double, real
boolboolean, bool
datedate
datetimedatetime, timestamp, timestamptz
timetime, timetz
arrayjson, jsonb

Index-Metadaten

php
[
    '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

php
[
    '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:

  1. Header-Kommentar mit Zeitstempel und Tabellenliste
  2. BEGIN TRANSACTION / START TRANSACTION
  3. DROP TABLE (in umgekehrter Abhängigkeitsreihenfolge)
  4. CREATE TABLE (in Abhängigkeitsreihenfolge, referenzierte Tabellen zuerst)
  5. CREATE INDEX (nicht-PK-Indexes)
  6. ALTER TABLE ... ADD FOREIGN KEY (nur MySQL/PostgreSQL)
  7. COMMIT

Dialekt-Unterschiede

FeatureMySQLPostgreSQLSQLite
Identifier-Quoting`backtick`"double-quote""double-quote"
Auto-IncrementAUTO_INCREMENTSERIAL / BIGSERIALAUTOINCREMENT
DROP TABLEDROP TABLE IF EXISTSDROP ... CASCADEDROP TABLE IF EXISTS
Foreign KeysALTER TABLE ... ADDALTER TABLE ... ADDNicht unterstützt (inline)
EngineENGINE=InnoDB

Beispiel: Generiertes MySQL DDL

sql
-- 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

json
{
  "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:

php
$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:

php
// order_items → orders → users
// order_items → products

// Ergebnis: users, products → orders → order_items

Zirkulä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-Export

Verzeichnisstruktur

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.php

API-Referenz

DbSchemaReader

MethodeSignaturBeschreibung
tablestables(): ?arrayAlle Tabellen
columnscolumns(string $container, ?array $fields = null): ?arraySpalten-Metadaten
indexesindexes(string $table): ?arrayIndex-Metadaten
foreignKeysforeignKeys(string $table): ?arrayForeign-Key-Metadaten
fieldTypefieldType(string $dbType): ?stringDB-Typ → PHP-Typ
getDriverNamegetDriverName(): stringTreibername

DbSchemaExporter

MethodeSignaturBeschreibung
toSqltoSql(array $tables): stringDDL-Script
toJsontoJson(array $tables, bool $prettyPrint = false): stringJSON-Export
toArraytoArray(array $tables): arrayArray-Export

Vollständiges Beispiel

Schema-Analyse und Export in allen drei Formaten:

php
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);