# 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 ### Overview Der EntityManager ist das zentrale Interface für alle Datenbank-Operationen. Er kombiniert mehrere Patterns: - **Unit of Work**: Change Tracking und Transaction Management - **Identity Map**: Vermeidung von Duplikaten - **Lazy Loading**: Performance-Optimierung durch verzögertes Laden - **Batch Loading**: N+1 Query Prevention ### Core Features **Service Classes** (interne Organisation): - `EntityFinder`: Entity-Suche und Lazy Loading - `EntityPersister`: Insert, Update, Delete Operationen - `EntityQueryManager`: QueryBuilder Integration - `EntityUtilities`: Hilfsmethoden und Profiling ### Basic Operations #### Finding Entities ```php use App\Framework\Database\EntityManager; final readonly class UserService { public function __construct( private EntityManager $entityManager ) {} public function getUserById(UserId $id): ?User { // Lazy Loading (Standard) return $this->entityManager->find(User::class, $id->value); } public function getUserByIdEager(UserId $id): ?User { // Eager Loading - lädt Relations sofort return $this->entityManager->findEager(User::class, $id->value); } public function getAllUsers(): array { return $this->entityManager->findAll(User::class); } public function findUserByEmail(Email $email): ?User { return $this->entityManager->findOneBy( User::class, ['email' => $email->value] ); } } ``` #### Saving Entities ```php public function createUser(CreateUserCommand $command): User { $user = User::create( id: $this->entityManager->generateId(), email: $command->email, name: $command->name ); // save() erkennt automatisch INSERT vs UPDATE return $this->entityManager->save($user); } public function updateUser(User $user, UpdateUserCommand $command): User { // Explizites UPDATE $user->updateProfile($command->name); return $this->entityManager->update($user); } ``` #### Batch Operations ```php public function createMultipleUsers(array $commands): array { $users = []; foreach ($commands as $command) { $users[] = User::create( id: $this->entityManager->generateId(), email: $command->email, name: $command->name ); } // Batch-Insert für bessere Performance return $this->entityManager->saveAll(...$users); } ``` #### Deleting Entities ```php public function deleteUser(User $user): void { $this->entityManager->delete($user); } ``` ### N+1 Query Prevention **Problem**: Lazy Loading führt zu N+1 Queries ```php // ❌ N+1 Problem $users = $this->entityManager->findAll(User::class); foreach ($users as $user) { // Jede Iteration führt eine separate Query aus (N+1) echo $user->getProfile()->bio; } ``` **Solution**: Batch Loading mit Relations ```php // ✅ Single Query mit Batch Loading $users = $this->entityManager->findWithRelations( User::class, criteria: [], relations: ['profile', 'posts'] // Lädt Relations in Batches ); foreach ($users as $user) { // Kein zusätzlicher Query - Profile bereits geladen echo $user->getProfile()->bio; } ``` ### Lazy Loading vs Eager Loading **Lazy Loading** (Standard): - Relations werden bei Zugriff geladen - Performance-Vorteil bei ungenutzten Relations - Potentielles N+1 Problem ```php $user = $this->entityManager->find(User::class, $id); // Profile wird erst bei Zugriff geladen: $bio = $user->getProfile()->bio; // Separate Query ``` **Eager Loading**: - Relations werden sofort geladen - Kein N+1 Problem - Overhead bei ungenutzten Relations ```php $user = $this->entityManager->findEager(User::class, $id); // Profile ist bereits geladen - kein zusätzlicher Query $bio = $user->getProfile()->bio; ``` ### Identity Map Der EntityManager nutzt eine Identity Map zur Vermeidung von Duplikaten: ```php // Beide Aufrufe liefern die GLEICHE Instanz $user1 = $this->entityManager->find(User::class, '123'); $user2 = $this->entityManager->find(User::class, '123'); var_dump($user1 === $user2); // true ``` **Vorteile**: - Keine Duplikate im Speicher - Konsistente Object Identity - Automatisches Change Tracking **Utility Methods**: ```php // Detach Entity (aus Identity Map entfernen) $this->entityManager->detach($user); // Clear Identity Map (z.B. für Batch Processing) $this->entityManager->clear(); // Identity Map Statistics $stats = $this->entityManager->getIdentityMapStats(); // Returns: ['total_entities' => 150, 'entities_by_class' => [...]] ``` ### Reference Loading **getReference()** - Lade Entity-Referenz ohne Existenz-Check: ```php // Erstellt Proxy ohne Database Query $user = $this->entityManager->getReference(User::class, $userId); // Query wird erst bei Zugriff ausgeführt $name = $user->getName(); // Jetzt wird geladen ``` **Use Cases**: - Foreign Key Relationships - Performance-kritische Pfade - Wenn Existenz bereits bekannt ist ### Profiling und Debugging ```php // Profiling aktivieren $this->entityManager->setProfilingEnabled(true); // Operationen ausführen $users = $this->entityManager->findAll(User::class); // Profiling-Statistiken abrufen $stats = $this->entityManager->getProfilingStatistics(); // Profiling-Summary $summary = $this->entityManager->getProfilingSummary(); // Returns: ProfileSummary mit total_queries, total_time, etc. // Profiling-Daten löschen $this->entityManager->clearProfilingData(); ``` ### Criteria API (Type-Safe Queries) **Modern**: Verwendung von Criteria statt Arrays ```php use App\Framework\Database\Criteria\DetachedCriteria; // Type-safe Criteria $criteria = DetachedCriteria::forEntity(User::class) ->where('email', '=', $email->value) ->andWhere('active', '=', true) ->orderBy('created_at', 'DESC') ->limit(10); $users = $this->entityManager->findByCriteria($criteria); $user = $this->entityManager->findOneByCriteria($criteria); $count = $this->entityManager->countByCriteria($criteria); ``` ### QueryBuilder Integration Für komplexe Queries: ```php // QueryBuilder für Entity-Klasse $queryBuilder = $this->entityManager->createQueryBuilderFor(User::class); $users = $queryBuilder ->select('*') ->where('email LIKE ?', ['%@example.com']) ->andWhere('created_at > ?', ['2024-01-01']) ->orderBy('name', 'ASC') ->limit(50) ->fetchAll(); // QueryBuilder für Table (ohne Entity) $qb = $this->entityManager->createQueryBuilderForTable('users'); ``` ### Domain Events Integration ```php // Domain Event aufzeichnen $this->entityManager->recordDomainEvent($user, new UserRegisteredEvent($user)); // Events für Entity dispatchen $this->entityManager->dispatchDomainEventsForEntity($user); // Alle Events dispatchen $this->entityManager->dispatchAllDomainEvents(); // Event-Statistiken $stats = $this->entityManager->getDomainEventStats(); // Returns: ['total_events' => 23, 'events_by_entity' => [...]] ``` ## Repository Pattern ### Overview Das Repository Pattern abstrahiert Daten-Zugriff und bietet domain-spezifische Query-Methoden. Das Framework nutzt **Composition over Inheritance**. ### Base EntityRepository **Framework-Service** für Common Operations: ```php namespace App\Framework\Database\Repository; final readonly class EntityRepository { public function __construct( private EntityManager $entityManager ) {} public function find(string $entityClass, string $id): ?object public function findAll(string $entityClass): array public function findBy(string $entityClass, array $criteria, ...): array public function findOneBy(string $entityClass, array $criteria): ?object public function save(object $entity): object public function delete(object $entity): void public function transaction(callable $callback): mixed // Batch Loading (N+1 Prevention) public function findWithRelations(...): array // Pagination public function findPaginated(...): PaginatedResult // Batch Operations public function saveBatch(array $entities, int $batchSize = 100): array public function deleteBatch(array $entities, int $batchSize = 100): void } ``` ### Domain Repository Pattern **Composition-basiert** statt Inheritance: ```php namespace App\Domain\User\Repositories; use App\Framework\Database\Repository\EntityRepository; final readonly class UserRepository { public function __construct( private EntityRepository $entityRepository ) {} public function findById(UserId $id): ?User { return $this->entityRepository->find(User::class, $id->value); } public function findByEmail(Email $email): ?User { return $this->entityRepository->findOneBy( User::class, ['email' => $email->value] ); } public function findActiveUsers(): array { return $this->entityRepository->findBy( User::class, criteria: ['active' => true], orderBy: ['created_at' => 'DESC'] ); } public function save(User $user): User { return $this->entityRepository->save($user); } public function delete(User $user): void { $this->entityRepository->delete($user); } // Domain-spezifische Methode mit Batch Loading public function findUsersWithProfiles(array $userIds): array { return $this->entityRepository->findWithRelations( User::class, criteria: ['id' => $userIds], relations: ['profile', 'settings'] ); } // Pagination public function findPaginated(int $page, int $limit = 20): PaginatedResult { return $this->entityRepository->findPaginated( User::class, page: $page, limit: $limit, orderBy: ['created_at' => 'DESC'] ); } } ``` ### Repository Best Practices **✅ Composition Over Inheritance**: ```php // ✅ Framework Pattern - Composition final readonly class OrderRepository { public function __construct( private EntityRepository $entityRepository ) {} } // ❌ Avoid - Inheritance class OrderRepository extends BaseRepository { // Problematisch: Tight coupling } ``` **✅ Domain-Specific Methods**: ```php // ✅ Sprechende Domain-Methoden public function findOverdueOrders(): array { return $this->entityRepository->findBy( Order::class, ['status' => 'pending'], orderBy: ['due_date' => 'ASC'] ); } // ❌ Generic Methods außerhalb Domain public function findBy(array $criteria): array { // Zu generisch - nutze EntityRepository direkt } ``` **✅ N+1 Prevention**: ```php // ✅ Batch Loading für Relations public function findOrdersWithItems(array $orderIds): array { return $this->entityRepository->findWithRelations( Order::class, criteria: ['id' => $orderIds], relations: ['items', 'customer'] // Lädt in Batches ); } ``` ## Unit of Work Pattern ### Overview Der Unit of Work Pattern verwaltet Entities und deren Änderungen für transaktionale Konsistenz. **Key Features**: - **Change Tracking**: Automatische Änderungserkennung - **Transactional Consistency**: Atomic Commits/Rollbacks - **Batch Operations**: Optimierte Bulk-Operationen - **Entity States**: NEW, MANAGED, DIRTY, DELETED, DETACHED ### Entity Lifecycle ```php use App\Framework\Database\UnitOfWork\UnitOfWork; final readonly class OrderService { public function __construct( private EntityManager $entityManager ) {} public function createOrder(CreateOrderCommand $command): Order { $unitOfWork = $this->entityManager->unitOfWork; // 1. Create new entity (state: NEW) $order = Order::create($command); // 2. Persist marks entity for INSERT (state: NEW → MANAGED) $unitOfWork->persist($order); // 3. Flush writes to database (state: MANAGED) $unitOfWork->flush(); // 4. Commit transaction $unitOfWork->commit(); return $order; } } ``` ### Change Tracking **Automatic Change Detection**: ```php $unitOfWork = $this->entityManager->unitOfWork; // Load entity (state: MANAGED) $user = $this->entityManager->find(User::class, $userId); // Modify entity $user->updateProfile($newName); // Change Tracking erkennt Änderung automatisch $unitOfWork->flush(); // UPDATE query wird generiert $unitOfWork->commit(); ``` **Manual Change Tracking**: ```php // Entity aus externer Quelle (state: DETACHED) $user = unserialize($serializedUser); // Merge in Unit of Work (state: DETACHED → MANAGED) $managedUser = $unitOfWork->merge($user); // Änderungen werden getrackt $unitOfWork->flush(); ``` ### Transaction Management **Explicit Transactions**: ```php public function processOrder(Order $order): void { $unitOfWork = $this->entityManager->unitOfWork; try { $unitOfWork->beginTransaction(); // Multiple operations $order->confirm(); $unitOfWork->persist($order); $this->inventoryService->reserve($order->items); $this->paymentService->charge($order->total); // Write changes to database $unitOfWork->flush(); // Commit transaction $unitOfWork->commit(); } catch (\Exception $e) { $unitOfWork->rollback(); throw $e; } } ``` **Implicit Transactions (Auto-Commit)**: ```php // Auto-commit ist standardmäßig aktiviert $unitOfWork->persist($entity); // Automatisch committed ``` **Disable Auto-Commit** für Batch Operations: ```php $unitOfWork->setAutoCommit(false); try { $unitOfWork->beginTransaction(); foreach ($orders as $order) { $unitOfWork->persist($order); // Kein Auto-Commit } $unitOfWork->flush(); // Batch-Write $unitOfWork->commit(); // Atomic Commit } catch (\Exception $e) { $unitOfWork->rollback(); throw $e; } finally { $unitOfWork->setAutoCommit(true); } ``` ### Entity States ```php use App\Framework\Database\UnitOfWork\EntityState; // Check Entity State $state = $unitOfWork->getEntityState($entity); match ($state) { EntityState::NEW => 'Entity marked for INSERT', EntityState::MANAGED => 'Entity being tracked, no changes', EntityState::DIRTY => 'Entity modified, pending UPDATE', EntityState::DELETED => 'Entity marked for DELETE', EntityState::DETACHED => 'Entity not managed by UnitOfWork', }; // Check if entity is managed if ($unitOfWork->contains($entity)) { // Entity is managed (NEW, MANAGED, DIRTY, or DELETED) } ``` ### Detaching Entities ```php // Detach entity from Unit of Work $unitOfWork->detach($user); // Entity state: DETACHED // Changes to entity are no longer tracked ``` **Use Cases**: - Long-running processes - Serialization/Caching - Read-only operations - Memory management in batch processing ### Bulk Operations ```php // Bulk Insert $unitOfWork->bulkInsert($entities); // Optimized batch INSERT // Bulk Update $unitOfWork->bulkUpdate($entities); // Optimized batch UPDATE // Bulk Delete $unitOfWork->bulkDelete($entities); // Optimized batch DELETE ``` ## Query Optimization ### N+1 Query Problem **Erkennnung**: Jede Iteration führt zusätzliche Query aus ```php // ❌ N+1 Problem $orders = $this->entityManager->findAll(Order::class); // 1 Query foreach ($orders as $order) { echo $order->getCustomer()->name; // N Queries (1 pro Order) } // Total: 1 + N Queries ``` ### Solution: Batch Loading ```php // ✅ Batch Loading $orders = $this->entityManager->findWithRelations( Order::class, criteria: [], relations: ['customer', 'items'] // Preload relations ); foreach ($orders as $order) { echo $order->getCustomer()->name; // Kein Query - bereits geladen } // Total: 2-3 Queries (1 für Orders, 1-2 für Relations) ``` ### Eager vs Lazy Loading **Strategic Loading**: ```php // Heavy Operation - Eager Loading public function getOrderDetails(OrderId $id): OrderDetails { $order = $this->entityManager->findEager(Order::class, $id->value); // Alle Relations sofort geladen return OrderDetails::fromOrder($order); } // Light Operation - Lazy Loading public function listOrders(): array { // Relations werden nur bei Bedarf geladen return $this->entityManager->findAll(Order::class); } ``` ### Pagination ```php use App\Framework\Database\Repository\EntityRepository; public function getUsers(int $page): PaginatedResult { return $this->entityRepository->findPaginated( User::class, page: $page, limit: 20, orderBy: ['created_at' => 'DESC'] ); } // PaginatedResult enthält: // - items: array (Entities) // - total: int (Total count) // - page: int (Current page) // - limit: int (Items per page) // - totalPages: int (Calculated) ``` ### Query Caching ```php // EntityManager mit Cache $entityManager = new EntityManager( // ... cacheManager: $cacheManager ); // Find verwendet Cache automatisch $user = $entityManager->find(User::class, $id); // Beim zweiten Aufruf: Cache Hit ``` ## Connection Pooling ### Overview Connection Pooling verbessert Performance durch Wiederverwendung von Datenbankverbindungen. **Features**: - Min/Max Connection Limits - Connection Health Monitoring - Automatic Retry mit Exponential Backoff - Idle Connection Cleanup - Connection Warmup ### Configuration ```php use App\Framework\Database\Config\PoolConfig; $poolConfig = new PoolConfig( minConnections: 2, // Minimum pool size maxConnections: 10, // Maximum pool size maxIdleTime: 300, // Idle timeout (seconds) healthCheckInterval: 60, // Health check frequency enableWarmup: true // Pre-create min connections ); ``` ### Usage ```php use App\Framework\Database\ConnectionPool; $pool = new ConnectionPool($driverConfig, $poolConfig, $timer); // Get connection from pool $connection = $pool->getConnection(); try { // Use connection $result = $connection->query('SELECT * FROM users'); } finally { // Return connection to pool (automatic with PooledConnection) $connection->release(); } ``` ### Health Monitoring ```php // Pool automatically monitors connection health $pool->performHealthCheck(); // Get pool statistics $stats = $pool->getPoolStatistics(); // Returns: [ 'current_connections' => 5, 'max_connections' => 10, 'connections_in_use' => 3, 'healthy_connections' => 5, 'total_created' => 12, 'total_destroyed' => 7 ] ``` ### Retry Logic ```php // Automatic retry with exponential backoff $retryStrategy = ExponentialBackoffStrategy::create() ->withMaxAttempts(3) ->withBaseDelay(Duration::fromMilliseconds(100)); // Connection pool uses retry automatically $connection = $pool->getConnectionWithRetry($retryStrategy); ``` ### Production Configuration ```php // Production Pool Config $productionPoolConfig = new PoolConfig( minConnections: 5, // Keep 5 connections ready maxConnections: 50, // Scale up to 50 under load maxIdleTime: 600, // 10 minutes idle timeout healthCheckInterval: 30, // Check every 30 seconds enableWarmup: true, // Pre-warm connections connectionTimeout: 5000 // 5 second timeout ); ``` ## Transaction Management ### Basic Transactions ```php // EntityManager Transaction Helper $this->entityManager->transaction(function (EntityManager $em) { $order = Order::create($command); $em->save($order); $this->inventoryService->reserve($order->items); $this->paymentService->charge($order->total); // Automatic commit on success, rollback on exception }); ``` ### Manual Transaction Control ```php $unitOfWork = $this->entityManager->unitOfWork; $unitOfWork->beginTransaction(); try { // Operations $unitOfWork->persist($entity1); $unitOfWork->persist($entity2); $unitOfWork->flush(); $unitOfWork->commit(); } catch (\Exception $e) { $unitOfWork->rollback(); throw $e; } ``` ### Nested Transactions (Savepoints) ```php $unitOfWork->beginTransaction(); try { $order = Order::create($command); $unitOfWork->persist($order); // Nested transaction (savepoint) $unitOfWork->beginTransaction(); try { $this->inventoryService->reserve($order->items); $unitOfWork->commit(); // Commit savepoint } catch (\Exception $e) { $unitOfWork->rollback(); // Rollback to savepoint throw $e; } $unitOfWork->commit(); // Commit main transaction } catch (\Exception $e) { $unitOfWork->rollback(); // Rollback main transaction throw $e; } ``` ### Transaction Isolation Levels ```php use App\Framework\Database\TransactionIsolation; $unitOfWork->beginTransaction( isolation: TransactionIsolation::READ_COMMITTED ); // Available isolation levels: // - READ_UNCOMMITTED (lowest isolation) // - READ_COMMITTED (default) // - REPEATABLE_READ // - SERIALIZABLE (highest isolation) ``` ### Transaction Best Practices **✅ Short Transactions**: ```php // ✅ Keep transactions short $this->entityManager->transaction(fn(EntityManager $em) => $em->save($entity) ); ``` **❌ Long Transactions**: ```php // ❌ Avoid long-running transactions $this->entityManager->transaction(function (EntityManager $em) { $this->sendEmail($user); // External I/O - BAD $this->processHeavyComputation(); // CPU-intensive - BAD $em->save($entity); }); ``` **✅ Batch with Periodic Commits**: ```php // ✅ Batch processing with periodic commits $unitOfWork->setAutoCommit(false); $batchSize = 100; for ($i = 0; $i < count($items); $i++) { $unitOfWork->persist($items[$i]); if (($i + 1) % $batchSize === 0) { $unitOfWork->flush(); $unitOfWork->commit(); $unitOfWork->beginTransaction(); } } $unitOfWork->flush(); $unitOfWork->commit(); $unitOfWork->setAutoCommit(true); ``` ## 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