Database Explorer

Der Explorer bietet eine intuitive und effiziente Methode zur Arbeit mit der Datenbank. Er kümmert sich automatisch um Beziehungen zwischen Tabellen und die Optimierung von Abfragen, sodass Sie sich auf Ihre Anwendung konzentrieren können. Er funktioniert sofort ohne Konfiguration. Wenn Sie die volle Kontrolle über SQL-Abfragen benötigen, können Sie den SQL-Zugriff nutzen.

  • Die Arbeit mit Daten ist natürlich und leicht verständlich
  • Generiert optimierte SQL-Abfragen, die nur die benötigten Daten laden
  • Ermöglicht einfachen Zugriff auf verwandte Daten ohne die Notwendigkeit, JOIN-Abfragen zu schreiben
  • Funktioniert sofort ohne jegliche Konfiguration oder Generierung von Entitäten

Mit dem Explorer beginnen Sie, indem Sie die Methode table() des Objekts Nette\Database\Explorer aufrufen (Details zur Verbindung finden Sie im Kapitel Verbindung und Konfiguration):

$books = $explorer->table('book'); // 'book' ist der Tabellenname

Die Methode gibt ein Objekt Selection zurück, das eine SQL-Abfrage repräsentiert. An dieses Objekt können weitere Methoden zur Filterung und Sortierung der Ergebnisse angehängt werden. Die Abfrage wird erst zusammengestellt und ausgeführt, wenn Sie Daten anfordern, beispielsweise durch Iteration mit einer foreach-Schleife. Jede Zeile wird durch ein Objekt ActiveRow repräsentiert:

foreach ($books as $book) {
	echo $book->title;        // Ausgabe der Spalte 'title'
	echo $book->author_id;    // Ausgabe der Spalte 'author_id'
}

Der Explorer erleichtert die Arbeit mit Beziehungen zwischen Tabellen erheblich. Das folgende Beispiel zeigt, wie einfach Sie Daten aus verknüpften Tabellen (Bücher und ihre Autoren) ausgeben können. Beachten Sie, dass Sie keine JOIN-Abfragen schreiben müssen; Nette erstellt sie für Sie:

$books = $explorer->table('book');

foreach ($books as $book) {
	echo 'Buch: ' . $book->title;
	echo 'Autor: ' . $book->author->name; // erstellt JOIN zur Tabelle 'author'
}

Nette Database Explorer optimiert Abfragen, um sie so effizient wie möglich zu gestalten. Das obige Beispiel führt nur zwei SELECT-Abfragen aus, unabhängig davon, ob Sie 10 oder 10.000 Bücher verarbeiten.

Zusätzlich verfolgt der Explorer, welche Spalten im Code verwendet werden, und lädt nur diese aus der Datenbank, wodurch weitere Leistung eingespart wird. Dieses Verhalten ist vollständig automatisch und adaptiv. Wenn Sie später den Code ändern und weitere Spalten verwenden, passt der Explorer die Abfragen automatisch an. Sie müssen nichts einstellen oder darüber nachdenken, welche Spalten Sie benötigen werden – überlassen Sie das Nette.

Filterung und Sortierung

Die Klasse Selection bietet Methoden zur Filterung und Sortierung der Datenauswahl.

where($condition, ...$params) Fügt eine WHERE-Bedingung hinzu. Mehrere Bedingungen werden mit dem AND-Operator verknüpft
whereOr(array $conditions) Fügt eine Gruppe von WHERE-Bedingungen hinzu, die mit dem OR-Operator verknüpft sind
wherePrimary($value) Fügt eine WHERE-Bedingung nach dem Primärschlüssel hinzu
order($columns, ...$params) Legt die ORDER BY-Sortierung fest
select($columns, ...$params) Spezifiziert die Spalten, die geladen werden sollen
limit($limit, $offset = null) Begrenzt die Anzahl der Zeilen (LIMIT) und setzt optional den OFFSET
page($page, $itemsPerPage, &$total = null) Legt die Paginierung fest
group($columns, ...$params) Gruppiert Zeilen (GROUP BY)
having($condition, ...$params) Fügt eine HAVING-Bedingung zur Filterung gruppierter Zeilen hinzu

Methoden können verkettet werden (sog. Fluent Interface): $table->where(...)->order(...)->limit(...).

In diesen Methoden können Sie auch spezielle Notationen für den Zugriff auf Daten aus verwandten Tabellen verwenden.

Escaping und Bezeichner

Methoden escapen automatisch Parameter und setzen Bezeichner (Tabellen- und Spaltennamen) in Anführungszeichen, wodurch SQL-Injection verhindert wird. Für die korrekte Funktion müssen einige Regeln beachtet werden:

  • Schlüsselwörter, Funktionsnamen, Prozedurnamen usw. großschreiben.
  • Spalten- und Tabellennamen kleinschreiben.
  • Zeichenketten immer über Parameter einfügen.
