Individuelle SQL-Reports
Ein Grid mit einem beliebigen, schreibgeschützten SELECT-Statement über query() betreiben - für Reports, die sich nicht auf eine einzelne Tabelle abbilden lassen.
table() vs. query()
->table($name) betreibt ein Grid mit einer echten, beschreibbaren Tabelle. ->query($sql) ist die Alternative für alles, was sich nicht auf eine einzelne Tabelle abbilden lässt - Joins, Aggregate, berechnete Spalten über mehrere Tabellen hinweg:
public function query(string $sql): self
$sql wird unverändert übernommen und von der API-Schicht als abgeleitete Tabelle (Derived Table) eingebunden - dieselbe Vertrauensstufe, die auch die rohen SQL-Escape-Hatches von where_raw()/subselect() haben. Es muss ein einfacher String sein, den Sie selbst im PHP-Code Ihrer Seite geschrieben haben - niemals etwas, das aus Request-Eingaben zusammengebaut wird.
Der Report-Modus ist schreibgeschützt
Ein Grid im Query-Modus hat kein Hinzufügen/Bearbeiten/Löschen, keine Bulk-Aktionen und kein Inline-Editing - es ist von Natur aus schreibgeschützt, da es keine einzelne Tabelle gibt, auf die ein Schreibvorgang eindeutig zielen könnte. Dies wird sowohl clientseitig als auch serverseitig durchgesetzt: Die API lehnt POST/PUT/DELETE gegen eine Route im Query-Modus rundheraus ab, unabhängig davon, was der Client sendet. Nur Auflisten/Suchen/Sortieren/Paginieren/Exportieren bleiben übrig.
columns() und route() sind weiterhin erforderlich
Ein Grid im Query-Modus öffnet nie eine eigene Datenbankverbindung, um Spalten zu ermitteln - seine Spaltenliste ist ausschließlich das, was ->columns() vorgibt. ->route() ist ebenfalls erforderlich, da es keinen Tabellennamen gibt, aus dem sich eine Route ableiten ließe.
Ein Beispiel mit JOIN
$xcrud = Xcrud::get_instance();
$xcrud->route('order-summary');
$xcrud->query(
'SELECT o.orderNumber, o.orderDate, o.status, c.customerName, c.country,
COUNT(od.id) AS itemCount,
SUM(od.quantityOrdered * od.priceEach) AS orderTotal
FROM orders o
JOIN customers c ON c.customerNumber = o.customerNumber
JOIN orderdetails od ON od.orderNumber = o.orderNumber
GROUP BY o.orderNumber, o.orderDate, o.status, c.customerName, c.country'
);
$xcrud->columns('orderNumber,orderDate,status,customerName,country,itemCount,orderTotal');
$xcrud->order_by('orderTotal', 'desc');
$xcrud->title('Order Summary Report');
echo $xcrud->render();
Die Spaltenaliase in $sql und die Namen in ->columns() werden nie gegeneinander geprüft (beim Rendern findet kein Datenbankzugriff statt) - eine Abweichung zeigt sich erst als Fehler bei der ersten echten Anfrage an das Grid, nicht als Render-Zeit-Exception wie es ein unbekannter Spaltenname in order_by()/label()/where() auslösen würde. Prüfen Sie die beiden Listen von Hand gegen.
Ein abschließendes ORDER BY innerhalb von $sql ohne LIMIT kann beim Einbetten als abgeleitete Tabelle stillschweigend entfallen - unproblematisch, da die eigene Sortierung des Grids bzw. ->order_by() ohnehin stets ein äußeres ORDER BY anwendet, aber verlassen Sie sich nicht darauf, dass ein inneres von selbst etwas bewirkt.