Drupal 7 Datenbank-Abfragen mit EntityFieldQuery

Eine wertvolle Neuerung aus Drupal 7 ist die EntityFieldQuery API. Hiermit können programmatische Inhalts-Abfragen über jede definierte Entität getätigt werden. Wollte man in Drupal 6 programmatisch eine Datenbankabfrage über bestimmte CCK-Felder machen, empfahl es sich aus Kompatibilitätsgründen mit Views eine Abfrage zu erstellen, diesen dann im Code zu laden und auszuführen. So war sichergestellt, dass kein Update am CCK Modul oder Veränderungen an der Datenstruktur zu Kompatibilitätsproblemen mit dem eigenen db_query()-Call führten. EntityFieldQuery ist eine alternative und deutlich bessere Abstraktionsschicht für diesen Anwendungsfall, denn es ist eine Drupal 7 Core Funktionalität und kein Modul in dessen Abhängigkeit man sich begibt, es arbeitet für simple Calls deutlich performanter als Views und es kann extrem flexibel eingesetzt und sogar um eigene Methoden erweitert werden. Die Fähigkeiten und Performance dieser API gehen soweit, dass es zu einer Grundüberlegung gemacht werden sollte, ob man für eine Darstellung von Inhalt das Views Modul oder doch lieber ein eigenes Modul mit EntityFieldQuery nutzen sollte.

Aber halten wir uns nicht lange auf: Es folgt der Code, der das alles möglich macht. Da es sich um eine vollwertige PHP Klasse handelt, muss eine Instanz erstellt werden.

$query = new EntityFieldQuery;

Jetzt können über diese Instanz bestimmte Bedingungen für die Entitäten-Ermittlung definiert werden. Das sind im Wesentlichen entityConditions, propertyConditions und fieldConditions. Der Unterschied ist selbsterklärend: entityConditions sind Bedingungen die für die Entität im Ganzen gelten, propertyConditions sind Bedingungen für die Entitäts-Eigenschaften und fieldConditions für die Felder der Entität. Hier ein Beispiel, wie mit der neu erstellten Instanz weitergearbeitet werden kann.

$result = $query
  ->entityCondition('entity_type', 'node')
  ->entityCondition('bundle', array('article', 'page'))
  ->propertyCondition('status', 1)
  ->fieldCondition('field_date', 'value', date("Y-m-d H:i:s"), '>')
  ->fieldOrderBy('field_date', 'value', 'ASC')
  ->range(0,20)
  ->execute();
$nids = array_keys($result['node']);

Mit diesen Bedingungen würden die ersten 20 veröffentlichten Nodes vom Typ article und page ausgelesen, deren Feld "field_date" einen Wert hat der größer ist als das jetzige Datum. Sortiert ist das Resultat aufsteigend nach besagtem field_date.

Natürlich kann bei entity_type jede Entität definiert werden. Auch selbstdefinierte Entitäten können hier genutzt werden. In propertyCondition können im Grunde alle Spalten der Datenbank-Tabelle dieser Entität genutzt werden. Bei node gibt es u.a. noch "created", "changed", "nid", usw. Bei fieldCondition verhält es sich genauso, es können alle Datenbank-Spalten dieser speziellen Feld-Tabelle genutzt werden. Die Spalte heisst in der Regel 'value', aber nicht immer. Z.B. bei einem E-Mail-Feld vom E-Mail-Feld-Modul nennt sich diese Spalte "email". Oder bei einem Entity-Reference-Feld nennt sich diese Spalte "target_id". Es muss also im Zweifel einfach ein kurzer Blick in die Datenbank getätigt werden um den genauen Bezeichner schnell herausfinden zu können. Auch muss das Format der Daten berücksichtigt werden. Date-Felder speichern in der Regel keine UNIX-Timestamps sondern SQL-konforme Timestamps. Hat man als Vergleich nur einen Unix-Timestamp zur Verfügung, muss dieser vor der Eingabe als Parameter entsprechend konvertiert werden. Sowohl bei propertyCondition als auch bei fieldCondition ist der standardmäßige Operator immer "=". Möchte man diesen ändern muss man das als Parameter tun, wie in obigem Beispiel bei fieldCondition demonstriert wird.

Der Rückgabewert ist ein Array, was als Schlüssel die Entitäts-ID und als Werte erneut die Entitäts-ID und die Revisions-ID der Entität enthält. Mit array_keys können die NIDs in ein neues Array übertragen werden, dass numerisch aufsteigende Keys enthält. Damit ist es leichter, weiterzuarbeiten. Jetzt können z.B. die ganzen Entitäts-Objekte geladen werden mit

$nodeobjects = entity_load('node', $nids);

und dann z.B. ein Render-Array aller Node-Teaser erstellt werden mit

$output = node_view_multiple($nodeobjects);

Schwächen sind, dass man zwar mehrere field- oder propertyConditions definieren kann, diese aber nicht per OR verknüpfen kann. Es sind immer AND-Verbindungen. Auch mag es übertrieben scheinen immer gleich im Anschluss per entity_load alle Entitäts-Objekte laden zu müssen um z.B. die Werte eines bestimmten Feldes zu bekommen. Es wäre schön, wenn man direkt über die Query bestimmen kann, welche Werte zusätzlich zur nid und vid über execute() zurückgegeben werden. Aber - es überrascht nicht - das ist möglich. Nur muss man dazu die EntityFieldQuery Klasse mit einer eigenen Klasse extenden. Dazu mehr in einem weiteren Artikel.