where('name = ' . $name);         // KRITISCHE SCHWACHSTELLE: SQL-Injection
where('name LIKE "%search%"');    // FALSCH: erschwert das automatische Setzen von Anführungszeichen
where('name LIKE ?', '%search%'); // RICHTIG: Wert über Parameter eingefügt

where('name like ?', $name);     // FALSCH: generiert: `name` `like` ?
where('name LIKE ?', $name);     // RICHTIG: generiert: `name` LIKE ?
where('LOWER(name) = ?', $value);// RICHTIG: LOWER(`name`) = ?

where (string|array $condition, …$parameters)static

Filtert Ergebnisse anhand von WHERE-Bedingungen. Ihre Stärke liegt in der intelligenten Verarbeitung verschiedener Wertetypen und der automatischen Wahl von SQL-Operatoren.

Grundlegende Verwendung:

$table->where('id', $value);     // WHERE `id` = 123
$table->where('id > ?', $value); // WHERE `id` > 123
$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow'

Dank der automatischen Erkennung geeigneter Operatoren müssen Sie sich nicht um verschiedene Sonderfälle kümmern. Nette löst sie für Sie:

$table->where('id', 1);          // WHERE `id` = 1
$table->where('id', null);       // WHERE `id` IS NULL
$table->where('id', [1, 2, 3]);  // WHERE `id` IN (1, 2, 3)
// Es kann auch ein Fragezeichen-Platzhalter ohne Operator verwendet werden:
$table->where('id ?', 1);        // WHERE `id` = 1

Die Methode verarbeitet auch negative Bedingungen und leere Arrays korrekt:

$table->where('id', []);         // WHERE `id` IS NULL AND FALSE -- findet nichts
$table->where('id NOT', []);     // WHERE `id` IS NULL OR TRUE -- findet alles
$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- findet alles
// $table->where('NOT id ?', $ids);  Achtung - diese Syntax wird nicht unterstützt

Als Parameter können Sie auch das Ergebnis aus einer anderen Tabelle übergeben – es wird eine Unterabfrage erstellt:

// WHERE `id` IN (SELECT `id` FROM `tableName`)
$table->where('id', $explorer->table($tableName));

// WHERE `id` IN (SELECT `col` FROM `tableName`)
$table->where('id', $explorer->table($tableName)->select('col'));

Bedingungen können Sie auch als Array übergeben, dessen Elemente mit AND verknüpft werden:

// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`)
$table->where([
	'price_final < price_original',
	'stock_count > min_stock',
]);

Im Array können Sie Schlüssel-Wert-Paare verwenden, und Nette wählt wieder automatisch die richtigen Operatoren:

// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3))
$table->where([
	'status' => 'active',
	'id' => [1, 2, 3],
]);

Im Array können Sie SQL-Ausdrücke mit Fragezeichen-Platzhaltern und mehreren Parametern kombinieren. Dies ist geeignet für komplexe Bedingungen mit genau definierten Operatoren:

// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5)
$table->where([
	'age > ?' => 18,
	'ROUND(score, ?) > ?' => [2, 75.5], // zwei Parameter als Array übergeben
]);

Mehrfache Aufrufe von where() verknüpfen Bedingungen automatisch mit AND.

whereOr (array $parameters)static

Fügt ähnlich wie where() Bedingungen hinzu, jedoch mit dem Unterschied, dass sie mit OR verknüpft werden:

// WHERE (`status` = 'active') OR (`deleted` = 1)
$table->whereOr([
	'status' => 'active',
	'deleted' => true,
]);

Auch hier können Sie komplexere Ausdrücke verwenden:

// WHERE (`price` > 1000) OR (`price_with_tax` > 1500)
$table->whereOr([
	'price > ?' => 1000,
	'price_with_tax > ?' => 1500,
]);

wherePrimary (mixed $key)static

Fügt eine Bedingung für den Primärschlüssel der Tabelle hinzu:

// WHERE `id` = 123
$table->wherePrimary(123);

// WHERE `id` IN (1, 2, 3)
$table->wherePrimary([1, 2, 3]);

Wenn die Tabelle einen zusammengesetzten Primärschlüssel hat (z. B. foo_id, bar_id), übergeben Sie ihn als Array:

// WHERE `foo_id` = 1 AND `bar_id` = 5
$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch();

// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3))
$table->wherePrimary([
	['foo_id' => 1, 'bar_id' => 5],
	['foo_id' => 2, 'bar_id' => 3],
])->fetchAll();

order (string $columns, …$parameters)static

Bestimmt die Reihenfolge, in der die Zeilen zurückgegeben werden. Sie können nach einer oder mehreren Spalten sortieren, in aufsteigender oder absteigender Reihenfolge oder nach einem eigenen Ausdruck:

$table->order('created');                   // ORDER BY `created`
$table->order('created DESC');              // ORDER BY `created` DESC
$table->order('priority DESC, created');    // ORDER BY `priority` DESC, `created`
$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC

