# Database Patterns Umfassende Dokumentation der Database-Patterns im Custom PHP Framework. ## Migration System: Safe Rollback Architecture Das Framework verwendet ein intelligentes Migration-System, das zwischen sicheren und unsicheren Rollbacks unterscheidet. ### Core Concepts **Forward-Only by Default**: Alle Migrations sind standardmäßig forward-only (nur `up()` Methode erforderlich). **Optional Safe Rollback**: Migrations, die SICHER rückgängig gemacht werden können (ohne Datenverlust), implementieren zusätzlich das `SafelyReversible` Interface. ### Migration Interface ```php /** * Base migration interface - Forward-only by default */ interface Migration { public function up(ConnectionInterface $connection): void; public function getVersion(): MigrationVersion; public function getDescription(): string; } ``` ### SafelyReversible Interface ```php /** * Optional interface for migrations that support safe rollback * * ONLY implement this if rollback is safe (no data loss)! */ interface SafelyReversible { public function down(ConnectionInterface $connection): void; } ``` ### Safe vs Unsafe Migrations **✅ Safe for Rollback** (implement SafelyReversible): - Creating new tables (can be dropped) - Adding nullable columns (can be removed) - Creating/dropping indexes (no data affected) - Renaming columns (data preserved) - Adding/removing foreign keys (constraints only) **❌ Unsafe for Rollback** (only Migration): - Dropping columns with data - Transforming data formats - Changing column types (data loss risk) - Merging/splitting tables - Deleting data ### Example: Safe Rollback ```php use App\Framework\Database\Migration\{Migration, SafelyReversible}; /** * Safe: New table can be dropped without data loss */ final readonly class CreateSessionsTable implements Migration, SafelyReversible { public function up(ConnectionInterface $connection): void { $schema = new Schema($connection); $schema->create('sessions', function ($table) { $table->string('id', 255)->primary(); $table->text('data'); }); $schema->execute(); } public function down(ConnectionInterface $connection): void { $schema = new Schema($connection); $schema->dropIfExists('sessions'); $schema->execute(); } } ``` ### Example: Unsafe Migration ```php /** * UNSAFE: Dropping column with data - NOT reversible */ final readonly class RemoveLegacyIdColumn implements Migration { public function up(ConnectionInterface $connection): void { $schema = new Schema($connection); $schema->table('users', function ($table) { $table->dropColumn('legacy_id'); // Data is LOST! }); $schema->execute(); } // NO down() method - data cannot be recovered } ``` ### Rollback Safety Check Der `MigrationRunner` prüft automatisch, ob eine Migration sicher rollbar ist: ```bash $ php console.php db:rollback 1 🔄 Rolling back migrations... ⚠️ Safety Check: Only migrations implementing SafelyReversible will be rolled back. ❌ Rollback failed: Migration 2024_12_19_150000 does not support safe rollback ℹ️ This migration cannot be safely rolled back. Reason: Data loss would occur during rollback. 💡 Recommendation: Create a new forward migration to undo the changes instead: php console.php make:migration FixYourChanges 📖 See docs/claude/examples/migrations/SafeVsUnsafeMigrations.md for guidelines. ``` ### Fix-Forward Strategy Statt unsichere Rollbacks: Neue Forward-Migration erstellen. ```php // Original migration (dropped column) final readonly class RemoveDeprecatedField implements Migration { public function up(ConnectionInterface $connection): void { $schema = new Schema($connection); $schema->table('users', function ($table) { $table->dropColumn('deprecated_field'); }); $schema->execute(); } } // Fix-Forward migration (restore column) final readonly class RestoreDeprecatedField implements Migration, SafelyReversible { public function up(ConnectionInterface $connection): void { $schema = new Schema($connection); $schema->table('users', function ($table) { $table->string('deprecated_field')->nullable(); }); $schema->execute(); } public function down(ConnectionInterface $connection): void { // Safe: Column is empty after restoration $schema = new Schema($connection); $schema->table('users', function ($table) { $table->dropColumn('deprecated_field'); }); $schema->execute(); } } ``` ### Best Practices 1. **Default to Forward-Only**: Nur `Migration` implementieren, außer du bist SICHER, dass Rollback safe ist 2. **Document Why Safe**: Kommentiere, warum eine Migration `SafelyReversible` ist 3. **Test Rollback**: Teste `up()` → `down()` → `up()` Cycle in Development 4. **Production: Forward-Only**: Auch "safe" Rollbacks sollten in Production vermieden werden 5. **See Examples**: Vollständige Guidelines in `docs/claude/examples/migrations/SafeVsUnsafeMigrations.md` --- ## Database Value Objects Das Framework verwendet Value Objects für alle Database-Identifier, um Type Safety und SQL-Injection-Prevention zu gewährleisten. ### Verfügbare Database Value Objects **Core Database VOs** (`src/Framework/Database/ValueObjects/`): - **TableName**: Validierte Tabellennamen mit optionalem Schema-Prefix - **ColumnName**: Validierte Spaltennamen - **IndexName**: Validierte Index-Namen - **ConstraintName**: Validierte Constraint-Namen (Foreign Keys, Check Constraints) - **DatabaseName**: Validierte Datenbank-Namen - **SchemaName**: Validierte Schema-Namen (PostgreSQL) ### Value Object Patterns Alle Database VOs folgen einheitlichen Patterns: ```php // Immutable readonly classes final readonly class TableName { public function __construct( public string $value, public ?SchemaName $schema = null ) { $this->validate(); } // Factory Method Pattern public static function fromString(string $value): self { return new self($value); } // __toString() für String-Interpolation public function __toString(): string { return $this->schema ? "{$this->schema}.{$this->value}" : $this->value; } // Equality Comparison public function equals(self $other): bool { return $this->value === $other->value && $this->schema?->equals($other->schema ?? null); } } ``` ### Schema Builder Integration Der Schema Builder unterstützt sowohl VOs als auch Strings (Union Types) für Backwards Compatibility: ```php use App\Framework\Database\Schema\Blueprint; use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName}; // ✅ Modern: Value Objects (Type Safe) $schema->create(TableName::fromString('users'), function (Blueprint $table) { $table->string(ColumnName::fromString('email'), 255); $table->unique(ColumnName::fromString('email'), IndexName::fromString('uk_users_email')); }); // ✅ Legacy: Strings (Backwards Compatible) $schema->create('users', function (Blueprint $table) { $table->string('email', 255); $table->unique('email', 'uk_users_email'); }); // ✅ Mixed: Kombiniert möglich $schema->create('users', function (Blueprint $table) { $table->string('email', 255); // String $table->unique( ColumnName::fromString('email'), // VO 'uk_users_email' // String ); }); ``` ### Variadic Parameters Command-Methoden nutzen variadic parameters statt Arrays für natürlichere API: ```php // ✅ Variadic Parameters - Natural $table->dropColumn('email', 'phone', 'address'); // ❌ Array Parameters - Clunky $table->dropColumn(['email', 'phone', 'address']); // ✅ Auch mit VOs $table->dropColumn( ColumnName::fromString('email'), ColumnName::fromString('phone') ); ``` ### TableName mit Schema-Support PostgreSQL unterstützt Schema-Prefixe: ```php use App\Framework\Database\ValueObjects\{TableName, SchemaName}; // Simple table name $table = TableName::fromString('users'); // Output: "users" // With schema prefix $table = new TableName( value: 'users', schema: SchemaName::fromString('public') ); // Output: "public.users" // Schema extraction if ($table->hasSchema()) { echo $table->schema->value; // "public" } ``` ### Validation Rules Alle Database VOs validieren ihre Eingaben: **TableName Validation**: - Keine leeren Strings - Alphanumerisch + Underscore - Beginnt nicht mit Ziffer - Max. 63 Zeichen (PostgreSQL Limit) - Reservierte Keywords verboten (siehe `RESERVED_KEYWORDS`) **ColumnName Validation**: - Keine leeren Strings - Alphanumerisch + Underscore - Beginnt nicht mit Ziffer - Max. 63 Zeichen - Reservierte Keywords verboten **IndexName/ConstraintName**: - Gleiche Rules wie ColumnName - Prefix-Conventions: `idx_`, `uk_`, `fk_`, `ck_` empfohlen **Example Validation**: ```php // ✅ Valid TableName::fromString('users'); TableName::fromString('user_profiles'); // ❌ Invalid - Exception geworfen TableName::fromString(''); // Leer TableName::fromString('123users'); // Beginnt mit Ziffer TableName::fromString('user-table'); // Ungültiges Zeichen TableName::fromString('select'); // Reserviertes Keyword ``` ## Migration Best Practices ### Migration mit Database VOs Neue Migrationen können VOs verwenden für zusätzliche Type Safety: ```php use App\Framework\Database\Migration\Migration; use App\Framework\Database\Schema\{Blueprint, Schema}; use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName, ConstraintName}; use App\Framework\Database\Schema\ForeignKeyAction; final class CreateUserProfilesTable implements Migration { public function up(Schema $schema): void { $schema->create(TableName::fromString('user_profiles'), function (Blueprint $table) { // Primary Key $table->string(ColumnName::fromString('ulid'), 26) ->primary(); // Foreign Key $table->string(ColumnName::fromString('user_id'), 26); // Relationship $table->foreign(ColumnName::fromString('user_id')) ->references(ColumnName::fromString('ulid')) ->on(TableName::fromString('users')) ->onDelete(ForeignKeyAction::CASCADE); // Index $table->index( ColumnName::fromString('user_id'), IndexName::fromString('idx_user_profiles_user_id') ); // Composite Index mit variadic parameters $table->index( ColumnName::fromString('user_id'), ColumnName::fromString('created_at'), IndexName::fromString('idx_user_profiles_lookup') ); // Timestamps $table->timestamps(); }); } public function down(Schema $schema): void { $schema->dropIfExists(TableName::fromString('user_profiles')); } } ``` ### Migration Naming Conventions **Index Names**: ```php // Pattern: idx_{table}_{columns} IndexName::fromString('idx_users_email') IndexName::fromString('idx_users_email_active') // Unique constraints: uk_{table}_{columns} IndexName::fromString('uk_users_email') // Composite IndexName::fromString('idx_orders_user_created') ``` **Foreign Key Constraints**: ```php // Pattern: fk_{table}_{referenced_table} ConstraintName::fromString('fk_user_profiles_users') ConstraintName::fromString('fk_orders_users') ``` **Check Constraints**: ```php // Pattern: ck_{table}_{column}_{condition} ConstraintName::fromString('ck_users_age_positive') ConstraintName::fromString('ck_orders_total_min') ``` ### Backwards Compatibility Bestehende Migrationen funktionieren ohne Änderungen: ```php // ✅ Legacy-Code bleibt voll funktionsfähig $schema->create('users', function (Blueprint $table) { $table->string('email'); $table->unique('email', 'uk_users_email'); }); // Union Types ermöglichen schrittweise Migration // string|TableName, string|ColumnName, etc. ``` ### Drop Operations Variadic parameters machen Drop-Operationen natürlich: ```php $schema->table('users', function (Blueprint $table) { // Drop multiple columns $table->dropColumn('old_field', 'deprecated_field', 'unused_field'); // Drop foreign key by constraint name $table->dropForeign('fk_users_company'); // Drop foreign key by column (finds constraint automatically) $table->dropForeign('company_id'); // Drop index by name $table->dropIndex('idx_users_email'); // Drop index by columns (finds index automatically) $table->dropIndex('email', 'active'); }); ``` ## EntityManager Usage TODO: Document EntityManager and UnitOfWork pattern ## Repository Pattern TODO: Document repository implementation and usage ## Query Optimization TODO: Document N+1 prevention and batch loading ## Connection Pooling TODO: Document connection pool configuration ## Transaction Management TODO: Document transaction patterns and best practices ## Database Testing ### Testing mit Database VOs ```php use App\Framework\Database\ValueObjects\{TableName, ColumnName}; it('creates table with value objects', function () { $tableName = TableName::fromString('test_users'); $this->schema->create($tableName, function (Blueprint $table) { $table->string(ColumnName::fromString('email')); }); expect($this->schema->hasTable($tableName))->toBeTrue(); }); it('validates table name format', function () { TableName::fromString('123invalid'); // Should throw })->throws(\InvalidArgumentException::class); ``` ### Test Cleanup ```php afterEach(function () { // Cleanup mit VOs $this->schema->dropIfExists(TableName::fromString('test_users')); $this->schema->dropIfExists(TableName::fromString('test_profiles')); }); ``` ## Performance Considerations ### Value Object Overhead Database VOs haben minimalen Performance-Overhead: - **Creation**: ~0.01ms pro VO (Validation einmalig) - **String Conversion**: ~0.001ms via `__toString()` - **Memory**: ~200 bytes pro VO Instance - **Recommendation**: VOs sind für alle Database Operations akzeptabel ### Caching Strategies ```php // VO Instances können gecached werden final class TableNameCache { private static array $cache = []; public static function get(string $name): TableName { return self::$cache[$name] ??= TableName::fromString($name); } } // Usage $users = TableNameCache::get('users'); $profiles = TableNameCache::get('user_profiles'); ``` ## Migration Pattern Recommendations ### Simple Tables (String-basiert OK) Für einfache Tabellen ohne komplexe Constraints: ```php $schema->create('simple_logs', function (Blueprint $table) { $table->id(); $table->string('message'); $table->timestamps(); }); ``` ### Complex Tables (VOs empfohlen) Für Tabellen mit Relationships, Constraints, Composite Indexes: ```php use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName, ConstraintName}; $schema->create(TableName::fromString('complex_orders'), function (Blueprint $table) { // Type Safety für alle Identifier $table->string(ColumnName::fromString('ulid'), 26)->primary(); $table->string(ColumnName::fromString('user_id'), 26); $table->foreign(ColumnName::fromString('user_id')) ->references(ColumnName::fromString('ulid')) ->on(TableName::fromString('users')) ->onDelete(ForeignKeyAction::CASCADE); // Composite indexes mit explicit naming $table->index( ColumnName::fromString('user_id'), ColumnName::fromString('status'), ColumnName::fromString('created_at'), IndexName::fromString('idx_orders_user_status_created') ); }); ``` ## SQL Injection Prevention Database VOs bieten eingebauten Schutz: ```php // ✅ VOs validieren Input - SQL Injection unmöglich $tableName = TableName::fromString($_GET['table']); // Wirft Exception bei Injection-Versuch // ❌ Raw strings sind gefährlich $query = "SELECT * FROM {$_GET['table']}"; // SQL Injection möglich! // ✅ VOs in Queries nutzen $query = "SELECT * FROM {$tableName}"; // Validated and safe ``` ## Framework Compliance Database VOs folgen allen Framework-Prinzipien: - ✅ **Readonly Classes**: Alle VOs sind `final readonly` - ✅ **Immutability**: Keine State-Mutation nach Construction - ✅ **No Inheritance**: `final` classes, composition over inheritance - ✅ **Value Objects**: Keine Primitive Obsession - ✅ **Type Safety**: Union Types für Backwards Compatibility - ✅ **Framework Integration**: `__toString()` für seamless integration