Files
michaelschiemer/docs/claude/database-patterns.md

1527 lines
38 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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