select (string $columns, …$parameters)static

Spezifiziert die Spalten, die aus der Datenbank zurückgegeben werden sollen. Standardmäßig gibt Nette Database Explorer nur die Spalten zurück, die tatsächlich im Code verwendet werden. Die Methode select() verwenden Sie daher in Fällen, in denen Sie spezifische Ausdrücke zurückgeben müssen:

// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date`
$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y');

Aliase, die mit AS definiert wurden, sind dann als Eigenschaften des ActiveRow-Objekts verfügbar:

foreach ($table as $row) {
	echo $row->formatted_date;   // Zugriff auf den Alias
}

limit (?int $limit, ?int $offset = null)static

Begrenzt die Anzahl der zurückgegebenen Zeilen (LIMIT) und ermöglicht optional die Einstellung eines Offsets:

$table->limit(10);        // LIMIT 10 (gibt die ersten 10 Zeilen zurück)
$table->limit(10, 20);    // LIMIT 10 OFFSET 20

Für die Paginierung ist es besser, die Methode page() zu verwenden.

page (int $page, int $itemsPerPage, &$numOfPages = null)static

Erleichtert die Paginierung von Ergebnissen. Akzeptiert die Seitennummer (beginnend bei 1) und die Anzahl der Elemente pro Seite. Optional kann eine Referenz auf eine Variable übergeben werden, in der die Gesamtzahl der Seiten gespeichert wird:

$numOfPages = null;
$table->page(page: 3, itemsPerPage: 10, $numOfPages);
echo "Gesamtzahl der Seiten: $numOfPages";

group (string $columns, …$parameters)static

Gruppiert Zeilen nach den angegebenen Spalten (GROUP BY). Wird normalerweise in Verbindung mit Aggregationsfunktionen verwendet:

// Zählt die Anzahl der Produkte in jeder Kategorie
$table->select('category_id, COUNT(*) AS count')
	->group('category_id');

having (string $having, …$parameters)static

Legt eine Bedingung zur Filterung gruppierter Zeilen fest (HAVING). Kann in Verbindung mit der Methode group() und Aggregationsfunktionen verwendet werden:

// Findet Kategorien mit mehr als 100 Produkten
$table->select('category_id, COUNT(*) AS count')
	->group('category_id')
	->having('count > ?', 100);

Daten lesen

Zum Lesen von Daten aus der Datenbank stehen Ihnen mehrere nützliche Methoden zur Verfügung:

foreach ($table as $key => $row) Iteriert über alle Zeilen, $key ist der Wert des Primärschlüssels, $row ist ein ActiveRow-Objekt
$row = $table->get($key) Gibt eine Zeile nach dem Primärschlüssel zurück
$row = $table->fetch() Gibt die aktuelle Zeile zurück und bewegt den Zeiger zur nächsten
$array = $table->fetchPairs() Erstellt ein assoziatives Array aus den Ergebnissen
$array = $table->fetchAll() Gibt alle Zeilen als Array zurück
count($table) Gibt die Anzahl der Zeilen im Selection-Objekt zurück

Das Objekt ActiveRow ist nur zum Lesen bestimmt. Das bedeutet, dass die Werte seiner Eigenschaften nicht geändert werden können. Diese Einschränkung gewährleistet die Datenkonsistenz und verhindert unerwartete Nebeneffekte. Daten werden aus der Datenbank geladen, und jede Änderung sollte explizit und kontrolliert erfolgen.

foreach – Iteration über alle Zeilen

Der einfachste Weg, eine Abfrage auszuführen und Zeilen zu erhalten, ist die Iteration in einer foreach-Schleife. Sie startet automatisch die SQL-Abfrage.

$books = $explorer->table('book');
foreach ($books as $key => $book) {
	// $key ist der Wert des Primärschlüssels, $book ist ActiveRow
	echo "$book->title ({$book->author->name})";
}

get ($key): ?ActiveRow

Führt eine SQL-Abfrage aus und gibt eine Zeile nach dem Primärschlüssel zurück, oder null, wenn sie nicht existiert.

$book = $explorer->table('book')->get(123);  // gibt ActiveRow mit ID 123 oder null zurück
if ($book) {
	echo $book->title;
}

fetch(): ?ActiveRow

Gibt eine Zeile zurück und bewegt den internen Zeiger zur nächsten. Wenn keine weiteren Zeilen mehr existieren, gibt sie null zurück.

$books = $explorer->table('book');
while ($book = $books->fetch()) {
	$this->processBook($book);
}

fetchPairs (string|int|null $key = null, string|int|null $value = null)array

Gibt Ergebnisse als assoziatives Array zurück. Das erste Argument bestimmt den Namen der Spalte, die als Schlüssel im Array verwendet wird, das zweite Argument bestimmt den Namen der Spalte, die als Wert verwendet wird:

$authors = $explorer->table('author')->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]

Wenn nur der erste Parameter angegeben wird, ist der Wert die gesamte Zeile, also das ActiveRow-Objekt:

