Filterformular

Aus den Feldern einer Source erzeugt das Paket ein Filterformular. Die Eingaben werden validiert, als Query-Parameter an die Vorschau übergeben und von der Source als WHERE-Klausel oder In-Memory-Filter angewendet.

So entsteht das Formular

Ohne eigenes filterControls() wird pro filterbarem ReportField ein Control erzeugt. Die Route reports.form rendert es; nach «Weiter» geht es an reports.preview.

ReportSource::fields() ReportField[] ist die Allow-List. Nur filterbare Felder erscheinen im Formular.
Report::filterControls() Optionaler Override. Default: FilterBuilder::for($this->source())->build().
Reports::filterSchema($report) Serialisierte Control-Liste für eigene UIs (Blade, Vue, React).
GET /reports/{name} Rendert das Formular. Ohne Controls macht die Route einen Redirect auf /run.

Keine Filter? Kein Formular.

Sind alle Felder filterable: false oder gibt es keine Source-Felder, ist filterControls() leer. Die form-Route leitet dann direkt auf die Vorschau weiter. Das ist kein Fehler, sondern der gewünschte Ein-Klick-Fall.

Live ausprobieren

Das ist das echte, automatisch erzeugte Formular der Preisliste. «Weiter» validiert die Werte und öffnet die Vorschau.

Das iframe zeigt die Formular-Route. Nach dem Absenden läuft es im iframe in die Vorschau; mit «Vorschau öffnen» startest du sie in einem neuen Tab.

Die Filter-Felder

Der Feldtyp bestimmt Widget, Operatoren und Query-Format. Über FilterBuilder lassen sich alle drei pro Feld überschreiben.

ReportField(type: 'string') Textfeld. Operatoren: like, =, !=. Query: ?name=Kaffee oder ?name[operator]=like&name[value]=Kaffee. Mit multiple() mehrere Zeilen.
'number' · 'int' · 'integer' · 'decimal' · 'float' Zahlenfeld mit Operator-Select. Operatoren: =, !=, <, <=, >, >=, between, in. Query: ?price[operator]=between&price[value][]=50&price[value][]=100.
'date' · 'datetime' Datumsfeld mit =, before, after, between, is_null. Query mit ISO-Datum: ?released_at[operator]=after&released_at[value][]=2026-01-01.
'boolean' · 'bool' Ja/Nein-Select. Query: ?active=1 (Ja) oder ?active=0 (Nein).
'enum' mit höchstens 6 Optionen Einfaches Select. Query: ?category=Küche. Intern wird der Operator in verwendet.
'enum' mit mehr als 6 Optionen oder ->multiselect() Aufklappbare Checkbox-Liste. Query: ?category[]=Küche&category[]=Büro.
'id' · 'fk' · 'relation' Suchbarer Dual-List-Picker. Query: ?supplier[]=1&supplier[]=2.
'json' Text-Control ohne Wert für is_null und is_not_null.

Filter als Query-Parameter

Das Formular submitted per GET. Du kannst dieselben URLs auch selbst bauen — etwa für Deeplinks, E-Mails oder ein eigenes Formular.

# text (default operator: like)
/reports/price_list/run?name=Kaffee

# text with an explicit operator
/reports/price_list/run?name[operator]=like&name[value]=Kaffee

# number: greater or equal
/reports/price_list/run?price[operator]=>=&price[value][]=50

# number: between
/reports/price_list/run?price[operator]=between&price[value][]=50&price[value][]=100

# enum / multiselect / picker
/reports/price_list/run?category=K%C3%BCche
/reports/price_list/run?category[]=K%C3%BCche&category[]=B%C3%BCro
/reports/price_list/run?supplier[]=1&supplier[]=2

Die Query-Formate, die das automatische Formular erzeugt.

Wie die Filter angewendet werden

Zwischen Formular und Report liegt der FilterValidator. Er lässt nur bekannte Controls durch und normalisiert Operator und Wert.

FilterValidator Allow-List aus filterControls(): unbekannte Keys werden verworfen, Werte pro Operator normalisiert (Skalar, between-Array, in-Liste, like-Muster).
FilterBag Das normalisierte Ergebnis wird der Source über withFilters() übergeben.
DatabaseSource Eloquent und Query Builder übersetzen die Filter in WHERE und die Sortierung in ORDER BY.
ArraySource · CollectionSource · RawSqlSource Diese Quellen filtern nach dem Laden in PHP; sie nutzen dieselben Operatoren.
$ctx->filters Die aktiven Filter im Band. Über die Paket-Routen sind Werte normalisiert: ["operator" => "in", "value" => ["Küche"]].

Direkte Code-Aufrufe wie Reports::html($report, $filters) umgehen den FilterValidator. Dort gelten nur die Feld-Allow-List der Source und der Default-Operator; validiere eigene Eingaben vorher selbst.

Ein eigenes Formular bauen

Du kannst die Paket-Routen mit einem eigenen Formular ansprechen oder einen eigenen Controller schreiben. Das Formular braucht nur die richtigen Feldnamen und die passende Action.

<form method="GET" action="{{ route('reports.preview', ['name' => 'price_list']) }}" target="_blank">
    <select name="category">
        <option value="">Alle Kategorien</option>
        <option value="Küche">Küche</option>
        <option value="Büro">Büro</option>
    </select>

    <input type="text" name="name" placeholder="Bezeichnung enthält …">

    <select name="price[operator]">
        <option value="=">=</option>
        <option value=">=">≥</option>
        <option value="<=">≤</option>
        <option value="between">zwischen</option>
    </select>
    <input type="number" step="any" name="price[value][]" placeholder="von">
    <input type="number" step="any" name="price[value][]" placeholder="bis">

    <button type="submit">Vorschau öffnen</button>
</form>
use Guggach\Reports\Facades\Reports;
use Guggach\Reports\Filters\FilterValidator;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

final class PriceListController
{
    public function __invoke(Request $request): Response
    {
        $report = Reports::find('price_list');

        // Same allow-list and operator normalisation as the package routes.
        $filters = (new FilterValidator($report->filterControls()))
            ->validate($request->query())
            ->all();

        return response(Reports::html($report, $filters));
    }
}

Dasselbe Formular als Blade-Markup und als eigener Controller.

Das automatische Formular anpassen

Override filterControls() in der Report-Klasse und ändere einzelne Felder mit dem FilterBuilder. Alles andere bleibt automatisch.

->field('price') Wählt ein Feld für die folgenden Anpassungen.
->control('number') Überschreibt das Widget: text, number, boolean, select, multiselect, date oder picker.
->operators([...]) Schränkt die Operatoren ein oder erweitert sie; der erste wird zum Default.
->options([...]) Werte für Select, Multiselect oder Picker.
->multiple() Erlaubt mehrere Werte.
->picker(...) Macht ein ID-/FK-Feld zum suchbaren Picker.
->only([...]) · ->except([...]) Controls gezielt behalten oder entfernen.
->build() Liefert die fertige Liste von FilterControl-Objekten.
use Guggach\Reports\Filters\FilterBuilder;

public function filterControls(): array
{
    return FilterBuilder::for($this->source())
        ->field('price')->operators(['=', '>=', '<=', 'between'])
        ->field('category')->multiselect()
        ->field('supplier')->picker(valueField: 'id', labelField: 'label')
        ->build();
}

Drei Felder anpassen und den Rest automatisch erzeugen lassen.

Die vollständige Referenz zu FilterBuilder, Operatoren und Feldtypen steht unter Datenquellen. Für ein komplett eigenes Formular brauchst du filterControls() nur, wenn du die Paket-Routen oder den FilterValidator weiterverwenden willst.