Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Query Builder und DQL

Query Builder und DQL

~16 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

findBy() aus Kapitel 21 reicht für einfache Abfragen – für komplexere Fälle (Kombinationen aus Filtern, Sortierung, Aggregation) brauchen wir Doctrines Query Builder und DQL (Doctrine Query Language).

DQL vs. SQL: der entscheidende Unterschied

DQL sieht SQL syntaktisch ähnlich, arbeitet aber mit ENTITY-Klassen und deren PROPERTIES statt mit Tabellen und Spalten – Doctrine übersetzt DQL automatisch in die tatsächliche SQL-Dialekt Ihrer konfigurierten Datenbank (PostgreSQL, MySQL, ...), OHNE dass Sie sich um Dialekt-Unterschiede kümmern müssen.

Den Query Builder im Repository nutzen

Eigene, wiederverwendbare Abfrage-Methoden gehören INS Repository, NICHT in den Controller – der Controller soll NICHTS über die konkrete Abfrage-Implementierung wissen müssen:

src/Repository/TaskRepository.php
// ... Constructor wie in Kapitel 21 ...

/**
 * @return Task[]
 */
public function findUeberfaelligeAufgaben(): array
{
    return $this->createQueryBuilder('t')
        ->andWhere('t.faelligAm < :heute')
        ->andWhere('t.status != :erledigt')
        ->setParameter('heute', new \DateTimeImmutable())
        ->setParameter('erledigt', 'erledigt')
        ->orderBy('t.faelligAm', 'ASC')
        ->getQuery()
        ->getResult()
    ;
}

createQueryBuilder('t') erzeugt einen Query Builder mit t als Alias für Task – GENAU wie ein SQL-Tabellen-Alias. setParameter() bindet Werte SICHER (schützt automatisch vor SQL-Injection) statt sie direkt in den Query-String einzusetzen.

Achtung: NIEMALS Nutzereingaben direkt in einen DQL/Query-Builder-String einbauen (z. B. per String-Konkatenation) – IMMER setParameter() verwenden. Genau wie vorbereitete SQL-Statements schützt das zuverlässig vor Injection-Angriffen.

JOINs über Beziehungen

/**
 * @return Task[]
 */
public function findAufgabenFuerNutzer(User $user): array
{
    return $this->createQueryBuilder('t')
        ->innerJoin('t.project', 'p')
        ->innerJoin('p.mitglieder', 'm')
        ->andWhere('m = :nutzer')
        ->setParameter('nutzer', $user)
        ->getQuery()
        ->getResult()
    ;
}

innerJoin('t.project', 'p') folgt der ManyToOne-Beziehung aus Kapitel 22 (KEIN manuelles ON-Statement nötig, Doctrine kennt die Verbindung bereits aus dem Entity-Mapping), innerJoin('p.mitglieder', 'm') folgt der ManyToMany-Beziehung aus Kapitel 23 – dieselbe Methode funktioniert für BEIDE Beziehungstypen identisch.

DQL direkt als String

Für manche Fälle ist rohes DQL lesbarer als der Query Builder – funktional GLEICHWERTIG, reine Geschmackssache/Team-Konvention:

$query = $this->getEntityManager()->createQuery(
    'SELECT t FROM App\Entity\Task t WHERE t.status = :status ORDER BY t.faelligAm ASC'
);
$query->setParameter('status', 'offen');

$aufgaben = $query->getResult();

Aggregation: COUNT, AVG und Co.

public function zaehleAufgabenNachStatus(Project $project): array
{
    return $this->createQueryBuilder('t')
        ->select('t.status, COUNT(t.id) as anzahl')
        ->andWhere('t.project = :project')
        ->setParameter('project', $project)
        ->groupBy('t.status')
        ->getQuery()
        ->getResult()
    ;
}

Ideal für Dashboard-Statistiken (Kapitel 45 nutzt genau diese Art von Abfrage, gecacht) – liefert ein Array mit status/anzahl-Paaren statt vollständiger Entity-Objekte.

getResult() vs. getOneOrNullResult() vs. getSingleResult()

MethodeVerhalten
getResult()Liefert ein Array – auch bei 0 oder 1 Treffern, NIEMALS einen Fehler.
getOneOrNullResult()Erwartet 0 oder 1 Treffer – liefert das Objekt oder null; wirft bei MEHR als 1 Treffer eine Exception.
getSingleResult()Erwartet GENAU 1 Treffer – wirft bei 0 ODER mehr als 1 Treffer eine Exception.

Tipp: find() aus Kapitel 21 nutzt INTERN getOneOrNullResult()-artiges Verhalten (liefert null statt zu werfen) – für eigene Query-Builder-Methoden, die GENAU EIN Ergebnis erwarten (z. B. "das aktuellste Projekt"), ist getOneOrNullResult() meist die sicherere Wahl gegenüber getSingleResult().