$authors = $explorer->table('author')->fetchPairs('id');
// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...]

Bei doppelten Schlüsseln wird der Wert aus der letzten Zeile verwendet. Bei Verwendung von null als Schlüssel wird das Array numerisch von Null indiziert (dann treten keine Kollisionen auf):

$authors = $explorer->table('author')->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]

fetchPairs (Closure $callback)array

Alternativ können Sie als Parameter einen Callback angeben, der für jede Zeile entweder den Wert selbst oder ein Schlüssel-Wert-Paar zurückgibt.

$titles = $explorer->table('book')
	->fetchPairs(fn($row) => "$row->title ({$row->author->name})");
// ['Erstes Buch (Jan Novák)', ...]

// Der Callback kann auch ein Array mit einem Schlüssel-Wert-Paar zurückgeben:
$titles = $explorer->table('book')
	->fetchPairs(fn($row) => [$row->title, $row->author->name]);
// ['Erstes Buch' => 'Jan Novák', ...]

fetchAll(): array

Gibt alle Zeilen als assoziatives Array von ActiveRow-Objekten zurück, wobei die Schlüssel die Werte der Primärschlüssel sind.

$allBooks = $explorer->table('book')->fetchAll();
// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...]

count(): int

Die Methode count() ohne Parameter gibt die Anzahl der Zeilen im Selection-Objekt zurück:

$table->where('category', 1);
$count = $table->count();
$count = count($table); // Alternative

Achtung: count() mit einem Parameter führt eine Aggregationsfunktion COUNT() in der Datenbank aus, siehe unten.

ActiveRow::toArray(): array

Konvertiert das ActiveRow-Objekt in ein assoziatives Array, wobei die Schlüssel die Spaltennamen und die Werte die entsprechenden Daten sind.

$book = $explorer->table('book')->get(1);
$bookArray = $book->toArray();
// $bookArray wird sein: ['id' => 1, 'title' => '...', 'author_id' => ..., ...]

Aggregation

Die Klasse Selection bietet Methoden zur einfachen Durchführung von Aggregationsfunktionen (COUNT, SUM, MIN, MAX, AVG usw.).

count($expr) Zählt die Anzahl der Zeilen
min($expr) Gibt den Minimalwert in der Spalte zurück
max($expr) Gibt den Maximalwert in der Spalte zurück
sum($expr) Gibt die Summe der Werte in der Spalte zurück
aggregation($function) Ermöglicht die Durchführung einer beliebigen Aggregationsfunktion. Z. B. AVG()GROUP_CONCAT()

count (string $expr): int

Führt eine SQL-Abfrage mit der COUNT-Funktion aus und gibt das Ergebnis zurück. Die Methode wird verwendet, um festzustellen, wie viele Zeilen einer bestimmten Bedingung entsprechen:

$count = $table->count('*');                 // SELECT COUNT(*) FROM `table`
$count = $table->count('DISTINCT column');   // SELECT COUNT(DISTINCT `column`) FROM `table`

Achtung: count() ohne Parameter gibt nur die Anzahl der Zeilen im Selection-Objekt zurück.

min (string $expr) und max(string $expr)

Die Methoden min() und max() geben den minimalen bzw. maximalen Wert in der angegebenen Spalte oder dem Ausdruck zurück:

// SELECT MAX(`price`) FROM `products` WHERE `active` = 1
$maxPrice = $products->where('active', true)
	->max('price');

sum (string $expr)

Gibt die Summe der Werte in der angegebenen Spalte oder dem Ausdruck zurück:

// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1
$totalPrice = $products->where('active', true)
	->sum('price * items_in_stock');

aggregation (string $function, ?string $groupFunction = null)

Ermöglicht die Durchführung einer beliebigen Aggregationsfunktion.

// Durchschnittlicher Preis der Produkte in einer Kategorie
$avgPrice = $products->where('category_id', 1)
	->aggregation('AVG(price)');

// Verbindet Produkt-Tags zu einer Zeichenkette
$tags = $products->where('id', 1)
	->aggregation('GROUP_CONCAT(tag.name) AS tags')
	->fetch()
	->tags;

Wenn wir Ergebnisse aggregieren müssen, die bereits selbst aus einer Aggregationsfunktion und Gruppierung hervorgegangen sind (z. B. SUM(wert) über gruppierte Zeilen), geben wir als zweites Argument die Aggregationsfunktion an, die auf diese Zwischenergebnisse angewendet werden soll:

// Berechnet den Gesamtpreis der Produkte auf Lager für einzelne Kategorien und summiert dann diese Preise.
$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total')
	->group('category_id')
	->aggregation('SUM(category_total)', 'SUM');

In diesem Beispiel berechnen wir zuerst den Gesamtpreis der Produkte in jeder Kategorie (SUM(price * stock) AS category_total) und gruppieren die Ergebnisse nach category_id. Dann verwenden wir aggregation('SUM(category_total)', 'SUM'), um diese Zwischensummen category_total zu addieren. Das zweite Argument 'SUM' gibt an, dass die SUM-Funktion auf die Zwischenergebnisse angewendet werden soll.

