# Hydrator Eager Loading System

Das Eager Loading System löst das **N+1 Query Problem** durch intelligente Batch-Queries und bietet massive Performance-Verbesserungen für Relations-intensive Anwendungen.

## Problem: N+1 Query Problem

### Vorher (Lazy Loading)
```php
// 1 Query für Users
$users = $repository->findAll(); // SELECT * FROM users

// N Queries für Relations (1 pro User)
foreach($users as $user) {
    $user->posts;    // SELECT * FROM posts WHERE user_id = ?
    $user->profile;  // SELECT * FROM profiles WHERE user_id = ?
}
// Total: 1 + (N × 2) = 201 Queries für 100 Users!
```

### Nachher (Eager Loading)
```php
// 3 Queries total durch Batch-Loading
$users = $hydrator->hydrateManyWithRelations($metadata, $userData, ['posts', 'profile']);
// 1. SELECT * FROM users
// 2. SELECT * FROM posts WHERE user_id IN (1,2,3,...,100)  
// 3. SELECT * FROM profiles WHERE user_id IN (1,2,3,...,100)
// Total: 3 Queries für 100 Users = 98.5% Reduktion!
```

## Features

### ✅ Selective Eager Loading
```php
// Lade nur spezifizierte Relations
$user = $hydrator->hydrateWithRelations($metadata, $data, ['posts', 'comments']);

// Ohne Relations = normale Hydration
$user = $hydrator->hydrateWithRelations($metadata, $data, []);
```

### ✅ Batch Processing für Collections
```php
// N+1 Problem gelöst für Collections
$users = $hydrator->hydrateManyWithRelations($metadata, $dataRows, ['posts', 'profile']);

// Intelligente Gruppierung nach Relation-Types
// - belongsTo: IN-Queries für Foreign Keys
// - hasMany: Gruppierung nach Local Keys  
// - one-to-one: Eindeutige Zuordnung
```

### ✅ Relation-Type Support
- **belongsTo**: Foreign Key Lookups mit IN-Queries
- **hasMany**: Reverse Foreign Key Lookups mit Gruppierung
- **one-to-one**: Eindeutige Relations-Zuordnung

## API Usage

### Single Entity mit Relations
```php
$user = $hydrator->hydrateWithRelations(
    $userMetadata, 
    $userData, 
    ['posts', 'profile', 'roles']
);

// Relations sind sofort verfügbar, keine zusätzlichen Queries
echo count($user->posts);    // Kein Query
echo $user->profile->bio;    // Kein Query
```

### Multiple Entities (Batch Loading)
```php
$users = $hydrator->hydrateManyWithRelations(
    $userMetadata,
    $userDataRows,
    ['posts', 'comments', 'profile']
);

// Alle Relations wurden mit nur 4 Queries geladen:
// 1x Users, 1x Posts, 1x Comments, 1x Profiles
```

### Performance-kritische Scenarios
```php
// Blog-System: Posts mit Comments, Tags, Categories
$posts = $hydrator->hydrateManyWithRelations(
    $postMetadata,
    $postDataRows, 
    ['comments', 'tags', 'category', 'author']
);

// Ohne Eager Loading: 1 + (50 × 4) = 201 Queries
// Mit Eager Loading: 5 Queries = 97.5% Reduktion
```

## Performance Benchmarks

### Real-World Scenarios

| Scenario | Entities | Relations | Lazy Queries | Eager Queries | Reduction |
|----------|----------|-----------|--------------|---------------|-----------|
| User Dashboard | 50 users | posts, profile | 101 | 3 | **97.0%** |
| Blog Listing | 20 posts | comments, tags, author | 81 | 4 | **95.1%** |
| E-Commerce | 100 products | category, reviews, images | 301 | 4 | **98.7%** |
| Social Feed | 30 posts | author, comments, likes | 91 | 4 | **95.6%** |

### Database Load Reduction
```php
// Blog System Beispiel (50 Posts)
$lazyQueries = 1 + (50 × 3);     // 151 Queries
$eagerQueries = 4;                // 4 Queries  
$loadReduction = 151 / 4;         // 37.75x weniger DB-Load!
```

## Implementation Details

