YForm Relationen effizient laden mit populateRelation
Quell-ID: GitHub Discussion #11
Use Case
Bei der Ausgabe von YForm-Datensätzen mit Relationen sollen die verknüpften Daten effizient geladen werden, um das N+1-Query-Problem zu vermeiden.
Verwendete AddOns
- YForm
Problemstellung
Ohne Optimierung führt jeder Aufruf von getRelatedDataset() oder getRelatedCollection() innerhalb einer Schleife zu einer separaten Datenbankabfrage. Bei 100 Produkten mit Kategorien entstehen so 101 Queries (1 für Produkte + 100 für Kategorien).
Lösung: populateRelation
Mit populateRelation() werden alle benötigten verknüpften Datensätze in einer einzigen Query vorgeladen:
<?php
$products = Product::query()->find();
$products->populateRelation('category_id'); // Alle Kategorien vorladen
foreach ($products as $product) {
echo $product->name;
echo $product->description;
// Keine zusätzliche Query mehr!
$category = $product->getRelatedDataset('category_id');
echo $category->name;
}
Ergebnis: Konstant 2 Datenbankabfragen statt n+1.
Verschachtelte Relationen
Bei verschachtelten Schleifen können mehrere Ebenen vorgeladen werden:
<?php
$products = Product::query()->find();
// Erste Ebene: Tags zu Produkten laden
$tags = $products->populateRelation('tags');
// Zweite Ebene: Kategorien zu Tags laden
$tags->populateRelation('category_id');
foreach ($products as $product) {
echo $product->name;
foreach ($product->getRelatedCollection('tags') as $tag) {
echo $tag->name;
// Keine zusätzliche Query!
$category = $tag->getRelatedDataset('category_id');
echo $category->name;
}
}
Ergebnis: Konstant 3 Queries statt potenziell hunderte.
Wann sollte man populateRelation verwenden?
Immer wenn getRelatedDataset() oder getRelatedCollection() innerhalb einer Schleife aufgerufen wird, sollte man stutzig werden. In der Regel ist dann populateRelation() eine einfache und effektive Lösung.
Unterstützte Relationstypen
populateRelation() funktioniert für alle YForm-Relationstypen:
- Single-Relation (1:1)
- Multiple-Relation (1:n)
- Mit Relationstabelle (n:m)
- Ohne Relationstabelle
Besserer Ansatz für komplexe Abfragen
Bei sehr komplexen Datenstrukturen kann ein manueller JOIN effizienter sein:
<?php
$sql = rex_sql::factory();
$query = '
SELECT p.*, c.name AS category_name
FROM ' . rex::getTable('product') . ' p
LEFT JOIN ' . rex::getTable('category') . ' c ON p.category_id = c.id
ORDER BY p.name
';
$products = $sql->getArray($query);
foreach ($products as $product) {
echo $product['name'] . ' - ' . $product['category_name'];
}
Diese Variante ist für sehr große Datenmengen oder spezielle Anforderungen (z.B. Aggregationen) geeignet.
Performance-Vergleich
| Methode | Queries bei 100 Produkten |
|---|---|
| Ohne Optimierung | ~101 |
| Mit populateRelation | 2 |
| Mit manuellem JOIN | 1 |