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