### Batch Loading Algorithm
1. **Entity Creation**: Erstelle alle Entities ohne Relations
2. **Key Collection**: Sammle alle IDs/Foreign Keys für Batch-Queries
3. **Batch Queries**: Ein Query pro Relation-Type mit IN-Clauses
4. **Grouping**: Gruppiere Related Entities nach Keys
5. **Assignment**: Weise Relations den entsprechenden Entities zu

### BelongsTo Relations (Foreign Key Lookup)
```php
// Sammle alle Foreign Keys
$foreignKeys = array_unique(array_column($dataRows, 'category_id'));

// Ein Batch-Query für alle Categories
$categories = $entityLoader->findBy(Category::class, ['id' => $foreignKeys]);

// Gruppiere nach ID für schnelle Zuordnung
$categoriesById = array_column($categories, null, 'id');
```

### HasMany Relations (Reverse Lookup)
```php  
// Sammle alle Entity IDs
$userIds = array_column($userDataRows, 'id');

// Ein Batch-Query für alle Posts
$posts = $entityLoader->findBy(Post::class, ['user_id' => $userIds]);

// Gruppiere Posts nach user_id
$postsByUserId = [];
foreach($posts as $post) {
    $postsByUserId[$post->user_id][] = $post;
}
```

### One-to-One Relations
```php
// Ähnlich wie hasMany, aber nur eine Relation pro Entity
$profiles = $entityLoader->findBy(Profile::class, ['user_id' => $userIds]);
$profileByUserId = array_column($profiles, null, 'user_id');
```

## Error Handling & Edge Cases

### Missing Relations
```php
// Entities ohne Relations erhalten Default-Werte
$userPosts = $postsByUserId[$userId] ?? []; // Empty array für hasMany
$userProfile = $profilesByUserId[$userId] ?? null; // null für belongsTo/one-to-one
```

### Empty Data Sets
```php
$result = $hydrator->hydrateManyWithRelations($metadata, [], ['posts']);
// Returns: [] (empty array, keine Fehler)
```

### Invalid Relations
```php
// Nicht-existierende oder Nicht-Relations werden übersprungen
$user = $hydrator->hydrateWithRelations($metadata, $data, ['invalid_relation']); 
// Kein Fehler, Relation wird ignoriert
```

## Best Practices

### 1. Selective Loading
```php
// ✅ Gut: Nur benötigte Relations laden
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments']);

// ❌ Schlecht: Alle Relations laden
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments', 'tags', 'category', 'ratings']);
```

### 2. Batch Processing priorisieren
```php
// ✅ Gut: Batch Loading für Collections
$users = $hydrator->hydrateManyWithRelations($metadata, $dataRows, ['posts']);

// ❌ Schlecht: Einzelne Hydration in Loop
foreach($dataRows as $data) {
    $users[] = $hydrator->hydrateWithRelations($metadata, $data, ['posts']);
}
```

### 3. Relation Depth begrenzen
```php
// ✅ Gut: Direkte Relations
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments']);

// ⚠️ Consideration: Nested Relations
// (Aktuell nicht unterstützt, würde zusätzliche Implementierung benötigen)
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author.profile', 'comments.author']);
```

## Benefits Summary

1. **Performance**: 95-98% Reduktion der Database-Queries
2. **Scalability**: Lineare statt exponentieller Query-Wachstum
3. **Resource Efficiency**: Drastisch reduzierte DB-Connection Usage
4. **User Experience**: Faster page loads and API responses
5. **Server Stability**: Reduzierter Memory- und CPU-Verbrauch
6. **Database Health**: Weniger Lock-Contention und Connection-Pool-Pressure

## Migration Path

### Bestehende Code-Base
```php
// Vorher: Lazy Loading mit N+1 Problem
$users = $repository->findAll();
foreach($users as $user) {
    // Implicit queries triggered
    $user->posts; 
    $user->profile;
}

// Nachher: Explicit Eager Loading  
$userData = $repository->findAllAsData(); // Raw data query
$users = $hydrator->hydrateManyWithRelations($metadata, $userData, ['posts', 'profile']);
```

### Performance Monitoring
- Monitor Query Count vor/nach Migration
- Database Profiling für Query-Performance
- Application Response Time Tracking
- Memory Usage Monitoring bei Large Collections