Insert, Update & Delete

Nette Database Explorer vereinfacht das Einfügen, Aktualisieren und Löschen von Daten. Alle genannten Methoden werfen im Fehlerfall eine Nette\Database\DriverException.

Selection::insert (iterable $data)

Fügt neue Datensätze in die Tabelle ein.

Einfügen eines einzelnen Datensatzes:

Den neuen Datensatz übergeben wir als assoziatives Array oder iterable Objekt (zum Beispiel ArrayHash, das in Formularen verwendet wird), wobei die Schlüssel den Spaltennamen in der Tabelle entsprechen.

Wenn die Tabelle einen definierten Primärschlüssel hat, gibt die Methode ein ActiveRow-Objekt zurück, das aus der Datenbank neu geladen wird, um eventuelle Änderungen auf Datenbankebene (Trigger, Standardwerte von Spalten, Berechnungen von Auto-Increment-Spalten) zu berücksichtigen. Dadurch wird die Datenkonsistenz gewährleistet und das Objekt enthält immer die aktuellen Daten aus der Datenbank. Wenn es keinen eindeutigen Primärschlüssel gibt, gibt sie die übergebenen Daten in Form eines Arrays zurück.

$row = $explorer->table('users')->insert([
	'name' => 'John Doe',
	'email' => 'john.doe@example.com',
]);
// $row ist eine Instanz von ActiveRow und enthält die vollständigen Daten der eingefügten Zeile,
// einschließlich der automatisch generierten ID und eventueller durch Trigger vorgenommener Änderungen
echo $row->id; // Gibt die ID des neu eingefügten Benutzers aus
echo $row->created_at; // Gibt die Erstellungszeit aus, falls sie durch einen Trigger gesetzt wurde

Einfügen mehrerer Datensätze auf einmal:

Die Methode insert() ermöglicht das Einfügen mehrerer Datensätze mit einer einzigen SQL-Abfrage. In diesem Fall gibt sie die Anzahl der eingefügten Zeilen zurück.

$insertedRows = $explorer->table('users')->insert([
	[
		'name' => 'John',
		'year' => 1994,
	],
	[
		'name' => 'Jack',
		'year' => 1995,
	],
]);
// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995)
// $insertedRows wird 2 sein

Als Parameter kann auch ein Selection-Objekt mit einer Datenauswahl übergeben werden.

$newUsers = $explorer->table('potential_users')
	->where('approved', 1)
	->select('name, email');

$insertedRows = $explorer->table('users')->insert($newUsers);

Einfügen spezieller Werte:

Als Werte können wir auch Dateien, DateTime-Objekte oder SQL-Literale übergeben:

$explorer->table('users')->insert([
	'name' => 'John',
	'created_at' => new DateTime,           // konvertiert in Datenbankformat
	'avatar' => fopen('image.jpg', 'rb'),   // fügt binären Inhalt der Datei ein
	'uuid' => $explorer::literal('UUID()'), // ruft die Funktion UUID() auf
]);

Selection::update (iterable $data)int

Aktualisiert Zeilen in der Tabelle gemäß dem angegebenen Filter. Gibt die Anzahl der tatsächlich geänderten Zeilen zurück.

Die zu ändernden Spalten übergeben wir als assoziatives Array oder iterable Objekt (zum Beispiel ArrayHash, das in Formularen verwendet wird), wobei die Schlüssel den Spaltennamen in der Tabelle entsprechen:

$affected = $explorer->table('users')
	->where('id', 10)
	->update([
		'name' => 'John Smith',
		'year' => 1994,
	]);
// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10

Zur Änderung numerischer Werte können Sie die Operatoren += und -= verwenden:

$explorer->table('users')
	->where('id', 10)
	->update([
		'points+=' => 1,  // erhöht den Wert der Spalte 'points' um 1
		'coins-=' => 1,   // verringert den Wert der Spalte 'coins' um 1
	]);
// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10

Selection::delete(): int

Löscht Zeilen aus der Tabelle gemäß dem angegebenen Filter. Gibt die Anzahl der gelöschten Zeilen zurück.

$count = $explorer->table('users')
	->where('id', 10)
	->delete();
// DELETE FROM `users` WHERE `id` = 10

Vergessen Sie beim Aufrufen von update() und delete() nicht, mit where() die Zeilen anzugeben, die geändert bzw. gelöscht werden sollen. Wenn Sie where() nicht verwenden, wird die Operation auf die gesamte Tabelle angewendet!

ActiveRow::update (iterable $data)bool

Aktualisiert Daten in der Datenbankzeile, die durch das ActiveRow-Objekt repräsentiert wird. Als Parameter akzeptiert es ein Iterable mit Daten, die aktualisiert werden sollen (Schlüssel sind Spaltennamen). Zur Änderung numerischer Werte können Sie die Operatoren += und -= verwenden:

