feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready
This commit is contained in:
@@ -464,23 +464,937 @@ $schema->table('users', function (Blueprint $table) {
|
||||
|
||||
## EntityManager Usage
|
||||
|
||||
TODO: Document EntityManager and UnitOfWork pattern
|
||||
### 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
|
||||
|
||||
TODO: Document repository implementation and usage
|
||||
### 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
|
||||
|
||||
TODO: Document N+1 prevention and batch loading
|
||||
### 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
|
||||
|
||||
TODO: Document connection pool configuration
|
||||
### 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
|
||||
|
||||
TODO: Document transaction patterns and best practices
|
||||
### 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user