K

Tabulka

Zdroje dat

Odkud tabulka bere řádky, když nejsou v databázi — kontrakt zdroje a co od něj tabulka pořád očekává.

Na této stránce

Tabulka čte řádky přes zdroj dat. Ve výchozím stavu ten zdroj obaluje Eloquent builder a nikdy ho neuvidíte — ->model() a ->query() fungují přesně jako dosud. Vlastní zdroj deklarujete, když řádky nejsou v databázi: read model, seznam DTO, odpověď API.

Mechanismus

Čtení tabulky jsou tři otázky a zdroj odpovídá na všechny: které řádky odpovídají (count), tahle jejich stránka (paginate) a řádek za tímhle klíčem (resolveRecord). Zdroj dostane QueryPlan — hledání, filtry, řazení a joiny, které tabulka vyřešila ze stavu — a PagingRequest pro výřez.

Plán je deklarativní. Filtr je sloupec, operátor a hodnota, nikdy closure, a právě to dělá zdroj mimo databázi vůbec možným: umí plán přečíst a odpovědět na něj metodami Collection místo SQL.

Na co zdroj odpovědět neumí, to deklaruje, že neumí — a engine pak vyhodí výjimku místo toho, aby vrátil řádky, které tiše ignorovaly půlku dotazu:

public function capabilities(): CapabilitySet
{
return new CapabilitySet(
Capability::Filterable,
Capability::Sortable,
Capability::Paginable,
); // žádné SqlExpression, žádné Joinable — dotaz na ně vyhodí výjimku
}

To je celá bezpečnostní vlastnost. Tabulka nad API, které neumí řadit, to musí říct — tabulka, která řadí podle ničeho a vypadá seřazeně, je horší než ta, která zahlásí chybu.

Tabulka nad řádky v paměti

use NyonCode\WireTable\Data\CollectionDataSource;
 
public function table(Table $table): Table
{
return $table
->dataSource(new CollectionDataSource([
['id' => 1, 'name' => 'Ada', 'score' => 90],
['id' => 2, 'name' => 'Grace', 'score' => 70],
]))
->columns([
TextColumn::make('name')->searchable()->sortable(),
TextColumn::make('score')->sortable(),
]);
}

Žádné ->model(), žádné ->query(). Hledání, filtry, řazení i stránkování fungují; zdroj na ně odpoví nad polem.

Co tabulka nad kolekcí neumí

CollectionDataSource deklaruje čtyři schopnosti a zbytek odmítá. Každé odmítnutí vyhodí UnsupportedQueryAspectException se jménem aspektu:

Když chcete Odkud to přijde Výsledek
Raw SQL výraz Column::sortUsing() se SQL, ->sqlExpression() vyhodí [sql_expression]
Cestu přes relaci TextColumn::make('company.name') vyhodí [joinable]
Agregace přes subquery rollupy ->sums(), ->counts() vyhodí [aggregateable]
Cursor stránkování ->paginationMode('cursor') vyhodí [cursor paging]
Detekci změny pro polling ->poll() vrátí null; polling místo toho porovná řádky

Nic z toho nejsou omezení, která by se dala tiše obejít. Tabulka nad seznamem v paměti je omezená tabulka a to omezení je vidět hned při první žádosti o něco mimo něj.

Vlastní zdroj

Implementujte NyonCode\WireCore\Core\Data\DataSource. Každá metoda dostane plán, takže zdroj rozhoduje, kolik z něj splní:

use NyonCode\WireCore\Core\Data\DataSource;
 
final class ReportingApiSource implements DataSource
{
public function paginate(QueryPlan $plan, PagingRequest $paging): LengthAwarePaginator|Paginator|CursorPaginator;
public function get(QueryPlan $plan): Collection;
public function chunk(QueryPlan $plan, int $size, callable $callback): void;
public function count(QueryPlan $plan): int;
public function resolveRecord(int|string $key): ?RecordContract;
public function resolveRecords(array $keys): Collection;
public function capabilities(): CapabilitySet;
public function changeToken(QueryPlan $plan): ?string;
}

Dvě z nich stojí za vysvětlení, protože jejich tvar není samozřejmý:

chunk() existuje odděleně od get(), aby export sta tisíc řádků zůstal ohraničený v paměti. get() materializuje; chunk() streamuje a zastaví se dřív, když callback vrátí false.

changeToken() smí vrátit null a null je skutečná odpověď, ne selhání: znamená, že zdroj nemá levný způsob, jak zjistit, že se data pohnula — polling tedy porovná řádky sám místo zkratky přes token.

Záznamy

Zdroj vrací řádky jako RecordContractgetKey(), get('dot.path'), toArray(), unwrap(). Ve stacku jsou dvě implementace: EloquentRecord obaluje model, ArrayRecord pole.

Váš vlastní kód dál dostává modely. Framework rozbaluje na hranici, takže closure akce si drží svoji obvyklou signaturu:

Action::make('archive')
->action(fn (Model $record) => $record->archive());

Důsledek stojí za to říct rovnou: nad zdrojem, kde není co rozbalit, taková akce k dispozici není. Je to táž degradace, jakou uplatňuje dotazová strana, jen o úroveň výš.

Co zůstává jen pro Eloquent

Dvě cesty si záměrně nechávají builder místo kontraktu a obě jsou případy, které kontrakt vyjádřit neumí:

  • Rollupy nad výběrem replikují agregační subquery na klíčovaný dotaz.
  • Zápisy fill handlu berou pesimistický zámek řádku (SELECT … FOR UPDATE).

Vlastní zdroj nedostane ani jedno.

Viz také

  • Exporty — streamují přes chunk() zdroje
  • Souhrny — agregace, které ne-SQL zdroj odmítne
  • Výběr — hromadné akce a resolveRecords()