Databaser
Führen Sie Abfragen aus einem Modul-Controller aus. Einstiegspunkte: DB::module('RAW') (Arrays) oder DB::module('ORM') (Entity/Collection). Lesen Sie Zeilen mit all() und nehmen Sie anschließend $rows[0] ?? null, wenn Sie eine einzelne Zeile benötigen. Übergeben Sie an execute($ok, $err) immer beide Callbacks — ohne Fehler-Callback löst ein Fehlschlag eine Exception aus. Schemaänderungen gehören in die Installation.php des Moduls. Modultabellen folgen dem Namensmuster {modulename}_*.
1. Einführung
1.1. Was ist Databaser?
Databaser ist eine robuste, flexible Bibliothek für die Datenbankarbeit und fest in das DotApp PHP Framework 2.0 integriert. Sie bietet einen einfachen, sicheren und effizienten Weg, sowohl grundlegende Operationen als auch fortgeschrittene Abfragen auszuführen. Databaser nimmt Ihnen das Schreiben von Raw-SQL ab (erlaubt es aber weiterhin) und stellt einen modernen Ansatz über einen intuitiven QueryBuilder sowie eine optionale ORM-Schicht (Object-Relational Mapping) bereit. Das Ziel ist, die Datenbankarbeit für Entwickler zu erleichtern, ohne Flexibilität oder Leistung aufzugeben.
Databaser ist Teil des Kerns von DotApp PHP Framework 2.0, Sie installieren oder konfigurieren es daher nicht separat. Sobald Sie die Datenbankverbindungen im Framework definiert haben, ist es einsatzbereit.
1.2. Wichtige Funktionen
Databaser bietet einen umfangreichen Funktionsumfang für die Arbeit mit Datenbanken:
- Einfache SQL-Konstruktion und -Ausführung: Prepared Statements halten die Datenarbeit sicher und unkompliziert.
- Mehrere Datenbankverbindungen: Definieren Sie Datenbanken und wechseln Sie zwischen ihnen mit gespeicherten Zugangsdaten.
- Eigene Treiber: Neben den Standardtreibern (MySQLi und PDO) können Sie eigene Datenbanktreiber implementieren.
- Optionales ORM: Verfügbar für MySQLi und PDO, mit den Klassen
Entity(eine einzelne Zeile) undCollection(eine Menge von Zeilen), die Daten als Objekte behandeln. - Lazy Loading und Beziehungen: Laden Sie verwandte Zeilen über Beziehungsmethoden einer Entity (
$user->hasMany('shop_posts', 'user_id')). - Erweiterte Beziehungen: Passen Sie den
QueryBuilderinnerhalb einer Beziehung an (zum Beispiel mitlimit,orderBy,where) über einen optionalen Callback-Parameter. - Validierung: Das ORM prüft Attribute während
save(). Iterieren Sie über eine Collection und speichern Sie jede Entity einzeln. - Integrierter QueryBuilder: Ein intuitiver QueryBuilder von einfachen SELECTs bis zu komplexen JOINs und Subqueries.
- SUCCESS- und ERROR-Callbacks: Jede Operation liefert Ergebnisse und Debug-Daten über Callbacks, was die Erfolgs- und Fehlerbehandlung vereinfacht.
- Transaktionsunterstützung: Unkomplizierte Transaktionsbehandlung mit automatischem Commit oder Rollback.
1.3. RAW vs. ORM: Wann welchen Ansatz verwenden?
Databaser bietet zwei wesentliche Wege, mit Daten zu arbeiten: RAW und ORM. Die Wahl hängt von den Anforderungen Ihres Projekts ab:
- RAW-MODUS:
- Abfrageergebnisse werden direkt zurückgegeben (zum Beispiel Arrays oder Datenbank-Ressourcen).
- Ideal für einfache Anwendungen, schnelle Prototypen oder Situationen, in denen Sie die volle Kontrolle über SQL benötigen.
- Beispiel: Ein einfaches SELECT, um Benutzer ohne Objekt-Mapping aufzulisten.
- Vorteile: Schnelle Ausführung, minimaler Overhead, volle Flexibilität beim Schreiben von Abfragen.
- ORM-MODUS:
- Daten werden auf Objekte abgebildet (
Entityfür eine Zeile,Collectionfür viele Zeilen), sodass Sie mit Daten als Objekten arbeiten. - Geeignet für komplexe Anwendungen, die Tabellenbeziehungen, Datenvalidierung oder objektorientierte Zeilenbehandlung benötigen.
- Beispiel: Verwalten von Benutzern und ihren Beiträgen (eine
HasMany-Beziehung) und automatisches Speichern von Änderungen. - Vorteile: Objektorientierter Ansatz, Beziehungsunterstützung, unkomplizierte Datenbehandlung.
- Daten werden auf Objekte abgebildet (
Wann sollten Sie welchen Ansatz verwenden?
- Wählen Sie RAW, wenn Sie hohe Leistung und einfache Abfragen benötigen.
- Wählen Sie ORM, wenn Sie mit komplexen Datenstrukturen arbeiten und eine klarere objektorientierte Lösung wünschen.
1.4. Unterstützung für Datenbanktreiber (MySQLi, PDO)
Databaser unterstützt zwei wesentliche Datenbanktreiber, die die häufigsten Anforderungen abdecken:
MySQLi
- QueryBuilder und ORM.
- Gut geeignet für Projekte, die bereits MySQLi nutzen, oder für einfachere Anwendungen mit MySQL-Datenbanken.
- Unterstützt alle Funktionen von
QueryBuilderund ORM.
PDO
- Unterstützung für mehrere Datenbanken (MySQL, PostgreSQL, SQLite und andere).
- Flexibler dank eines dynamischen DSN (Data Source Name), mit dem Sie sich mit unterschiedlichen Datenbanktypen verbinden können.
- Unterstützt ebenfalls
QueryBuilderund ORM.
Beide Treiber sind austauschbar ausgelegt — Code für den einen Treiber funktioniert ohne größere Änderungen mit dem anderen, sofern Sie die Besonderheiten des Ziel-Datenbanksystems beachten.
1.5. Integrierter QueryBuilder
Der QueryBuilder ist das Herzstück von Databaser. Er lässt Sie SQL mit verkettbaren Methoden aufbauen, sodass sichere, lesbare Abfragen leichter zu schreiben sind. Er unterstützt:
- Grundoperationen:
select,insert,update,delete. - Bedingungen:
where,orWhere, verschachtelte Bedingungen über eine Closure. - Tabellen-Joins:
join,leftJoin. - Aggregationen:
groupBy,having. - Sortierung und Limits:
orderBy,limit,offset. - Raw-Abfragen:
rawmit Fragezeichen-Platzhaltern (?) und benannten Variablen (:name). - Tabellenquelle:
from, wenn die Tabelle nicht anselect()oderdelete()übergeben wird.
Der QueryBuilder verwaltet Prepared Statements und Bindings automatisch und schützt so vor SQL-Injection. Jeder Wert in einer Abfrage (zum Beispiel in where-Bedingungen oder in Daten für insert) wird escaped und durch Platzhalter (? oder benannte Variablen :name) ersetzt. Das verringert das Risiko von Sicherheitsproblemen und hält den Code lesbarer.
1.6. Callbacks für SUCCESS und ERROR
Databaser verwendet Callbacks, um Ergebnisse und Fehler zu behandeln. Jede Operation (zum Beispiel execute(), save()) kann zwei optionale Callbacks entgegennehmen:
SUCCESS-Callback
Wird ausgeführt, wenn die Operation erfolgreich ist. Er erhält drei Parameter:
$result: Das Operationsergebnis (zum Beispiel ein Daten-Array im RAW-Modus oder ein Objekt im ORM-Modus).$db: DieDatabaser-Instanz, die Sie für weitere Abfragen verwenden können.$debug: Debug-Daten (zum Beispiel die erzeugte SQL-Abfrage und die Bindings).
ERROR-Callback
Wird ausgeführt, wenn ein Fehler auftritt. Er erhält ebenfalls drei Parameter:
$error: Ein Array mit Fehlerdetails (error — Fehlertext, errno — Fehlercode).$db: DieDatabaser-Instanz für Folgeoperationen.$debug: Debug-Daten zur Analyse des Problems.
Dieser Ansatz vereinfacht die Behandlung und lässt Sie Operationen direkt in Callbacks verketten. Wenn eine Abfrage unmittelbar eine weitere starten soll, rufen Sie $db->q() aus dem SUCCESS-Callback auf. Erfolgs- und Fehlerlogik bleiben getrennt und nachvollziehbar. Übergeben Sie an execute($ok, $err) immer beide Callbacks. Lassen Sie den ERROR-Callback weg, löst execute() bei einem Fehlschlag eine Exception aus, die Sie mit try/catch fangen können. Ist der ERROR-Callback gesetzt, läuft try/catch für diesen Fehlschlag nicht — die Fehlerbehandlung ist vollständig an den Callback delegiert.
2. Erste Schritte
2.1. Databaser installieren und konfigurieren
Databaser ist fester Bestandteil von DotApp PHP Framework 2.0, Sie installieren es daher nicht separat. Sobald das Framework in Ihrem Projekt eingerichtet ist, steht Databaser über die Facade DB:: zur Verfügung. Verwenden Sie in Modulcode DB::module('RAW') oder DB::module('ORM'). Dieses Kapitel setzt voraus, dass das Framework konfiguriert und einsatzbereit ist.
2.2. Eine Datenbankverbindung hinzufügen
Databaser kann mehrere Datenbankverbindungen hinzufügen und verwalten. Registrieren Sie sie in app/config.php mit Config::addDatabase(). Beispiel:
Config::addDatabase(
'main', // Connection name
'localhost', // Host
'root', // Username
'password123', // Password
'my_database', // Database name
'utf8mb4', // Charset
'MYSQL', // Database type
'pdo' // Driver
);
2.3. Einen Treiber wählen (MySQLi oder PDO)
Databaser unterstützt MySQLi und PDO. Treiber und Hauptdatenbank (maindb) werden in der Regel in der Konfiguration gewählt, nicht in jeder Abfrage. Verwenden Sie in den Beispielen DB::module('RAW') oder DB::module('ORM'). Die manuelle Treiberwahl bleibt fortgeschrittenen eigenen Treibern vorbehalten.
2.4. Erste Datenbankverbindung
Nachdem Sie eine Verbindung in der Konfiguration definiert haben, verwendet das Framework Treiber und Hauptverbindung automatisch. Sie können die Verbindung so prüfen:
if (DB::isConnected()) {
echo 'Database is connected.';
}
Beispiel einer ersten einfachen Abfrage:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items');
})
->execute(
function ($result, $db, $debug) {
echo "Generated query: " . $debug['query'] . "\n";
var_dump($result);
},
function ($error, $db, $debug) {
echo "Error: {$error['error']} (code: {$error['errno']})\n";
}
);
Erläuterung
DB::module('RAW'): Kanonischer Einstieg. Treiber und Standarddatenbank kommen ausapp/config.php.execute($ok, $err): Übergeben Sie immer beide Callbacks. Ohne$errlöst ein Datenbankfehler eine Exception aus.q()(Aliasqb()): Startet denQueryBuilderund definiert die Abfrage (in diesem FallSELECT * FROM shop_items).execute(): Führt die Abfrage mit Callbacks für Erfolg und Fehler aus.$result: Ein Array von Ergebnissen (im RAW-Modus).$debug: Enthält die erzeugte SQL-Abfrage und weitere Informationen.
Ausgabe (Beispiel):
Generated query: SELECT * FROM shop_items
array(2) {
[0] => array(3) {
["id"] => string(1) "1"
["name"] => string(4) "Jane"
["age"] => string(2) "25"
}
[1] => array(3) {
["id"] => string(1) "2"
["name"] => string(5) "Maria"
["age"] => string(2) "30"
}
}
3. QueryBuilder: Detaillierte Übersicht
Der QueryBuilder ist ein zentrales Werkzeug in Databaser. Er lässt Sie SQL mit verkettbaren Methoden aufbauen. Die wesentlichen Vorteile sind Einfachheit, Lesbarkeit und Sicherheit — er verwaltet Prepared Statements und Bindings automatisch und schützt so vor SQL-Injection. Dieses Kapitel behandelt die Funktionsweise, die verfügbaren Methoden und Beispiele von einfachen bis komplexen Abfragen.
3.1. Grundprinzipien des QueryBuilders
Der QueryBuilder ist ein Objekt der Klasse Dotsystems\App\Parts\QueryBuilder. Sie verwenden ihn innerhalb von q() oder qb() an der Facade DB:: (typischerweise DB::module('RAW')->q(...)). Sie bauen die Abfrage, indem Sie Methoden nacheinander aufrufen; jede Methode fügt einen Teil der SQL-Anweisung hinzu (zum Beispiel select, where, join). Schließen Sie die Abfrage mit execute() ab. Lesen Sie Zeilen mit all() und nehmen Sie $rows[0] ?? null, wenn Sie eine einzelne Zeile benötigen.
Wesentliche Eigenschaften:
- Verkettbarkeit: Methoden geben die
QueryBuilder-Instanz zurück, sodass Sie sie verketten können. - Prepared Statements: Alle Werte werden automatisch escaped und durch Platzhalter (?) ersetzt.
- Flexibilität: Raw-SQL steht über
raw()für Sonderfälle zur Verfügung. - Debugging: Nach der Ausführung enthält
$debugdas erzeugte SQL und die Bindings.
Beispiel für die grundlegende Verwendung:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('age', '>', 18);
})->execute(
function ($result, $db, $debug) {
echo $debug['query']; // "SELECT * FROM shop_items WHERE age > ?"
var_dump($debug['bindings']); // [18]
var_dump($result);
},
function ($error, $db, $debug) {
echo "Error: {$error['error']} (code: {$error['errno']})\n";
}
);
3.2. Liste der QueryBuilder-Methoden
Hier folgt eine detaillierte Übersicht der wichtigsten QueryBuilder-Methoden mit Erläuterungen und Beispielen.
3.2.1. select
Die Methode select() legt fest, welche Spalten aus welcher Tabelle gelesen werden.
Syntax: select($columns = '*', $table = null)
Parameter:
$columns: Eine Zeichenkette oder ein Array von Spalten (zum Beispiel 'id, name' oder ['id', 'name']).$table: Tabellenname (optional, wenn Siefrom()verwenden).
SQL-Äquivalent: SELECT columns FROM table
Beispiel:
$qb->select('id, name', 'shop_items');
// SQL: SELECT id, name FROM shop_items
3.2.2. insert
Die Methode insert() fügt eine neue Zeile in eine Tabelle ein.
Syntax: insert($table, array $data)
Parameter:
$table: Tabellenname.$data: Assoziatives Array der Daten (Spalte => Wert).
SQL-Äquivalent: INSERT INTO table (columns) VALUES (values)
Beispiel:
$qb->insert('shop_items', ['name' => 'Jane', 'age' => 25]);
// SQL: INSERT INTO shop_items (name, age) VALUES (?, ?)
// Bindings: ['Jane', 25]
3.2.3. update
Die Methoden update() und set() aktualisieren vorhandene Zeilen.
Syntax: update($table) + set(array $data)
Parameter:
$table: Tabellenname.$data: Assoziatives Array der aktualisierten Werte.
SQL-Äquivalent: UPDATE table SET column = value
Beispiel:
$qb->update('shop_items')->set(['age' => 26])->where('id', '=', 1);
// SQL: UPDATE shop_items SET age = ? WHERE id = ?
// Bindings: [26, 1]
3.2.4. delete
Die Methode delete() entfernt Zeilen aus einer Tabelle.
Syntax: delete($table = null)
Parameter:
$table: Tabellenname (optional, wenn sie andernorts definiert ist).
SQL-Äquivalent: DELETE FROM table
Beispiel:
$qb->delete('shop_items')->where('id', '=', 1);
// SQL: DELETE FROM shop_items WHERE id = ?
// Bindings: [1]
3.2.5. where und orWhere
Die Methoden where() und orWhere() fügen Bedingungen hinzu.
Syntax: where($column, $operator = null, $value = null, $boolean = 'AND')
Parameter:
$column: Eine Spalte oder eine Closure für verschachtelte Bedingungen.$operator: Operator (zum Beispiel =, >, <).$value: Ein Wert oder eine Closure für eine Subquery.$boolean: Logische Verknüpfung (Standard AND).
SQL-Äquivalent: WHERE column operator value
Beispiel:
$qb->select('*', 'shop_items')
->where('age', '>', 18)
->orWhere('name', '=', 'Jane');
// SQL: SELECT * FROM shop_items WHERE age > ? OR name = ?
// Bindings: [18, 'Jane']
3.2.6. join (INNER, LEFT)
Die Methoden join() und leftJoin() verbinden Tabellen.
Syntax: join($table, $first, $operator, $second, $type = 'INNER')
Parameter:
$table: Eine Tabelle oder eine Subquery (QueryBuilder).$first: Erste Spalte der Join-Bedingung.$operator: Join-Operator.$second: Zweite Spalte der Join-Bedingung.$type: Join-Typ (INNER, LEFT).
SQL-Äquivalent: INNER JOIN table ON condition
Beispiel:
$qb->select('shop_items.name, shop_posts.title', 'shop_items')
->join('shop_posts', 'shop_items.id', '=', 'shop_posts.user_id');
// SQL: SELECT shop_items.name, shop_posts.title FROM shop_items INNER JOIN shop_posts ON shop_items.id = shop_posts.user_id
3.2.7. groupBy
Die Methode groupBy() gruppiert Ergebnisse.
Syntax: groupBy($columns)
Parameter:
$columns: Eine Spalte oder ein Array von Spalten.
SQL-Äquivalent: GROUP BY columns
Beispiel:
$qb->select('age', 'shop_items')->groupBy('age');
// SQL: SELECT age FROM shop_items GROUP BY age
3.2.8. having
Die Methode having() filtert gruppierte Ergebnisse.
Syntax: having($column, $operator, $value)
Parameter:
$column: Spalte.$operator: Operator.$value: Wert.
SQL-Äquivalent: HAVING column operator value
Beispiel:
$qb->select('age', 'shop_items')->groupBy('age')->having('age', '>', 20);
// SQL: SELECT age FROM shop_items GROUP BY age HAVING age > ?
// Bindings: [20]
3.2.9. orderBy
Die Methode orderBy() sortiert Ergebnisse.
Syntax: orderBy($column, $direction = 'ASC')
Parameter:
$column: Spalte.$direction: Richtung (ASC oder DESC).
SQL-Äquivalent: ORDER BY column direction
Beispiel:
$qb->select('*', 'shop_items')->orderBy('age', 'DESC');
// SQL: SELECT * FROM shop_items ORDER BY age DESC
3.2.10. limit und offset
Die Methoden limit() und offset() begrenzen, wie viele Zeilen zurückgegeben werden.
Syntax: limit($limit) + offset($offset)
Parameter:
$limit: Anzahl der Zeilen.$offset: Start-Offset.
SQL-Äquivalent: LIMIT count OFFSET offset
Beispiel:
$qb->select('*', 'shop_items')->limit(5)->offset(10);
// SQL: SELECT * FROM shop_items LIMIT ? OFFSET ?
// Bindings: [5, 10]
3.2.11. raw
Die Methode raw() lässt Sie eine Raw-SQL-Abfrage ausführen.
Syntax: raw($sql, array $bindings = [])
Parameter:
$sql: Raw-SQL-Zeichenkette.$bindings: Array der Werte für Platzhalter.
SQL-Äquivalent: Die Abfrage, die Sie übergeben.
Beispiel:
$qb->raw('SELECT * FROM shop_items WHERE age > ?', [18]);
// SQL: SELECT * FROM shop_items WHERE age > ?
// Bindings: [18]
3.2.12. from
Die Methode from() setzt die Tabelle, wenn Sie sie nicht an select(), delete() oder eine ähnliche Methode übergeben haben.
Syntax: from($table)
Parameter:
$table: Tabellenname.
SQL-Äquivalent: FROM table
Beispiel:
$qb->select('id, name')->from('shop_items');
// SQL: SELECT id, name FROM shop_items
3.3. Beispiele von einfachen bis komplexen Abfragen
Einfaches select
$qb->select('*', 'shop_items');
// SQL: SELECT * FROM shop_items
select mit einer where-Bedingung
$qb->select('name', 'shop_items')->where('age', '>', 18);
// SQL: SELECT name FROM shop_items WHERE age > ?
// Bindings: [18]
Verschachtelte Bedingungen (Closure)
$qb->select('*', 'shop_items')->where(function ($qb) {
$qb->where('age', '>', 18)->orWhere('name', '=', 'Jane');
});
// SQL: SELECT * FROM shop_items WHERE (age > ? OR name = ?)
// Bindings: [18, 'Jane']
join mit mehreren Tabellen
$qb->select('shop_items.name, shop_posts.title', 'shop_items')
->join('shop_posts', 'shop_items.id', '=', 'shop_posts.user_id')
->leftJoin('shop_comments', 'shop_posts.id', '=', 'shop_comments.post_id');
// SQL: SELECT shop_items.name, shop_posts.title FROM shop_items
// INNER JOIN shop_posts ON shop_items.id = shop_posts.user_id
// LEFT JOIN shop_comments ON shop_posts.id = shop_comments.post_id
Subquery als Wert
$qb->select('name', 'shop_items')->where('id', '=', function ($qb) {
$qb->select('user_id', 'shop_posts')->where('title', '=', 'News');
});
// SQL: SELECT name FROM shop_items WHERE id = (SELECT user_id FROM shop_posts WHERE title = ?)
// Bindings: ['News']
raw-Abfrage mit benannten Variablen
$qb->raw('SELECT * FROM shop_items WHERE age > :age AND name = :name', [
'age' => 18,
'name' => 'Jane'
]);
// SQL: SELECT * FROM shop_items WHERE age > ? AND name = ?
// Bindings: [18, 'Jane']
4. Arbeiten mit Databaser in DotApp
Dieses Kapitel behandelt die praktische Nutzung von Databaser im DotApp-Framework: Rückgabetyp festlegen, Abfragen ausführen, mit ORM arbeiten, Transaktionen verwalten und Ergebnisse prüfen. Databaser ist auf Flexibilität und Einfachheit ausgelegt, unabhängig davon, ob Sie den RAW-Ansatz oder das objektorientierte ORM bevorzugen.
4.1. Den Rückgabetyp festlegen (RAW vs. ORM)
Legen Sie den Rückgabetyp mit DB::module('RAW') oder DB::module('ORM') fest — nicht mit einer Methode return().
- RAW: Gibt Rohdaten zurück (zum Beispiel ein Array von Zeilen oder eine Datenbank-Ergebnisressource). Das ist der Standard.
- ORM: Gibt Daten als Objekte zurück (
Entityfür eine einzelne Zeile,Collectionfür mehrere Zeilen).
Syntax: DB::module($type)
$type: Die Zeichenkette 'RAW' oder 'ORM' (Groß- und Kleinschreibung spielt keine Rolle).
Beispiel — RAW:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->execute(
function ($result, $db, $debug) {
var_dump($result); // Array of rows
},
function ($error) {
// execute() without this callback throws on error
}
);
Beispiel — ORM:
DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->execute(
function ($result, $db, $debug) {
var_dump($result); // Collection instance
},
function ($error) {
// execute() without this callback throws on error
}
);
Sie können den Rückgabetyp vor jeder Abfrage ändern, sodass verschiedene Teile der Anwendung nach Bedarf RAW oder ORM verwenden können.
4.2. Methoden zum Ausführen von Abfragen
Databaser stellt mehrere Methoden bereit, um mit dem QueryBuilder aufgebaute Abfragen auszuführen. Jede Methode hat einen bestimmten Einsatzzweck.
4.2.1. execute()
Die Methode execute() ist die vielseitigste — sie führt die Abfrage aus und liefert Ergebnisse über Callbacks.
Syntax: execute($success = null, $error = null)
Parameter:
$success: Erfolgs-Callback (function ($result, $db, $debug)).$error: Fehler-Callback (function ($error, $db, $debug)). Übergeben Sie diesen Callback immer; ohne ihn löstexecute()bei einem Fehler eine Exception aus.
Ausgabe: Hängt vom Rückgabetyp ab (RAW: Array/Ressource, ORM: Collection/Entity).
Beispiel:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('age', '>', 18);
})->execute(
function ($result, $db, $debug) {
echo "Query: " . $debug['query'] . "\n";
var_dump($result);
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
4.2.2. first() — unsicher bei leeren Ergebnissen
Rufen Sie first() nicht ungesichert auf. Leeres RAW löst eine Undefined-Index-Warnung aus; leeres ORM ist fatal. Bevorzugen Sie all() und nehmen Sie Index 0:
$rows = DB::module('RAW')->q(function ($qb) {
$qb->select('*')->from('shop_items')->where('id', '=', 1)->limit(1);
})->all();
$row = $rows[0] ?? null;
4.2.3. all()
Die Methode all() gibt jede Ergebniszeile zurück. Das ist auch der sichere Weg, eine einzelne Zeile zu lesen: nehmen Sie $rows[0] ?? null.
Syntax: all()
Ausgabe: RAW — Array von Zeilen; ORM — Collection.
Beispiel:
$users = DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
foreach ($users as $user) {
echo $user['name'] . "\n";
}
4.2.4. raw()
Die Methode raw() ist ein Terminal und gibt das Treiberergebnis zurück (zum Beispiel ein mysqli_result oder ein PDO-Statement). Holen Sie Zeilen aus diesem Ergebnis mit DB::fetchArray().
Syntax: raw()
Ausgabe: Hängt vom Treiber ab (zum Beispiel mysqli_result oder ein PDO-Statement).
Beispiel:
$result = DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->raw();
while ($row = DB::fetchArray($result)) {
echo $row['name'] . "\n";
}
4.3. Arbeiten mit ORM
Der ORM-Modus lässt Sie Zeilen als Objekte behandeln, was Aktualisierungen und Beziehungen zwischen Tabellen vereinfacht.
4.3.1. Entity und Collection
Entity: Repräsentiert eine Tabellenzeile. Sie stellt Attribute bereit, die den Spalten entsprechen, plus Methoden für Aktualisierungen und Beziehungen.
Collection: Eine Gruppe von Entity-Objekten mit Iteration und Hilfsmethoden wie filter(), map() und pluck().
Beispiel:
$users = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
foreach ($users as $user) {
echo $user->name . "\n"; // Collection yields Entity objects
}
4.3.2. Daten speichern (save())
Entity::save($ok, $err) schreibt Änderungen in die Datenbank. Die Methode gibt void zurück — verwenden Sie immer die Callbacks; schreiben Sie nicht if ($entity->save()). Lesen Sie eine Zeile mit all() und $rows[0] ?? null, bevor Sie speichern.
Beispiel:
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('id', '=', 1);
})->all();
$user = $rows[0] ?? null;
if ($user) {
$user->age = 26;
$user->save(
function ($result, $db, $debug) {
echo "User saved!\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}
4.3.3. Beziehungen (hasOne, hasMany)
ORM unterstützt Beziehungen zwischen Tabellen. Laden Sie verwandte Zeilen über einen Methodenaufruf auf der Entity — nicht über eine magische Eigenschaft wie $user->posts:
- hasOne: Eins-zu-eins.
- hasMany: Eins-zu-viele.
Beispiel:
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('id', '=', 1);
})->all();
$user = $rows[0] ?? null;
$posts = $user ? $user->hasMany('shop_posts', 'user_id') : [];
foreach ($posts as $post) {
echo $post->title . "\n";
}
4.3.4. Lazy Loading und Collection-Methoden
Verwandte Zeilen werden geladen, wenn Sie die Beziehungsmethode aufrufen (Lazy Loading). Collection bietet Hilfsmethoden wie filter(), map() und pluck(). pluck('name') gibt eine Collection der Werte dieses Feldes zurück; rufen Sie darauf all() auf, wenn Sie ein gewöhnliches PHP-Array benötigen.
Beispiel:
$users = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
$names = $users->pluck('name');
var_dump($names);
4.4. Transaktionen
Databaser unterstützt Transaktionen, sodass eine Gruppe von Schreibvorgängen entweder vollständig gelingt oder vollständig zurückgerollt wird.
4.4.1. transaction(), commit(), rollback()
Manuelle Steuerung mit DB::module('RAW')->transaction(), anschließend commit() oder rollback():
$db = DB::module('RAW');
$db->transaction();
$db->q(function ($qb) {
$qb->insert('shop_items', ['name' => 'Jane']);
})->execute(
function ($result, $db, $debug) {
$db->commit();
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
$db->rollback();
}
);
Automatische Transaktion mit transact(). Der Operations-Callback erhält die Databaser-Instanz sowie Erfolgs- und Fehler-Callbacks — reichen Sie beide an jedes execute() durch, damit die Transaktion committen oder zurückrollen kann:
DB::module('RAW')->transact(function ($db, $ok, $err) {
$db->q(function ($qb) {
$qb->insert('shop_items', ['name' => 'Jane']);
})->execute($ok, $err);
}, function ($result, $db, $debug) {
echo "Transaction succeeded!\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
4.5. Debugging und Arbeiten mit der Ausgabe
Jede Operation stellt drei wesentliche Werte bereit:
- result: Das Abfrageergebnis (RAW: Array, ORM: Objekte).
- db: Die
Databaser-Instanz für Folgeabfragen. - debug: Ein Array mit Informationen (zum Beispiel
queryundbindings). Dieselbe Nutzlast enthält auchinsert_idundaffected_rows.
Debug-Beispiel:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('age', '>', 18);
})->execute(
function ($result, $db, $debug) {
echo "SQL: " . $debug['query'] . "\n";
echo "Bindings: " . implode(', ', $debug['bindings']) . "\n";
var_dump($result);
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
5. Praktische Beispiele
Dieses Kapitel zeigt die praktische Nutzung von Databaser im DotApp-Framework. Behandelt werden gängige CRUD-Operationen (Erstellen, Lesen, Aktualisieren, Löschen), fortgeschrittene Abfragen mit join und Subqueries, Transaktionen und Fehlerbehandlung. Nach einem Insert oder Update lesen Sie die neue ID und die Anzahl betroffener Zeilen mit $db->inserted_id() und $db->affected_rows() (Unterstriche). Dieselben Werte stehen in den execute-Callbacks auch als $execution_data['insert_id'] und $execution_data['affected_rows'] zur Verfügung.
5.1. Grundlegende CRUD-Operationen im RAW-Modus
Erstellen:
DB::module('RAW')->q(function ($qb) {
$qb->insert('shop_items', ['name' => 'Jane', 'age' => 25]);
})->execute(
function ($result, $db, $debug) {
$id = $db->inserted_id(); // ID of the new row
echo "New user with ID: $id has been created.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Lesen:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('age', '>', 20);
})->execute(
function ($result, $db, $debug) {
foreach ($result as $user) {
echo "Name: {$user['name']}, Age: {$user['age']}\n";
}
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Aktualisieren:
DB::module('RAW')->q(function ($qb) {
$qb->update('shop_items')->set(['age' => 26])->where('name', '=', 'Jane');
})->execute(
function ($result, $db, $debug) {
$rows = $db->affected_rows(); // Number of affected rows
echo "$rows row(s) updated.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Löschen:
DB::module('RAW')->q(function ($qb) {
$qb->delete('shop_items')->where('name', '=', 'Jane');
})->execute(
function ($result, $db, $debug) {
$rows = $db->affected_rows();
echo "$rows row(s) deleted.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
5.2. Grundlegende CRUD-Operationen im ORM-Modus
Erstellen:
DB::module('ORM')->q(function ($qb) {
$qb->insert('shop_items', ['name' => 'Maria', 'age' => 30]);
})->execute(
function ($result, $db, $debug) {
$id = $db->inserted_id();
$items = $db->q(function ($qb) use ($id) {
$qb->select('*', 'shop_items')->where('id', '=', $id);
})->all();
$user = $items[0] ?? null;
if ($user) {
echo "Created user: {$user->name}\n";
}
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Lesen:
$users = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
foreach ($users as $user) {
echo "Name: {$user->name}, Age: {$user->age}\n";
}
Aktualisieren:
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('name', '=', 'Maria');
})->all();
$user = $rows[0] ?? null;
if ($user) {
$user->age = 31;
$user->save(
function ($result, $db, $debug) use ($user) {
echo "User {$user->name} updated.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}
Löschen:
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('name', '=', 'Maria');
})->all();
$user = $rows[0] ?? null;
if ($user) {
DB::module('RAW')->q(function ($qb) use ($user) {
$qb->delete('shop_items')->where('id', '=', $user->id);
})->execute(
function ($result, $db, $debug) {
$rows = $db->affected_rows();
echo "$rows row(s) deleted.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}
5.3. Fortgeschrittene Beispiele mit JOIN und Subquery
JOIN über Tabellen:
DB::module('RAW')->q(function ($qb) {
$qb->select('shop_items.name, shop_posts.title', 'shop_items')
->join('shop_posts', 'shop_items.id', '=', 'shop_posts.user_id')
->where('shop_items.age', '>', 25);
})->execute(
function ($result, $db, $debug) {
foreach ($result as $row) {
echo "User: {$row['name']}, Post: {$row['title']}\n";
}
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Subquery im ORM:
DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')
->where('id', '=', function ($subQb) {
$subQb->select('user_id', 'shop_posts')
->where('title', '=', 'News');
});
})->execute(
function ($users, $db, $debug) {
foreach ($users as $user) {
echo "User with News post: {$user->name}\n";
}
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
5.4. Arbeiten mit Transaktionen
Automatische Transaktion:
DB::module('RAW')->transact(function ($db, $ok, $err) {
$db->q(function ($qb) {
$qb->insert('shop_items', ['name' => 'Peter', 'age' => 28]);
})->execute(function ($result, $db, $debug) use ($ok, $err) {
$id = $db->inserted_id();
$db->q(function ($qb) use ($id) {
$qb->insert('shop_posts', ['user_id' => $id, 'title' => 'First post']);
})->execute($ok, $err);
}, $err);
}, function ($result, $db, $debug) {
echo "Transaction succeeded. Last insert ID: " . $db->inserted_id() . "\n";
}, function ($error, $db, $debug) {
echo "Transaction error: {$error['error']}\n";
});
Manuelle Transaktion:
$db = DB::module('RAW');
$db->transaction();
$db->q(function ($qb) {
$qb->insert('shop_items', ['name' => 'Anna', 'age' => 22]);
})->execute(
function ($result, $db, $debug) {
$id = $db->inserted_id();
$db->q(function ($qb) use ($id) {
$qb->insert('shop_posts', ['user_id' => $id, 'title' => 'Test']);
})->execute(
function ($result, $db, $debug) {
$db->commit();
echo "Transaction completed.\n";
},
function ($error, $db, $debug) {
$db->rollback();
echo "Rollback: {$error['error']}\n";
}
);
},
function ($error, $db, $debug) {
$db->rollback();
echo "Error: {$error['error']}\n";
}
);
5.5. Debugging und Fehlerbehandlung
Eine Abfrage debuggen:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('age', '>', 18);
})->execute(
function ($result, $db, $debug) {
echo "SQL: " . $debug['query'] . "\n";
echo "Bindings: " . implode(', ', $debug['bindings']) . "\n";
echo "Affected rows: " . $db->affected_rows() . "\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']} (code: {$error['errno']})\n";
echo "SQL: " . $debug['query'] . "\n";
}
);
Einen Fehler behandeln:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'missing_table'); // Invalid query
})->execute(
function ($result, $db, $debug) {
echo "Success\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
// Follow-up query on a table that exists
$db->q(function ($qb) {
$qb->select('*', 'shop_items');
})->execute(
function ($result, $db, $debug) {
echo "Recovery query succeeded.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}
);
6. Arbeiten mit SchemaBuilder
SchemaBuilder ist ein Werkzeug von Databaser zum Definieren und Verwalten der Datenbankstruktur. Es lässt Sie Tabellen aus PHP erstellen, ändern und löschen, ohne Raw-DDL von Hand zu schreiben. Es ist über createTable(), alterTable() und dropTable() mit dem QueryBuilder integriert. Dieses Kapitel behandelt Methoden, Argumente und praktische Beispiele.
6.1. Grundlagen von SchemaBuilder
SchemaBuilder ist die Klasse Dotsystems\App\Parts\SchemaBuilder. Sie erhalten sie im Callback von createTable(), alterTable() und verwandten Hilfsmethoden. Diese Hilfsmethoden werden aus dem QueryBuilder innerhalb von q() oder aus schema() aufgerufen. Das Ziel ist ein programmatischer Weg, Tabellen, Spalten, Indizes und Fremdschlüssel zu definieren. Die entstehenden Anweisungen werden in SQL umgewandelt und über den aktiven Treiber (MySQLi oder PDO) ausgeführt.
Wesentliche Eigenschaften:
- Verkettbare Methoden: Wie der
QueryBuilderistSchemaBuilderfür Verkettung ausgelegt. - Abstraktion: Es arbeitet unabhängig vom Datenbanktreiber, obwohl einige Funktionen von der Engine abhängen.
- Einfachheit: Sie können ein Schema definieren, ohne vollständige SQL-Syntax zu schreiben.
6.2. SchemaBuilder-Methoden
Übersicht der wichtigsten Methoden, ihrer Argumente und Beispiele.
Spalten-Hilfsmethoden geben eine Spaltendefinition zurück. Verketten Sie Modifikatoren an diesem Objekt: nullable(), default(), unsigned() (nur MySQL) und comment(). Übergeben Sie kein Nullable-Flag als nachgestelltes Argument an string() oder integer(). Es gibt keine Hilfsmethode timestamps() — deklarieren Sie created_at und updated_at selbst mit datetime() (oder timestamp(), wenn Sie ausdrücklich eine TIMESTAMP-Spalte wollen).
Weitere Spalten-Hilfsmethoden sind text(), decimal($name, $precision = 10, $scale = 2), timestamp(), date(), boolean(), bigInteger() und tinyInteger().
6.2.1. id()
Fügt einen Primärschlüssel BIGINT AUTO_INCREMENT hinzu.
Syntax: id($name = 'id')
Parameter:
$name: Spaltenname (Standard'id').
SQL-Äquivalent: id BIGINT NOT NULL AUTO_INCREMENT plus eine Primärschlüssel-Constraint. Unter MySQL können Sie ->unsigned() verketten.
Beispiel:
$schema->id(); // Creates the `id` column
6.2.2. string()
Fügt eine VARCHAR-Spalte hinzu.
Syntax: string($name, $length = 255)
Parameter:
$name: Spaltenname.$length: Länge (Standard 255).
Erlauben Sie NULL, indem Sie nullable() verketten: $schema->string('name', 100)->nullable().
SQL-Äquivalent: VARCHAR(length) [NOT NULL | NULL]
Beispiel:
$schema->string('name', 100)->nullable(); // `name` VARCHAR(100) NULL
6.2.3. integer()
Fügt eine INT-Spalte hinzu.
Syntax: integer($name)
Parameter:
$name: Spaltenname.
Erlauben Sie NULL, indem Sie nullable() verketten: $schema->integer('age')->nullable().
SQL-Äquivalent: INT [NOT NULL | NULL]
Beispiel:
$schema->integer('age'); // `age` INT NOT NULL
$schema->integer('age')->nullable(); // `age` INT NULL
6.2.4. created_at / updated_at
Deklarieren Sie DateTime-Spalten mit datetime(). Rufen Sie nicht timestamps() auf — diese Methode existiert nicht. Verwenden Sie timestamp() nur, wenn Sie eine TIMESTAMP-Spalte wollen.
Syntax: datetime('created_at') / datetime('updated_at')
SQL-Äquivalent:
created_at DATETIME NOT NULL
updated_at DATETIME NOT NULL
Beispiel:
$schema->datetime('created_at');
$schema->datetime('updated_at');
6.2.5. foreign()
Fügt einen Fremdschlüssel hinzu.
Syntax: foreign($column, $name = null), anschließend ->references($col)->on($table)->onDelete($action) verketten.
Parameter:
$column: Lokale Spalte, die den Fremdschlüssel hält.$name: Optionaler Constraint-Name.
Verketten Sie references(), on() und onDelete() an dem von foreign() zurückgegebenen Objekt.
SQL-Äquivalent: FOREIGN KEY (column) REFERENCES table (references) ON DELETE CASCADE
Beispiel:
$schema->foreign('user_id')->references('id')->on('shop_items')->onDelete('CASCADE');
6.2.6. index()
Fügt einen Index auf einer oder mehreren Spalten hinzu.
Syntax: index($columns, $name = null)
Parameter:
$columns: Spaltenname oder ein Array von Spaltennamen.$name: Optionaler Indexname.
SQL-Äquivalent: INDEX (column)
Beispiel:
$schema->index('name');
6.2.7. addColumn() (für ALTER TABLE)
Fügt einer vorhandenen Tabelle eine neue Spalte hinzu.
Syntax: addColumn($name, $type, $length = null, $nullable = false, $default = null, $comment = null)
Parameter:
$name: Spaltenname.$type: Typ (zum BeispielVARCHAR,INT).$length: Länge (optional).$nullable:NULLerlauben (Standardfalse).$default: Standardwert (optional).$comment: Spaltenkommentar (optional).
SQL-Äquivalent: ADD column type [length] [NOT NULL | NULL]
Beispiel:
$schema->addColumn('email', 'VARCHAR', 150, true);
6.2.8. dropColumn() (für ALTER TABLE)
Entfernt eine Spalte aus einer Tabelle.
Syntax: dropColumn($name)
Parameter:
$name: Spaltenname.
SQL-Äquivalent: DROP COLUMN column
Beispiel:
$schema->dropColumn('email');
6.3. SchemaBuilder verwenden
Verwenden Sie SchemaBuilder mit den QueryBuilder-Methoden createTable(), alterTable() und dropTable(). Führen Sie sie innerhalb von DB::module('RAW')->q(function ($qb) { ... })->execute($ok, $err) aus. Alternativ kapseln Sie dieselbe QueryBuilder-Arbeit in DB::module('RAW')->schema($callback, $success, $error). Übergeben Sie an execute() immer beide Callbacks und an schema() immer einen Fehler-Callback.
6.3.1. Eine Tabelle erstellen
createTable() erstellt eine neue Tabelle.
Beispiel:
DB::module('RAW')->q(function ($qb) {
$qb->createTable('shop_items', function ($schema) {
$schema->id();
$schema->string('name', 50);
$schema->integer('age')->nullable();
$schema->datetime('created_at');
$schema->datetime('updated_at');
$schema->index('name');
});
})->execute(
function ($result, $db, $debug) {
echo "Table 'shop_items' was created.\n";
echo "SQL: {$debug['query']}\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Dieselbe DDL über schema():
DB::module('RAW')->schema(
function ($qb) {
$qb->createTable('shop_items', function ($schema) {
$schema->id();
$schema->string('name', 50);
$schema->integer('age')->nullable();
$schema->datetime('created_at');
$schema->datetime('updated_at');
$schema->index('name');
});
},
function ($result, $db, $debug) {
echo "Table 'shop_items' was created.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Erzeugtes SQL:
CREATE TABLE shop_items (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`age` INT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
INDEX `idx_name` (`name`),
CONSTRAINT `pk_id` PRIMARY KEY (`id`)
)
6.3.2. Eine Tabelle ändern
alterTable() ändert eine vorhandene Tabelle.
Beispiel:
DB::module('RAW')->q(function ($qb) {
$qb->alterTable('shop_items', function ($schema) {
$schema->addColumn('email', 'VARCHAR', 100, true);
$schema->foreign('user_id')->references('id')->on('shop_items')->onDelete('CASCADE');
$schema->dropColumn('age');
});
})->execute(
function ($result, $db, $debug) {
echo "Table 'shop_items' was altered.\n";
echo "SQL: {$debug['query']}\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Erzeugtes SQL:
ALTER TABLE shop_items
ADD `email` VARCHAR(100) NULL,
ADD FOREIGN KEY (`user_id`) REFERENCES `shop_items` (`id`) ON DELETE CASCADE,
DROP COLUMN `age`
6.3.3. Eine Tabelle löschen
dropTable() entfernt eine Tabelle.
Beispiel:
DB::module('RAW')->q(function ($qb) {
$qb->dropTable('shop_items');
})->execute(
function ($result, $db, $debug) {
echo "Table 'shop_items' was dropped.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Erzeugtes SQL:
DROP TABLE shop_items
6.4. Fortgeschrittene Beispiele
Eine Tabelle mit Fremdschlüsseln erstellen:
DB::module('RAW')->q(function ($qb) {
$qb->createTable('shop_posts', function ($schema) {
$schema->id();
$schema->string('title', 200);
$schema->integer('user_id');
$schema->foreign('user_id')->references('id')->on('shop_items')->onDelete('CASCADE');
$schema->datetime('created_at');
$schema->datetime('updated_at');
});
})->execute(
function ($result, $db, $debug) {
echo "Table 'shop_posts' created.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
Gesammelte Schemaänderung in einer Transaktion:
DB::module('RAW')->transact(
function ($db, $commitOnSuccess, $rollbackOnError) {
$db->q(function ($qb) {
$qb->createTable('shop_items', function ($schema) {
$schema->id();
$schema->string('name');
});
})->execute($commitOnSuccess, $rollbackOnError);
$db->q(function ($qb) {
$qb->createTable('shop_posts', function ($schema) {
$schema->id();
$schema->integer('user_id');
$schema->foreign('user_id')->references('id')->on('shop_items')->onDelete('CASCADE');
});
})->execute($commitOnSuccess, $rollbackOnError);
},
function ($result, $db, $debug) {
echo "Schema change succeeded.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
6.5. Hinweise und Einschränkungen
Kompatibilität: Einige Funktionen (zum Beispiel ON DELETE CASCADE) verhalten sich nicht auf jeder Engine gleich. SQLite hat insbesondere eingeschränkte Unterstützung für das Löschen von Spalten, Indizes und Fremdschlüsseln.
Transaktionen: Verwenden Sie für größere Schemaänderungen DB::module('RAW')->transact(...), damit die Arbeit konsistent bleibt.
Debugging: Prüfen Sie immer $debug['query'] (das dritte execute-Argument), um das erzeugte SQL zu verifizieren.
Exceptions: SchemaBuilder wirft \InvalidArgumentException bei ungültigen Bezeichnern, nicht unterstützten Typen und fehlenden Fremdschlüssel-Zielen. Kapseln Sie DDL in try/catch.
7. CacheDriverInterface
Databaser im DotApp-Framework kann Abfrageergebnisse cachen, sodass wiederholte Anfragen nach denselben Daten die Datenbank überspringen. Query-Caching ist aktiv, wenn Config::db('cache') === true. Ein eigener Cache-Speicher muss get und set bereitstellen; binden Sie ihn mit DB::module()->cache($driverObject) an (bevorzugen Sie das gegenüber DB::cache()). Für ORM-Schreibvorgänge benötigen Sie außerdem deleteKeys(). Dieses Kapitel beschreibt den erwarteten Treibervertrag und die Nutzung mit Databaser.
7.1. Was ist CacheDriverInterface?
CacheDriverInterface ist der Vertrag zum Speichern und Laden gecachter Abfrageergebnisse. Databaser kann mit jedem Hintergrundspeicher sprechen (Memcached, Redis, Dateisystem), sofern Ihr Objekt die unten stehenden Methoden implementiert. Nachdem Sie einen Treiber mit cache() zugewiesen haben, versucht Databaser vor der Abfrage get() und nach erfolgreicher Ausführung set().
Wichtig: Entity::save() mit aktiviertem Cache erfordert deleteKeys() am Treiber. Kein mitgelieferter Treiber implementiert deleteKeys(). Lassen Sie db.cache ausgeschaltet, sofern Sie keinen eigenen Treiber bereitstellen, der alle vier Methoden inklusive deleteKeys() implementiert.
Vorteile:
- Geringere Datenbanklast.
- Schnellerer Zugriff auf häufig angeforderte Daten.
- Flexibilität — Sie können jedes Cache-Backend verwenden.
7.2. Methoden von CacheDriverInterface
Das Interface definiert vier Methoden. Query-Caching verwendet get und set. Die Invalidierung bei Entity::save() erfordert außerdem deleteKeys():
interface CacheDriverInterface {
public function get($key);
public function set($key, $value, $ttl = null);
public function delete($key);
public function deleteKeys($pattern);
}
7.2.1. get($key)
Lädt einen Wert anhand des Schlüssels aus dem Cache.
Parameter:
$key: String — eindeutiger Schlüssel für die gespeicherten Daten.
Rückgabewert: Der gespeicherte Wert oder null, wenn der Schlüssel nicht existiert.
Zweck: Databaser ruft diese Methode auf, um zu prüfen, ob das Abfrageergebnis bereits gecacht ist.
7.2.2. set($key, $value, $ttl = null)
Speichert einen Wert unter dem angegebenen Schlüssel im Cache.
Parameter:
$key: String — Speicherschlüssel.$value: Zu speichernde Daten (Array, Objekt und so weiter).$ttl: Lebensdauer in Sekunden (optional;nullbedeutet keine Ablaufzeit auf Treiberebene).Databaserübergibt immer3600.
Rückgabewert: Keiner (oder true/false je nach Implementierung).
Zweck: Nach einer erfolgreichen Abfrage speichert Databaser das Ergebnis im Cache.
7.2.3. delete($key)
Entfernt einen einzelnen Schlüssel aus dem Cache.
Parameter:
$key: String — zu entfernender Schlüssel.
Rückgabewert: Keiner (oder true/false).
Zweck: Explizites Löschen eines Cache-Eintrags.
7.2.4. deleteKeys($pattern)
Entfernt mehrere Schlüssel, die zu einem Muster passen.
Parameter:
$pattern: String — Schlüsselmuster (zum Beispiel"shop_items:*").
Rückgabewert: Keiner (oder die Anzahl gelöschter Schlüssel).
Zweck: Databaser ruft diese Methode auf, wenn sich Daten ändern (zum Beispiel Entity::save() im ORM), damit verwandte Cache-Einträge invalidiert werden. Ist ein Cache-Treiber gesetzt und fehlt diese Methode, löst save() eine Exception aus.
7.3. Einen eigenen Cache-Treiber implementieren
Beispiel eines einfachen dateibasierten Cache-Treibers. Betrachten Sie dies als Beispiel für einen eigenen Treiber, nicht als mitgelieferte Framework-Klasse:
class FileCacheDriver implements CacheDriverInterface {
private $cacheDir;
public function __construct($cacheDir = '/tmp/cache') {
$this->cacheDir = $cacheDir;
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0777, true);
}
}
public function get($key) {
$file = $this->cacheDir . '/' . md5($key);
if (file_exists($file)) {
$data = unserialize(file_get_contents($file));
if ($data['expires'] === null || $data['expires'] > time()) {
return $data['value'];
}
unlink($file); // Expired — remove it
}
return null;
}
public function set($key, $value, $ttl = null) {
$file = $this->cacheDir . '/' . md5($key);
$expires = $ttl ? time() + $ttl : null;
$data = ['value' => $value, 'expires' => $expires];
file_put_contents($file, serialize($data));
return true;
}
public function delete($key) {
$file = $this->cacheDir . '/' . md5($key);
if (file_exists($file)) {
unlink($file);
return true;
}
return false;
}
public function deleteKeys($pattern) {
$count = 0;
foreach (glob($this->cacheDir . '/*') as $file) {
$key = basename($file);
if (fnmatch($pattern, $key)) {
unlink($file);
$count++;
}
}
return $count;
}
}
Erläuterung:
get(): Liest Daten aus einer Datei, sofern sie nicht abgelaufen sind.set(): Schreibt Daten mit optionalem TTL in eine Datei.delete(): Entfernt eine bestimmte Datei.deleteKeys(): Entfernt Dateien, die zu einem Muster passen (verwendetfnmatch).
7.4. Einen Cache-Treiber mit Databaser verwenden
Aktivieren Sie Query-Caching mit Config::db('cache') === true. Weisen Sie anschließend Ihren Cache-Speicher mit DB::module()->cache($driverObject) zu. Das Objekt muss get($key) und set($key, $value, $lifetime) bereitstellen. Bevorzugen Sie das gegenüber DB::cache().
$cacheDriver = new FileCacheDriver('/tmp/myapp_cache');
DB::module()->cache($cacheDriver);
// Example query with caching
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('age', '>', 18);
})->execute(
function ($result, $db, $execution_data) {
echo "Results (from cache or DB):\n";
var_dump($result);
// On a cache hit, $execution_data is an empty array.
},
function ($error, $db, $execution_data) {
echo "Error: {$error['error']}\n";
}
);
So funktioniert es:
Databaserbaut einen Schlüssel in der Form"{table}:{returnType}:" . md5($query . serialize($bindings))(zum Beispielshop_items:RAW:gefolgt vom Hash).- Es prüft den Cache mit
get(). Bei einem Treffer gibt es den gespeicherten Wert ohne Datenbankabfrage zurück und liefert ein leeres$execution_dataan den Erfolgs-Callback. - Bei einem Cache-Miss führt es die Abfrage aus und speichert das Ergebnis mit
set(). Die TTL ist fest auf 3600 Sekunden gesetzt. - Bei einer ORM-Aktualisierung wie
Entity::save()invalidiert es verwandte Schlüssel mitdeleteKeys().
7.5. Fortgeschrittenes Caching-Beispiel
Caching mit ORM und Invalidierung:
$cacheDriver = new FileCacheDriver();
DB::module()->cache($cacheDriver);
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
$user = $rows[0] ?? null;
if ($user !== null) {
$user->age = 40;
$user->save(
function ($result, $db, $execution_data) {
echo "Item saved, cache invalidated.\n";
},
function ($error, $db, $execution_data) {
echo "Error: {$error['error']}\n";
}
);
}
Was passiert:
- Die erste Abfrage speichert die
Collectionim Cache. - Bei
save()läuftdeleteKeys("shop_items:ORM:*")und invalidiert alle ORM-Cache-Einträge fürshop_items. - Hat der zugewiesene Treiber kein
deleteKeys(), löstsave()eine Exception aus. Lassen Siedb.cacheausgeschaltet, sofern Ihr eigener Treiber nicht alle vier Methoden implementiert.
7.6. Hinweise und Tipps
TTL: Databaser speichert Abfrageergebnisse 3600 Sekunden.
Schlüsselformat: Schlüssel verwenden "{table}:{returnType}:" . md5(...), sodass Muster wie "shop_items:*" die Einträge einer Tabelle treffen.
Cache-Treffer: Der Erfolgs-Callback läuft weiterhin, $execution_data ist jedoch leer.
deleteKeys(): Kein mitgelieferter Treiber implementiert es. Lassen Sie Config::db('cache') ausgeschaltet, sofern Sie keinen eigenen Treiber bereitstellen, der get, set, delete und deleteKeys() implementiert.
Performance: Bevorzugen Sie in der Produktion einen schnellen Speicher wie Redis gegenüber Dateien — weiterhin nur, nachdem dieser Speicher die vier oben genannten Methoden implementiert.
Tests: Prüfen Sie, dass deleteKeys() den Cache tatsächlich invalidiert, damit Sie nach save() keine veralteten Zeilen ausliefern.
8. Arbeiten mit Entity
Eine Entity repräsentiert eine Tabellenzeile im ORM-Modul. Beschaffen Sie Entities über DB::module('ORM'), lesen Sie die erste Zeile jedoch sicher mit all() und $rows[0] ?? null.
Hinweis zu ORM-Beziehungen: with(), whereHas() und withCount() speichern in DotApp PHP Framework 2.0 nur Zustand und beeinflussen SQL nicht. Stellen Sie sie nicht als Eager Loading dar. Laden Sie Beziehungen durch Aufruf von Entity-Methoden, zum Beispiel $user->hasMany('shop_posts', 'user_id').
8.2. Grundlegende Verwendung von Entity
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('id', '=', 1)->limit(1);
})->all();
$user = $rows[0] ?? null;
if ($user) {
echo $user->name;
$user->age = 26;
$user->save(
function ($result, $db, $debug) {
echo "User saved.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}
8.3. Beziehungen
Beziehungen sind Methoden, die auf einer Entity aufgerufen werden. Der optionale Callback kann die verwandte Abfrage anpassen, zum Beispiel mit where(), orderBy() oder limit().
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('id', '=', 1)->limit(1);
})->all();
$user = $rows[0] ?? null;
$posts = $user ? $user->hasMany('shop_posts', 'user_id', null, function ($qb) {
$qb->orderBy('created_at', 'DESC')->limit(2);
}) : [];
foreach ($posts as $post) {
echo $post->title . "\n";
}
Beziehungsmethoden an Entity:
hasOne($relatedTable, $foreignKey, $localKey = null, $callback = null)→Entity|nullbelongsTo($relatedTable, $foreignKey, $ownerKey = null, $callback = null)→Entity|nullhasMany($relatedTable, $foreignKey, $localKey = null, $callback = null)→CollectionmorphOne($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null)→Entity|nullmorphMany($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null)→CollectionmorphTo($name = null, $type = null, $id = null, $ownerKey = null)steht ebenfalls zur Verfügung und gibtEntity|nullzurück
Polymorphe Beziehung mit Filter
Eine polymorphe Beziehung speichert den übergeordneten Typ und die übergeordnete ID auf der verwandten Zeile. Übergeben Sie einen Callback, um die verwandte Abfrage zu filtern, zu sortieren oder zu begrenzen. Lesen Sie den übergeordneten Datensatz mit all() und $rows[0] ?? null:
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('id', '=', 1)->limit(1);
})->all();
$item = $rows[0] ?? null;
if ($item) {
$recentImages = $item->morphMany('shop_images', 'imageable_type', 'imageable_id', 'shop_items', null, function ($qb) {
$qb->orderBy('created_at', 'DESC')->limit(3);
});
foreach ($recentImages as $image) {
echo "Latest image: {$image->url}\n";
}
}
8.4. Eine neue Entity einfügen
$item = DB::newEntity();
$item->table('shop_items');
$item->name = 'Jane Novak';
$item->age = 30;
$item->save(
function ($result, $db, $debug) {
echo "New record created with ID: {$db->inserted_id()}.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
9. Arbeiten mit Collection
Eine Collection ist eine Menge von Entities, die all() zurückgibt. Nutzen Sie Iteration, filter(), map(), pluck(), toArray() und count(). Verwenden Sie nicht saveAll(); Entity::save() gibt void zurück, speichern Sie daher jede Entity einzeln und übergeben Sie immer einen Fehler-Callback.
9.2. Grundlegende Verwendung von Collection
$items = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
foreach ($items as $item) {
echo "Name: {$item->name}\n";
}
$allItems = $items->all();
$first = $allItems[0] ?? null;
9.3. Filter, Map und einzelnes Speichern
$items = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
$active = $items->filter(function ($item) {
return (int) $item->active === 1;
});
foreach ($active as $item) {
$item->checked_at = date('Y-m-d H:i:s');
$item->save(
null,
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}
10. Datenbankschema
Sie definieren die Tabellenstruktur im Code mit SchemaBuilder und in einem Modul über einen versionierten Installer Installation.php. Sie können Tabellen und Spalten erstellen, ändern und löschen. Kapseln Sie Stapeloperationen in einer Transaktion mit transact(). Rufen Sie niemals DB::migrate() auf. Es gibt keine Hilfsmethode timestamps() — fügen Sie datetime()-Spalten ausdrücklich hinzu, wenn Sie sie benötigen.
10.2. Was ist Schemaverwaltung?
Sie definieren Tabellen und Beziehungen programmatisch. In Databaser geschieht das mit SchemaBuilder; kapseln Sie gesammelte Änderungen in einer Transaktion mit transact(). Wesentliche Vorteile:
- Automatisierung: Datenbankänderungen liegen im Code und können versioniert werden.
- Transaktionen: Stapeloperationen sind sicher und bei Fehlern umkehrbar.
- Multiplattform: Unterstützung für unterschiedliche Treiber (MySQLi, PDO) mit an die Datenbank angepasster Syntax.
10.3. Grundprinzipien
Die Schemaverwaltung in Databaser beruht auf diesen Prinzipien:
- SchemaBuilder: Definition von Tabellen und Spalten (zum Beispiel
id(),string(),foreign(),datetime()). - Installation.php: Versionierte Installation und Deinstallation von Modultabellen, abgesichert mit
self::alreadyDoneundself::markDone. - Transaktionen: Gesammelte Änderungen über
transact(), wobei mehrere Operationen als eine Einheit laufen. - Treiberunterstützung: MySQLi und PDO passen die Syntax an den Datenbanktyp an (zum Beispiel MySQL, PostgreSQL, SQLite).
10.4. Das Schema verwenden
Definieren Sie die Tabellenstruktur und wenden Sie sie an. Übergeben Sie an schema() immer einen Fehler-Callback. Beispiel für das Erstellen einer Tabelle:
DB::module('RAW')->schema(function ($schema) {
$schema->createTable('shop_items', function ($table) {
$table->id();
$table->string('name');
});
}, function ($result, $db, $debug) {
echo "Table 'shop_items' was created successfully.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
Ausgabe:
Table 'shop_items' was created successfully.
Gesammelte Änderung mit einer Transaktion (mehrere Tabellen):
DB::module('RAW')->transact(function ($db) {
$db->q(function ($qb) {
$qb->createTable('shop_items', function ($schema) {
$schema->id();
$schema->string('name');
});
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
});
$db->q(function ($qb) {
$qb->createTable('shop_posts', function ($schema) {
$schema->id();
$schema->integer('user_id');
$schema->foreign('user_id')->references('id')->on('shop_items')->onDelete('CASCADE');
});
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
});
}, function ($result, $db, $debug) {
echo "Schema change succeeded.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
Ausgabe:
Schema change succeeded.
10.5. Verfügbare Schema-Methoden
Databaser stellt die folgenden Methoden für die Arbeit mit dem Schema bereit:
10.5.1. schema($callback, $success, $error)
Definiert und führt eine einzelne Schemaoperation aus (zum Beispiel das Erstellen einer Tabelle). Übergeben Sie immer den Fehler-Callback.
Syntax: schema(callable $callback, callable $success = null, callable $error = null)
Parameter:
$callback: Closure, die die Operation überSchemaBuilderdefiniert.$success: Callback bei Erfolg.$error: Callback bei Fehler (übergeben Sie diesen immer).
Beispiel:
DB::module('RAW')->schema(function ($schema) {
$schema->createTable('shop_items', function ($table) {
$table->id();
$table->string('email', 100);
});
}, function ($result, $db, $debug) {
echo "Table created.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
Ausgabe:
Table created.
10.5.2. Modulschema mit Installation.php
Erstellen und versionieren Sie Modultabellen aus Installation.php. Sichern Sie jede Version mit self::alreadyDone und halten Sie sie mit self::markDone fest. Rufen Sie niemals DB::migrate() auf.
DB::module('RAW')->q(function ($qb) {
$qb->raw(
"CREATE TABLE IF NOT EXISTS `shop_items` (
`id` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(200) NOT NULL,
`created_at` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
[]
);
})->execute(
function () { /* self::markDone('1.0.0'); */ },
function ($error) { \Dotsystems\App\Parts\Logger::use()->error('schema failed', $error); }
);
10.5.3. transact($operations, $success, $error)
Führt eine Menge von Schemaänderungen innerhalb einer Transaktion aus. Rufen Sie sie auf der Modulinstanz auf: DB::module('RAW')->transact(...).
Syntax: transact(callable $operations, callable $success = null, callable $error = null)
Parameter:
$operations: Closure mit mehreren Schemaoperationen. Das erste Argument ist die Modulinstanz ($db).$success: Callback bei Erfolg (nach Commit).$error: Callback bei Fehler (zurückgerollt). Übergeben Sie diesen immer.
Beispiel:
DB::module('RAW')->transact(function ($db) {
$db->q(function ($qb) {
$qb->createTable('shop_comments', function ($schema) {
$schema->id();
$schema->integer('post_id');
});
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
});
}, function ($result, $db, $debug) {
echo "Batch schema change succeeded.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
Ausgabe:
Batch schema change succeeded.
10.5.4. SchemaBuilder::createTable($table, $callback)
Erstellt eine neue Tabelle mit definierter Struktur.
Syntax: createTable(string $table, callable $callback)
Beispiel:
DB::module('RAW')->schema(function ($schema) {
$schema->createTable('shop_products', function ($table) {
$table->id();
$table->string('name');
$table->decimal('price', 8, 2);
});
}, function ($result, $db, $debug) {
echo "Table 'shop_products' created.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
10.5.5. SchemaBuilder::alterTable($table, $callback)
Ändert eine vorhandene Tabelle (fügt zum Beispiel eine Spalte hinzu).
Syntax: alterTable(string $table, callable $callback)
Beispiel:
DB::module('RAW')->schema(function ($schema) {
$schema->alterTable('shop_items', function ($table) {
$table->addColumn('age', 'INT', null, true);
});
}, function ($result, $db, $debug) {
echo "Column added.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
10.5.6. SchemaBuilder::dropTable($table)
Löscht eine Tabelle.
Syntax: dropTable(string $table)
Beispiel:
DB::module('RAW')->schema(function ($schema) {
$schema->dropTable('shop_items');
}, function ($result, $db, $debug) {
echo "Table dropped.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
10.6. Praktische Beispiele
Tabellen mit einem Fremdschlüssel erstellen:
DB::module('RAW')->transact(function ($db) {
$db->q(function ($qb) {
$qb->createTable('shop_items', function ($schema) {
$schema->id();
$schema->string('username');
});
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
});
$db->q(function ($qb) {
$qb->createTable('shop_posts', function ($schema) {
$schema->id();
$schema->string('title');
$schema->integer('user_id');
$schema->foreign('user_id')->references('id')->on('shop_items')->onDelete('CASCADE');
});
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
});
}, function ($result, $db, $debug) {
echo "Tables created.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
Ausgabe:
Tables created.
Eine Tabelle ändern (eine Spalte hinzufügen):
DB::module('RAW')->schema(function ($schema) {
$schema->alterTable('shop_items', function ($table) {
$table->string('email', 100);
});
}, function ($result, $db, $debug) {
echo "Email column added.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
Ausgabe:
Email column added.
Löschen Sie Tabellen aus Installation::uninstaller() mit DB::module('RAW')->q(...)->execute($ok, $err).
10.7. Hinweise
- Transaktionen: Verwenden Sie
transact()auf der Modulinstanz für gesammelte Schemaänderungen, damit die Datenbank konsistent bleibt. Sie können auf dieser Instanz auchtransaction(),commit()undrollback()aufrufen. - Treiberunterstützung: Die Syntax wird an den Treiber angepasst (zum Beispiel MySQL vs. SQLite), einige Funktionen (zum Beispiel
ON UPDATEunter Oracle) sind jedoch möglicherweise nicht vollständig unterstützt. - Schema installieren: Erstellen Sie Modultabellen in
Installation.phpmitself::alreadyDone/self::markDone. Rufen Sie niemalsDB::migrate()auf. - Fehler-Callbacks: Übergeben Sie an
schema(),execute()undtransact()immer einen Fehler-Callback.
11. Fallstudie: E-Shop mit ORM
Dieses Kapitel ist eine praktische Fallstudie, die zeigt, wie Sie Databaser und sein ORM nutzen, um einen einfachen E-Shop aufzubauen. Wir entwerfen die Datenbankstruktur, erstellen Tabellen, füllen sie mit Daten und arbeiten mit diesen Daten über Entity und Collection. Die Beispiele enthalten error-Callbacks, damit Sie eine robuste Fehlerbehandlung anwenden können.
11.1. Die Datenbankstruktur entwerfen
Für den E-Shop verwenden wir diese Tabellen:
- shop_customers: Kunden und Administratoren.
- shop_products: Produkte im Katalog.
- shop_product_descriptions: Produktbeschreibungen (ein Produkt kann mehrere haben, zum Beispiel in unterschiedlichen Sprachen).
- shop_orders: Bestellungen.
- shop_order_items: Bestellpositionen (Produkte, die mit Bestellungen verknüpft sind).
SQL zum Erstellen der Tabellen
Sie können diese Anweisungen kopieren und in einer MySQL-Datenbank ausführen:
-- Customers
CREATE TABLE shop_customers (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
role ENUM('customer', 'admin') DEFAULT 'customer',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Products
CREATE TABLE shop_products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Product descriptions
CREATE TABLE shop_product_descriptions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT UNSIGNED NOT NULL,
language VARCHAR(10) NOT NULL,
description TEXT NOT NULL,
FOREIGN KEY (product_id) REFERENCES shop_products(id) ON DELETE CASCADE
);
-- Orders
CREATE TABLE shop_orders (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT UNSIGNED NOT NULL,
total_price DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'shipped', 'delivered') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES shop_customers(id) ON DELETE CASCADE
);
-- Order items
CREATE TABLE shop_order_items (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NOT NULL,
quantity INT NOT NULL DEFAULT 1,
price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES shop_orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES shop_products(id) ON DELETE CASCADE
);
SQL zum Befüllen mit Daten
Diese Anweisungen füllen die Tabellen mit Beispieldaten:
-- Customers
INSERT INTO shop_customers (name, email, role) VALUES
('Jane Novak', 'jane@example.com', 'customer'),
('Admin Peter', 'admin@example.com', 'admin');
-- Products
INSERT INTO shop_products (name, price, stock) VALUES
('White t-shirt', 15.99, 50),
('Black shoes', 49.99, 20),
('Winter jacket', 89.99, 10);
-- Product descriptions
INSERT INTO shop_product_descriptions (product_id, language, description) VALUES
(1, 'en', 'Comfortable white cotton t-shirt.'),
(1, 'sk', 'Comfortable white cotton t-shirt.'),
(2, 'en', 'Elegant black shoes for any occasion.'),
(3, 'en', 'Warm winter jacket with a hood.');
-- Orders
INSERT INTO shop_orders (customer_id, total_price, status) VALUES
(1, 65.98, 'pending'),
(1, 89.99, 'shipped');
-- Order items
INSERT INTO shop_order_items (order_id, product_id, quantity, price) VALUES
(1, 1, 2, 15.99),
(1, 2, 1, 49.99),
(2, 3, 1, 89.99);
11.2. Implementierung in Databaser mit ORM
ORM-Beispiele verwenden DB::module('ORM'), sichere Lesezugriffe über all() und explizite Beziehungen über hasMany(). Verwenden Sie with() nicht so, als würde es verwandte Zeilen in SQL laden — es erzeugt kein SQL.
11.2.1. Einen Kunden und seine Bestellungen laden
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_customers')->where('id', '=', 1)->limit(1);
})->all();
$customer = $rows[0] ?? null;
$orders = $customer ? $customer->hasMany('shop_orders', 'customer_id') : [];
foreach ($orders as $order) {
echo "Order #{$order->id}: {$order->status}\n";
}
11.2.2. Ein neues Produkt mit Beschreibung hinzufügen
DB::module('RAW')->transact(function ($db) {
$product = $db->newEntity();
$product->table('shop_products');
$product->name = 'Green scarf';
$product->price = 19.99;
$product->stock = 30;
$product->save(
function ($result, $db, $debug) {
$description = $db->newEntity();
$description->table('shop_product_descriptions');
$description->product_id = $db->inserted_id();
$description->language = 'en';
$description->description = 'Warm green scarf for winter.';
$description->save(null, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}, function ($result, $db, $debug) {
echo "Product added.\n";
}, function ($error, $db, $debug) {
echo "Transaction error: {$error['error']}\n";
});
11.2.3. Eine Bestellung mit ihren Positionen anzeigen
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_orders')->where('id', '=', 1)->limit(1);
})->all();
$order = $rows[0] ?? null;
$items = $order ? $order->hasMany('shop_order_items', 'order_id') : [];
foreach ($items as $item) {
$productRows = DB::module('ORM')->q(function ($qb) use ($item) {
$qb->select('name', 'shop_products')->where('id', '=', $item->product_id)->limit(1);
})->all();
$product = $productRows[0] ?? null;
if ($product) {
echo "Item: {$product->name}, quantity: {$item->quantity}\n";
}
}
11.3. Validierung verwenden
Fügen Sie vor dem Speichern eine Validierung für ein Produkt hinzu.
$product = DB::newEntity();
$product->table('shop_products');
$product->setRules([
'name' => ['required', 'string', 'max:100'],
'price' => ['required', 'numeric', 'min:0'],
'stock' => ['integer', 'min:0']
]);
$product->name = 'This English product name is deliberately written to be longer than one hundred characters so that Databaser validation rejects it';
$product->price = -5;
$product->stock = 10;
$product->save(
function ($result, $db, $debug) {
echo "Product saved successfully.\n";
},
function ($error, $db, $debug) {
echo "Validation failed: {$error['error']}\n";
}
);
11.4. Hinweise zur Fallstudie
Transaktionen: Die Nutzung von transact() auf der Modulinstanz hält zusammengehörige Schreibvorgänge konsistent; error-Callbacks melden Fehlschläge.
Beziehungen: Laden Sie verwandte Zeilen mit expliziten Entity-Methoden wie hasMany(). with(), whereHas() und withCount() sind unwirksame Platzhalter und erzeugen kein SQL — sie sind kein Weg, Beziehungen zu laden.
Validierung: Regeln schützen vor ungültigen Daten und erzeugen eine klare Fehlermeldung.
Fehlerbehandlung: error-Callbacks lassen Sie auf Probleme reagieren (zum Beispiel Protokollierung oder Hinweise an Benutzer). Übergeben Sie an Entity::save() immer einen Fehler-Callback.
12. Tipps und Tricks
Dieses Kapitel bietet praktische Hinweise, wie Sie Databaser im DotApp-Framework wirksam einsetzen. Es behandelt Abfrageoptimierung, Sicherheit und Erweiterungspunkte.
12.1. Abfrageoptimierung
Effiziente Abfragen sind der Schlüssel zu einer schnellen Anwendung. Einige Tipps:
- Wählen Sie nur die benötigten Spalten: Statt
select('*', 'shop_items')verwenden Sie konkrete Spalten, zum Beispielselect('id, name', 'shop_items'). Das verringert die übertragene Datenmenge.
DB::module('RAW')->q(function ($qb) {
$qb->select('id, name', 'shop_items')->where('age', '>', 18);
})->execute(
function ($result, $db, $debug) {
var_dump($result);
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
- Verwenden Sie Indizes: Für häufige Filter in
where()(zum Beispiel id, age) fügen Sie Indizes mitschema()hinzu:
DB::module('RAW')->schema(function ($schema) {
$schema->alterTable('shop_items', function ($table) {
$table->index('age');
});
}, function ($result, $db, $debug) {
echo "Index created.\n";
}, function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
});
- Paginieren Sie Listen, die wachsen können: Bevorzugen Sie für wachsende Listen (Artikel, Bestellungen, Logs)
paginate($perPage, $page)gegenüber reinemlimit()/offset(). Verwenden Sielimit()undoffset()nur, wenn Sie einen einmaligen Ausschnitt benötigen.
$page = DB::module('RAW')->q(function ($qb) {
$qb->select('id, name', 'shop_items')->orderBy('id', 'DESC');
})->paginate(20, 1);
foreach ($page['data'] as $row) {
echo "{$row['name']}\n";
}
- Cachen Sie wiederholte Abfragen: Wenn Sie einen Cache-Treiber implementiert haben, verwenden Sie ihn zum Speichern von Ergebnissen:
DB::module('RAW')->cache($myCacheDriver)->q(function ($qb) {
$qb->select('*', 'shop_items');
})->execute(
function ($result, $db, $debug) {
echo "Results from cache or DB: ";
var_dump($result);
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
12.2. Sicherheit (Schutz vor SQL-Injection)
Databaser ist mit Blick auf Sicherheit entworfen, dennoch lohnt es sich, die bewährten Praktiken zu kennen:
- Verwenden Sie immer Prepared Statements: Der
QueryBuilderescaped Werte automatisch, interpolieren Sie daher niemals Variablen in die Abfragezeichenkette.
Richtig:
DB::module('RAW')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('name', '=', 'Jane');
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
});
Falsch:
$name = "Jane'; DROP TABLE shop_items; --";
DB::module('RAW')->q(function ($qb) use ($name) {
$qb->raw("SELECT * FROM shop_items WHERE name = '$name'");
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
}); // Dangerous!
- Raw-Abfragen mit RAW: Wenn Sie
raw()verwenden, übergeben Sie Werte immer über Bindings:
DB::module('RAW')->q(function ($qb) {
$qb->raw('SELECT * FROM shop_items WHERE age > ?', [18]);
})->execute(null, function ($error) {
echo "Error: {$error['error']}\n";
});
- Validierungsregeln im ORM: Wenn Sie Daten über
Entityspeichern, setzen Sie Regeln:
$rows = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items')->where('id', '=', 1)->limit(1);
})->all();
$user = $rows[0] ?? null;
if ($user) {
$user->setRules(['name' => 'required|string|max:50']);
$user->name = 'Jane';
$user->save(
null,
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
}
12.3. Databaser mit eigenen Treibern erweitern
Registrieren Sie einen eigenen Datenbanktreiber mit Databaser::customDriver($name, $class). Die Klasse muss public static function create(Databaser $db) bereitstellen und innerhalb von create() die Treiber-Closures an dieser Instanz registrieren.
DB::addDriver() ist nicht die Modul-API zum Registrieren eines Treibers. Treiberklassen dürfen sie intern aus create() nutzen; Anwendungs- und Modulcode sollte Databaser::customDriver($name, $class) aufrufen.
Zu registrierende Closures: select_db, q, return, execute, first, all, raw, fetchArray, fetchFirst, newEntity, newCollection, inserted_id, affected_rows, schema, transaction, transact, commit, rollback.
Databaser::customDriver('custom', CustomDriver::class);
// CustomDriver::create(Databaser $db) registers the closures listed above.
12.4. ORM-Beziehungen
Laden Sie verwandte Zeilen mit expliziten Entity-Methoden wie hasMany(). with() in DotApp 2.0 erzeugt kein SQL und ist kein Weg, Beziehungen zu laden:
$items = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
foreach ($items as $item) {
foreach ($item->hasMany('shop_posts', 'user_id') as $post) {
echo "Item: {$item->name}, Post: {$post->title}\n";
}
}
12.5. Stapeloperationen mit Collection
Verwenden Sie nicht saveAll(). Speichern Sie nach map() jede Entity einzeln mit save() und übergeben Sie immer einen Fehler-Callback:
$items = DB::module('ORM')->q(function ($qb) {
$qb->select('*', 'shop_items');
})->all();
$items->map(function ($item) {
$item->age += 1;
$item->save(
function ($result, $db, $debug) {
echo "Item saved.\n";
},
function ($error, $db, $debug) {
echo "Error: {$error['error']}\n";
}
);
return $item;
});
Methoden: filter(), map(), pluck().