Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,243 @@
# 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