Začínáme
Autorizace
Každé odmítnutí jde přes laravelí Gate — na co se ho ptá tabulka, formulář, akce a stránka, a kdy.
Na této stránce
Wire používá Laravel Gate a policies. Autorizace tak zůstává kompatibilní s nativními Laravel policies, Spatie Permission a balíčky, které registrují abilities do Gate.
Sdílená pravidla komponent
Sloupce, filtry, akce, pole a widgety mohou používat sdílené autorizační metody, když komponenta podporuje viditelnost nebo autorizaci.
Action::make('approve') ->label('Approve') ->authorize('approve') ->action(fn (Order $record) => $record->approve()); TextColumn::make('internal_note') ->label('Internal note') ->permission('orders.internal-notes.view'); SelectFilter::make('department_id') ->authorizeUsing(fn (User $user) => $user->is_admin);
Pořadí vyhodnocení:
| Pravidlo | Chování |
|---|---|
| Žádná autorizace nakonfigurovaná | Povoleno |
| Žádný přihlášený uživatel | Zamítnuto |
authorizeUsing() |
Vlastní callback má prioritu |
authorize() |
Kontroluje Laravel Gate ability |
permission() |
Kontroluje permission řetězec přes Gate |
Autorizace po záznamech
Callback authorizeUsing() dostává přihlášeného uživatele a — kde ho povrch má — záznam řádku, takže autorizaci lze zúžit na jednotlivé záznamy:
Action::make('approve') ->authorizeUsing(fn (User $user, $record) => $user->id === $record?->manager_id) ->action(fn (Order $record) => $record->approve());
Záznam je přítomný u řádkových akcí; je null u povrchů bez záznamu (strukturální viditelnost sloupce/filtru, pole, widgety), takže jednoargumentová closura fn ($user) => … zůstává platná všude.
Tohle řídí, zda celý sloupec/akce strukturálně existuje (vyhodnoceno jednou). Pro skrytí nebo redakci jedné buňky na řádek — např. zobrazit salary jen na záznamech, které uživatel smí vidět — použijte místo toho visibleForRecord() sloupce, který běží při renderu buňky se záznamem daného řádku:
TextColumn::make('salary') ->visibleForRecord(fn ($record) => auth()->user()->can('viewSalary', $record));
Policies tabulky
Zapněte kontroly policies na tabulce pomocí authorize().
use NyonCode\WireTable\Table; public function table(Table $table): Table{ return $table ->model(Order::class) ->authorize() ->columns([ // ... ]);}
Wire kontroluje tyto policy metody, když jsou potřeba:
| Schopnost tabulky | Policy ability |
|---|---|
| Vytvořit záznam | create |
| Aktualizovat záznam | update |
| Smazat záznam | delete |
| Zobrazit záznam | view |
Přepisy tabulky
Přepisy použijte, když tabulka potřebuje pravidla odlišná od policy modelu.
return $table ->model(Order::class) ->authorize() ->authorizeCreate(fn () => auth()->user()?->can('create', Order::class) ?? false) ->authorizeUpdate(fn (Order $record) => ! $record->is_locked) ->authorizeDelete(fn (Order $record) => $record->status === 'draft') ->authorizeView(fn (Order $record) => $record->tenant_id === auth()->user()?->tenant_id);
Každý přepis přijímá boolean nebo closuru.
Inline editace
Editovatelné sloupce mohou pro inline editaci vyžadovat Gate ability.
TextInputColumn::make('price') ->authorizeInline('orders.update-price');
Pokud uživatel neprojde kontrolou ability, sloupec zůstane viditelný, ale inline editace je zamítnuta.
Akce
Akce lze skrýt nebo zamítnout pomocí Gate abilities, permission řetězců nebo vlastních callbacků.
Action::make('refund') ->label('Refund') ->authorize('refund') ->visible(fn (Order $record) => $record->is_paid) ->requiresConfirmation() ->action(fn (Order $record) => $record->refund());
Pro jednoduché permission řetězce:
Action::make('export') ->permission('orders.export') ->action(fn () => $this->exportTable());
Formuláře
Formuláře mohou používat policies modelu pro create a update.
use NyonCode\WireForms\Forms\Form; public function form(Form $form): Form{ return $form ->model($this->user ?? User::class) ->authorize() ->schema([ // ... ]);}
Když je authorize() zapnuto:
| Stav formuláře | Policy ability |
|---|---|
| Třída modelu nebo neuložený model | create |
| Existující instance modelu | update |
Při zamítnutí je formulář read-only a nelze ho uložit.
Pro vlastní pravidla formuláře:
return $form ->model($this->user) ->authorizeUsing(fn (User $user) => $user->hasRole('editor')) ->schema([ // ... ]);
Sortable
Sortable operace by měly být chráněné v hoocích vaší Livewire komponenty.
public function beforeRowsReordered(array $orderedIds): void{ $this->authorize('reorder', Task::class);}
Hooky životního cyklu viz Sortable řazení řádků.
Související dokumentace
| Dokument | Co pokrývá |
|---|---|
| Core Akce | Řádkové, hromadné, hlavičkové akce a modální akce |
| Přehled tabulek | Nastavení tabulky a API na úrovni tabulky |
| Přehled formulářů | Nastavení formuláře a chování ukládání |
| Audit Log | Záznam změn modelů po úspěšné autorizaci |
Multi-tenancy
Ve výchozím stavu vypnutá — většina aplikací má jednoho tenanta a scopovat je by
byla WHERE klauzule koupená za nic. Jakmile je zapnutá, je striktní.
// config/wire-core.php'tenancy' => [ 'enabled' => true, 'column' => 'tenant_id',],
Naváž resolver; výchozí odpovídá null, což se zapnutou tenancy znamená prázdnou stránku, dokud to neuděláte:
use NyonCode\WireCore\Core\Tenancy\Contracts\TenantResolver; app()->bind(TenantResolver::class, fn () => new class implements TenantResolver { public function resolve(): int|string|null { return auth()->user()?->tenant_id; }});
Pak označ modely, které tenantovi patří:
use NyonCode\WireCore\Core\Tenancy\Concerns\BelongsToTenant; class Invoice extends Model{ use BelongsToTenant; }
Zapíná se pro každý model zvlášť, protože framework nemůže vědět, které z vašich tabulek tenant vlastní, a hádat by znamenalo hádat, kdo co smí vidět.
Fail-safe
Zapnutá tenancy bez resolvovaného tenanta vrací nic, nikdy vše.
To je celý bezpečnostní příběh v jedné větě. Každý běžný stav vyrobí null
tenanta — před přihlášením, na queue workeru, v konzolovém příkazu — takže scope,
který by null četl jako „bez omezení“, by každému z nich podal všechny řádky.
Místo toho omezí na 0 = 1.
Záměrně to není ani where tenant_id is null: řádek, který nikdo nevlastní, by
pak viděli všichni, což je tentýž únik v jiném kabátě.
Co je scopované
Globální scope, ne plugin hook — a ta volba je pointa. Hook pokryje jednu čtecí cestu, a jen když je zrovna navázaný plugin manager. Globální scope pokryje každý dotaz, který Eloquent postaví:
| Scopované | |
|---|---|
Invoice::query(), výpis tabulky, relace |
ano |
Invoice::find($id) ve vašem vlastním controlleru |
ano |
->update() a ->delete() |
ano |
| Frontovaný job resolvující záznamy podle klíče | ano |
Invoice::create() |
připíše se aktuálnímu tenantovi |
Create bez resolvovaného tenanta vyhodí výjimku místo zápisu řádku s null tenantem — takový řádek by pak byl neviditelný pro každý scopovaný dotaz, takže by uživatelova práce byla pryč a nikdo by to neřekl. Explicitně nastavený sloupec se nechá být, kvůli seederu nebo záměrnému přesunu mezi tenanty.
Sloupec je kvalifikovaný, protože scopovaný model se běžně joinuje a joinovaná
tabulka mívá vlastní tenant_id.
Čtení napříč tenanty
Invoice::acrossAllTenants()->count();
Záměrně upovídané a snadno greppovatelné: admin report nebo konzolový příkaz má reálnou potřebu a revize musí najít každé místo, které si ji nárokovalo.
Zdroj, na který Eloquent nedosáhne
Non-Eloquent DataSource — CollectionDataSource, zdroj nad API — nestaví
žádný Eloquent dotaz, takže na něj globální scope nedosáhne. Obalte ho:
use NyonCode\WireCore\Core\Tenancy\TenantScopedDataSource; $source = new TenantScopedDataSource(new CollectionDataSource($rows), app(Tenancy::class));
Omezuje plán, ne řádky, které se vrátí — a právě proto je bezpečný na všech
metodách, ne jen na té, která řádky vrací: count() a paginate() odpovídá
zdroj, aniž by řádky vůbec vydal, a zdroj, který ten filtr neumí, ho hlasitě
odmítne (UnsupportedQueryAspectException) místo aby vrátil řádky, které nikdo
neomezil.
resolveRecord() bere klíč, ne plán, takže tam se záznam načte a zkontroluje —
bez toho se tenant dostane na cizí řádek tím, že si do URL napíše jeho id.
Fail-safe je tentýž, jaký má TenantScope: tenancy zapnutá a žádný tenant
rozpoznaný znamená nic. Vypnutá tenancy deleguje beze změny, takže obalení
zdroje jednotenantovou aplikaci nic nestojí.
Třetím argumentem předáte sloupec tam, kde si ho zdroj pojmenoval jinak než konfigurace.