Nach der Durchführung der Aktualisierung wird ActiveRow automatisch aus der Datenbank neu geladen, um eventuelle Änderungen auf Datenbankebene (z. B. Trigger) zu berücksichtigen. Die Methode gibt true zurück, nur wenn tatsächlich Daten geändert wurden.

$article = $explorer->table('article')->get(1);
$article->update([
	'views += 1',  // erhöhen die Anzahl der Ansichten
]);
echo $article->views; // Gibt die aktuelle Anzahl der Ansichten aus

Diese Methode aktualisiert nur eine bestimmte Zeile in der Datenbank. Für die Massenaktualisierung mehrerer Zeilen verwenden Sie die Methode Selection::update().

ActiveRow::delete()

Löscht die Zeile aus der Datenbank, die durch das ActiveRow-Objekt repräsentiert wird.

$book = $explorer->table('book')->get(1);
$book->delete(); // Löscht das Buch mit der ID 1

Diese Methode löscht nur eine bestimmte Zeile in der Datenbank. Für das Massenlöschen mehrerer Zeilen verwenden Sie die Methode Selection::delete().

Beziehungen zwischen Tabellen

In relationalen Datenbanken sind Daten auf mehrere Tabellen verteilt und über Fremdschlüssel miteinander verbunden. Nette Database Explorer bietet eine revolutionäre Möglichkeit, mit diesen Beziehungen zu arbeiten – ohne JOIN-Abfragen zu schreiben und ohne die Notwendigkeit, etwas zu konfigurieren oder zu generieren.

Zur Veranschaulichung der Arbeit mit Beziehungen verwenden wir das Beispiel einer Buchdatenbank (finden Sie auf GitHub). In der Datenbank haben wir folgende Tabellen:

  • author – Schriftsteller und Übersetzer (Spalten id, name, web, born)
  • book – Bücher (Spalten id, author_id, translator_id, title, sequel_id)
  • tag – Schlagwörter (Spalten id, name)
  • book_tag – Verknüpfungstabelle zwischen Büchern und Schlagwörtern (Spalten book_id, tag_id)
Datenbankstruktur, die in den Beispielen verwendet wird

In unserem Beispiel der Buchdatenbank finden wir verschiedene Arten von Beziehungen (obwohl das Modell im Vergleich zur Realität vereinfacht ist):

  • One-to-many 1:N – jedes Buch hat einen Autor, ein Autor kann mehrere Bücher schreiben
  • Zero-to-many 0:N – ein Buch kann einen Übersetzer haben, ein Übersetzer kann mehrere Bücher übersetzen
  • Zero-to-one 0:1 – ein Buch kann einen weiteren Teil haben
  • Many-to-many M:N – ein Buch kann mehrere Schlagwörter haben und ein Schlagwort kann mehreren Büchern zugeordnet sein

In diesen Beziehungen gibt es immer eine übergeordnete und eine untergeordnete Tabelle. Zum Beispiel ist in der Beziehung zwischen Autor und Buch die Tabelle author übergeordnet und book untergeordnet – man kann sich vorstellen, dass ein Buch immer einem Autor “gehört”. Dies spiegelt sich auch in der Datenbankstruktur wider: Die untergeordnete Tabelle book enthält den Fremdschlüssel author_id, der auf die übergeordnete Tabelle author verweist.

Wenn wir Bücher einschließlich der Namen ihrer Autoren auflisten müssen, haben wir zwei Möglichkeiten. Entweder erhalten wir die Daten mit einer einzigen SQL-Abfrage mittels LEFT JOIN:

SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id

Oder wir laden die Daten in zwei Schritten – zuerst die Bücher und dann ihre Autoren – und fügen sie dann in PHP zusammen:

SELECT * FROM book;
SELECT * FROM author WHERE id IN (1, 2, 3);  -- IDs der Autoren der abgerufenen Bücher

Der zweite Ansatz ist tatsächlich effizienter, auch wenn das überraschend sein mag. Die Daten werden nur einmal pro Tabelle geladen und können besser im Cache genutzt werden. Genau auf diese Weise arbeitet Nette Database Explorer – alles wird unter der Haube gelöst und Ihnen wird eine elegante API geboten:

$books = $explorer->table('book');
foreach ($books as $book) {
	echo 'Titel: ' . $book->title;
	echo 'geschrieben von: ' . $book->author->name; // $book->author ist der Datensatz aus der Tabelle 'author'
	echo 'übersetzt von: ' . $book->translator?->name;
}

Zugriff auf die übergeordnete Tabelle

Der Zugriff auf die übergeordnete Tabelle ist unkompliziert. Es handelt sich um Beziehungen wie ein Buch hat einen Autor oder ein Buch kann einen Übersetzer haben. Den zugehörigen Datensatz erhalten wir über eine Eigenschaft des ActiveRow-Objekts. Der Name der Eigenschaft entspricht dem Namen der Spalte mit dem Fremdschlüssel, jedoch ohne das Suffix _id:

