⌘K

Tabulka

Cesty relací a tečková notace

Tečková notace přes relace, agregace, pivoty a morfy — a joiny, eager loady a poddotazy, na které se změní.

Na této stránce

Názvy sloupců podporují hlubokou tečkovou notaci pro relace, agregáty, pivoty a morphy. Core parser Relation AST automaticky určuje JOINy, eager loady a podotázky.

Jednoduchá relace

TextColumn::make('author.name') // belongsTo nebo hasOne
TextColumn::make('category.title')

Vnořené relace

TextColumn::make('author.country.name') // 3 úrovně hluboko
TextColumn::make('order.customer.company.name') // 4 úrovně hluboko

Agregáty

TextColumn::make('orders.count') // withCount
TextColumn::make('items.sum.amount') // withSum
TextColumn::make('ratings.avg.score') // withAvg
TextColumn::make('bids.min.amount') // withMin
TextColumn::make('bids.max.amount') // withMax

Sloupec může rollup relace deklarovat i přímo, včetně případu, kdy zobrazený názvem sloupce není název agregačního atributu:

->counts(string $relationship): static
->sums(string $relationship, string $column): static
->averages(string $relationship, string $column): static
->mins(string $relationship, string $column): static
->maxes(string $relationship, string $column): static
->isAggregate(): bool
->getAggregateFunction(): ?string
->getAggregateRelation(): ?string
->getAggregateColumn(): ?string
->getAggregateAttribute(): ?string

Konvence atributu je {relationship}_{function} pro počty a {relationship}_{function}_{column} pro ostatní agregáty. Například sums('orders', 'total') čte orders_sum_total. Tyto metody deklarují hodnoty rollupu pro každý záznam; agregace v patičce se nastavuje zvlášť přes summarize(). Viz Souhrny.

Pivot data

TextColumn::make('tags.pivot.sort_order')
TextColumn::make('roles.pivot.assigned_at')->dateTime()

Morph relace

TextColumn::make('commentable.title') // polymorfní

Eager loading hodnot z closure

Query planner odvodí relace z cesty sloupce, například company.name. Neumí prohlédnout closure použitou v displayUsing(), actionUrl() nebo callbacku barvy. Relace používané pouze uvnitř takových closures deklarujte, abyste se vyhnuli línému načtení na každém řádku:

TextColumn::make('company')
->displayUsing(fn ($state, $record) => $record->company->name)
->loadRelations('company');
->loadRelations(string|array $relations): static
->getEagerLoadRelations(): array

loadRelations() přijímá jeden název relace nebo seznam. Opakovaná volání názvy sloučí a deduplikují. Je potřeba jen pro relace skryté uvnitř closures; relace pojmenované tečkovou cestou sloupce se odvozují automaticky.

Řazení a filtrování podle singulární relace

Singulární relace — belongsTo, hasOne nebo hasOneThrough — se řeší LEFT JOINem, takže řazení i filtrování proběhne v SQL proti přijoinované tabulce: žádná whereHas podotázka, žádné filtrování v paměti. Vnořené singulární řetězce se joinují segment po segmentu (company.country.name → dva zřetězené joiny) a hasOneThrough se sám rozpadne na dva joiny (base → mezitabulka → cíl).

// Řazení: označ relační sloupec jako sortable.
TextColumn::make('company.name')->sortable();
 
// Filtrování: SelectFilter podporuje tečkovou cestu přímo ve jméně.
SelectFilter::make('company.name')
->options(['Acme' => 'Acme', 'Globex' => 'Globex']);
 
// Ekvivalent, pokud chceš mít klíč stavu filtru plochý ('company') a přitom
// cílit na relační sloupec:
SelectFilter::make('company')
->column('company.name')
->options(['Acme' => 'Acme', 'Globex' => 'Globex']);

Obojí se přeloží na stejný join:

select "users".*
from "users"
left join "companies" as "users_company" on "users"."company_id" = "users_company"."id"
where "users_company"."name" = ? -- filtr
order by "users_company"."name" asc -- řazení

Takto se joinují jen singulární relace (belongsTo, hasOne, hasOneThrough). To-many relace — hasMany, belongsToMany, hasManyThrough, morphMany — a morph cíle se pro zobrazení načítají eager loadem a přes join se řadit ani filtrovat nedají (join by násobil řádky rodiče).

Scopes a constraints relace (vč. soft delete)

Přijoinovaná strana odpovídá tomu, co vrátí Eloquentova relační query. Relace nesoucí jakýkoli constraint — global scopes modelu (SoftDeletes, tenancy, published/active flag, cokoli přes addGlobalScope()) nebo constraints deklarované přímo na metodě relace (belongsTo(...)->where('active', true)) — se přijoinuje jako scoped subquery:

left join (
select * from "companies"
where "companies"."deleted_at" is null -- SoftDeletes global scope
and "companies"."active" = ? -- ->where(...) na relaci
) as "users_company" on "users"."company_id" = "users_company"."id"

LEFT JOIN zůstává LEFT JOINem: rodič, jehož related řádek je scopem odfiltrován, se pořád zobrazí — related hodnota se bere jako prázdná (řadí/filtruje se jako NULL). U hasOneThrough se scopuje mezitabulka i cílový model. Relace bez scopes a constraints si drží prostý přímý join na tabulku.

Meze: constraints na metodě relace platí pro belongsTo/hasOne, ale ne pro hasOneThrough (tam se aplikují jen global scopes jeho modelů). Constraint musí být samostatný — korelovaný na rodičovský řádek (whereColumn('companies.x', 'users.y')) se jako subquery vyjádřit nedá. morphOne se přes join nescopuje (načítá se pro zobrazení eager loadem).

Jak to funguje

  1. RelationPath::parse('author.country.name') vyprodukuje [RelationSegment('author'), RelationSegment('country'), ColumnSegment('name')]
  2. QueryPlanner postaví RelationGraph určující optimální strategii přístupu
  3. Singulární belongsTo / hasOne / hasOneThrough relace → LEFT JOIN (umožňuje řazení i filtrování; hasOneThrough přes dva joiny s mezitabulkou)
  4. HasMany/belongsToMany/hasManyThrough/morphMany → eager load (jen zobrazení)
  5. Agregáty → withCount() / withSum() podotázky
  6. Pivot → JOIN mezitabulky