$book = $explorer->table('book')->get(1);
echo $book->author->name;      // findet den Autor über die Spalte author_id
echo $book->translator?->name; // findet den Übersetzer über translator_id (nullsafe)

Wenn Sie auf die Eigenschaft $book->author zugreifen, sucht der Explorer in der Tabelle book nach einer Spalte, deren Name auf author endet und auf _id endet (also author_id). Anhand des Wertes in dieser Spalte lädt er den entsprechenden Datensatz aus der Tabelle author und gibt ihn als ActiveRow zurück. Ähnlich funktioniert auch $book->translator, das die Spalte translator_id verwendet. Da die Spalte translator_id NULL enthalten kann, verwenden wir im Code den Nullsafe-Operator ?->.

Einen alternativen Weg bietet die Methode ref(), die zwei Argumente akzeptiert: den Namen der Zieltabelle und optional den Namen der Verbindungspalte. Sie gibt eine Instanz von ActiveRow oder null zurück:

echo $book->ref('author', 'author_id')->name;      // Beziehung zum Autor
echo $book->ref('author', 'translator_id')->name;  // Beziehung zum Übersetzer

Die Methode ref() ist nützlich, wenn der Name der Beziehung nicht eindeutig aus dem Spaltennamen abgeleitet werden kann oder wenn Sie eine explizite Steuerung bevorzugen.

Der Explorer optimiert Datenbankabfragen automatisch. Wenn Sie Bücher in einer Schleife durchlaufen und auf ihre zugehörigen Datensätze (Autoren, Übersetzer) zugreifen, generiert der Explorer nicht für jedes Buch eine separate Abfrage. Stattdessen führt er nur eine SELECT-Abfrage pro referenzierter Tabelle durch, was die Datenbanklast erheblich reduziert. Zum Beispiel:

$books = $explorer->table('book');
foreach ($books as $book) {
	echo $book->title . ': ';
	echo $book->author->name;
	echo $book->translator?->name;
}

Dieser Code führt nur diese drei blitzschnellen Abfragen an die Datenbank aus:

SELECT * FROM `book`;
SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id aus der Spalte author_id der ausgewählten Bücher
SELECT * FROM `author` WHERE (`id` IN (2, 3));    -- id aus der Spalte translator_id der ausgewählten Bücher

Die Logik zur Erkennung der Beziehung basiert auf den Conventions. Wir empfehlen die Verwendung von DiscoveredConventions, die Fremdschlüssel analysiert und die einfache Arbeit mit bestehenden Beziehungen zwischen Tabellen ermöglicht.

Zugriff auf die untergeordnete Tabelle

Der Zugriff auf die untergeordnete Tabelle funktioniert in umgekehrter Richtung. Nun fragen wir welche Bücher hat dieser Autor geschrieben oder welche Bücher hat dieser Übersetzer übersetzt. Für diesen Abfragetyp verwenden wir die Methode related(), die eine Selection mit den zugehörigen Datensätzen zurückgibt. Sehen wir uns ein Beispiel an:

$author = $explorer->table('author')->get(1);

// Gibt alle Bücher des Autors aus
foreach ($author->related('book.author_id') as $book) {
	echo "Geschrieben: $book->title";
}

// Gibt alle Bücher aus, die der Autor übersetzt hat
foreach ($author->related('book.translator_id') as $book) {
	echo "Übersetzt: $book->title";
}

Die Methode related() akzeptiert die Beschreibung der Beziehung als ein Argument mit Punktnotation (Zieltabelle.Fremdschlüsselspalte) oder als zwei separate Argumente (Zieltabelle, Fremdschlüsselspalte):

$author->related('book.translator_id');  // ein Argument
$author->related('book', 'translator_id'); // zwei Argumente

Der Explorer kann die korrekte Verbindungspalte oft automatisch erkennen, wenn sie den Konventionen folgt (z.B. zieltabelle_id). In diesem Fall würde die Verbindung über die Spalte book.author_id erfolgen, da der Name der Quelltabelle author ist und die Zieltabelle book heißt:

$author->related('book');  // verwendet book.author_id

Wenn mehrere mögliche Beziehungen bestehen oder die Spalte nicht den Konventionen entspricht, wirft der Explorer eine AmbiguousReferenceKeyException.

Die Methode related() können Sie natürlich auch beim Durchlaufen mehrerer Datensätze in einer Schleife verwenden, und der Explorer optimiert auch in diesem Fall die Abfragen automatisch:

$authors = $explorer->table('author');
foreach ($authors as $author) {
	echo $author->name . ' hat geschrieben:';
	foreach ($author->related('book') as $book) {
		echo $book->title;
	}
}

Dieser Code generiert nur zwei blitzschnelle SQL-Abfragen:

SELECT * FROM `author`;
SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id der ausgewählten Autoren

Many-to-Many-Beziehung

Für eine Many-to-many-Beziehung (M:N) ist die Existenz einer Verknüpfungstabelle erforderlich (in unserem Fall book_tag), die zwei Spalten mit Fremdschlüsseln (book_id, tag_id) enthält. Jede dieser Spalten verweist auf den Primärschlüssel einer der verbundenen Tabellen. Um die zugehörigen Daten zu erhalten, holen wir zuerst die Datensätze aus der Verknüpfungstabelle mit related('book_tag') und fahren dann mit den Zieldaten fort:

$book = $explorer->table('book')->get(1);
// gibt die Namen der dem Buch zugewiesenen Tags aus
foreach ($book->related('book_tag') as $bookTag) {
	echo $bookTag->tag->name;  // gibt den Namen des Tags über die Verknüpfungstabelle aus
}

$tag = $explorer->table('tag')->get(1);
// oder umgekehrt: gibt die Namen der mit diesem Tag gekennzeichneten Bücher aus
foreach ($tag->related('book_tag') as $bookTag) {
	echo $bookTag->book->title; // gibt den Namen des Buches aus
}

Der Explorer optimiert die SQL-Abfragen wieder in eine effiziente Form:

SELECT * FROM `book`;
SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...));  -- id der ausgewählten Bücher
SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...));                 -- id der in book_tag gefundenen Tags

Abfragen über verwandte Tabellen

In den Methoden where(), select(), order() und group() können Sie spezielle Notationen verwenden, um auf Spalten aus verbundenen Tabellen zuzugreifen. Der Explorer erstellt automatisch die erforderlichen JOINs.

Punktnotation (referenzierte_tabelle.spalte) wird für Many-to-One- oder One-to-One-Beziehungen verwendet (Zugriff auf die übergeordnete Tabelle):

$books = $explorer->table('book');

// Findet Bücher, deren Autorname mit 'Jon' beginnt
$books->where('author.name LIKE ?', 'Jon%');

// Sortiert Bücher nach Autorennamen absteigend
$books->order('author.name DESC');

// Gibt den Buchtitel und den Autorennamen aus
$books->select('book.title, author.name');

Doppelpunktnotation (:untergeordnete_tabelle.spalte) wird für die 1:N-Beziehung aus Sicht der übergeordneten Tabelle verwendet:

$authors = $explorer->table('author');

// Findet Autoren, die ein Buch mit 'PHP' im Titel geschrieben haben
$authors->where(':book.title LIKE ?', '%PHP%');

// Zählt die Anzahl der Bücher für jeden Autor
$authors->select('*, COUNT(:book.id) AS book_count')
	->group('author.id');

Im obigen Beispiel mit der Doppelpunktnotation (:book.title) ist die Spalte mit dem Fremdschlüssel nicht angegeben. Der Explorer erkennt automatisch die richtige Spalte anhand des Namens der übergeordneten Tabelle. In diesem Fall wird über die Spalte book.author_id verbunden, da der Name der Quelltabelle author ist. Wenn mehrere mögliche Verbindungen bestehen, wirft der Explorer eine AmbiguousReferenceKeyException.

Die Verbindungspalte kann explizit in Klammern angegeben werden:

// Findet Autoren, die ein Buch mit 'PHP' im Titel übersetzt haben
$authors->where(':book(translator_id).title LIKE ?', '%PHP%');

Notationen können für den Zugriff über mehrere Tabellen verkettet werden:

// Findet Autoren von Büchern, die mit dem Tag 'PHP' gekennzeichnet sind
$authors->where(':book:book_tag.tag.name', 'PHP')
	->group('author.id');

Erweiterung der Bedingungen für JOIN

Die Methode joinWhere() erweitert die Bedingungen, die in der ON-Klausel beim Verknüpfen von Tabellen in SQL angegeben werden.

Nehmen wir an, wir möchten Bücher finden, die von einem bestimmten Übersetzer übersetzt wurden, und wir möchten die Bedingung direkt in den JOIN einbauen:

// Findet Bücher, die vom Übersetzer namens 'David' übersetzt wurden
$books = $explorer->table('book')
	->joinWhere('translator', 'translator.name', 'David');
// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David')

In der joinWhere()-Bedingung können wir dieselben Konstrukte wie in der where()-Methode verwenden – Operatoren, Fragezeichen-Platzhalter, Wertearrays oder SQL-Ausdrücke.

Für komplexere Abfragen mit mehreren JOINs können wir Tabellenaliase definieren:

$tags = $explorer->table('tag')
	->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950)
	->alias(':book_tag.book.author', 'book_author');
// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id`
// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id`
// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id`
//    AND (`book_author`.`born` < 1950)

Beachten Sie den Unterschied: Während die where()-Methode Bedingungen zur WHERE-Klausel hinzufügt, erweitert die joinWhere()-Methode die Bedingungen in der ON-Klausel beim Verknüpfen von Tabellen.

Version: 4.0