> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Policies registreren en overschrijven in packages

> Lees de Gate-implementatie van Laravel 13: policies registreren in een package, ontwerpen zonder afhankelijkheid van het gebruikersmodel, de resolutievolgorde, en overschrijven en testen in de gebruikende applicatie.

Soms wil je autorisatie toevoegen aan de modellen die een package levert, terwijl de gebruikende applicatie de ruimte houdt om die te vervangen door eigen regels. Als je de koppeling tussen model en policy expliciet vastlegt en de gebruiker pas beoordeelt op het moment dat de autorisatie wordt uitgevoerd, scheid je de registratie van de beslissingen per request.

Deze pagina verwijst naar de standaardbranch `13.x` van de officiële Laravel-documentatie en naar de officiële implementatie en tests van de nieuwste Framework-release `v13.42.0`, gecontroleerd op 10 november 2026. Zie de [autorisatiegids](https://kawax.biz/nl/authorization.md) voor de basis van autorisatie en [package auto-discovery](https://kawax.biz/nl/advanced/package-discovery.md) voor het laden van providers.

## Niet afhankelijk zijn van de User-klasse van de applicatie

Als voorbeeld voegen we `update`-autorisatie toe aan `Acme\Reports\Models\Report` uit het package. We gaan ervan uit dat de tabel `acme_reports` een kolom `owner_id` heeft waarin de authenticatie-identifier van de eigenaar staat. Hoe je migraties levert, lees je in [packagemigraties](https://kawax.biz/nl/advanced/package-migrations.md).

`src/Models/Report.php` is een gewoon Eloquent-model.

```php theme={null}
<?php

namespace Acme\Reports\Models;

use Illuminate\Database\Eloquent\Model;

class Report extends Model
{
    protected $table = 'acme_reports';
}
```

Als de policy van het package `App\Models\User` als type opgeeft, wordt die afhankelijk van de namespace en modelstructuur van de gebruikende applicatie. Heb je alleen de authenticatie-identifier van de gebruiker nodig, gebruik dan het Laravel-contract `Authenticatable`.

Zet het volgende in `src/Policies/ReportPolicy.php`.

```php theme={null}
<?php

namespace Acme\Reports\Policies;

use Acme\Reports\Models\Report;
use Illuminate\Contracts\Auth\Authenticatable;

class ReportPolicy
{
    public function update(Authenticatable $user, Report $report): bool
    {
        $userId = $user->getAuthIdentifier();
        $ownerId = $report->getAttribute('owner_id');

        return $userId !== null
            && $ownerId !== null
            && (string) $userId === (string) $ownerId;
    }
}
```

Eerst worden `null`-identifiers uitgesloten. Het typeverschil tussen een integer uit de database en een string wordt opgevangen door beide als string te vergelijken. In dit voorbeeld mag alleen de eigenaar bijwerken. In applicaties met meerdere gebruikerstypen of guards controleer je ook of de identifier alleen het subject uniek identificeert. Zo niet, vervang de policy dan door een eigen policy die de grenzen van gebruikerstype of tenant meeneemt.

> [!NOTE]
>
> `Authenticatable` is een contract voor onder meer de authenticatie-identifier. Het biedt geen `isAdministrator()` of methoden om rollen op te halen. Heb je packagespecifieke rechteninformatie nodig, definieer dan een apart contract en laat de gebruikende applicatie een implementatie injecteren.

## De koppeling registreren in een serviceprovider

Registreer in `boot()` van `src/ReportsServiceProvider.php` alleen de modellen die het package bezit.

```php theme={null}
<?php

namespace Acme\Reports;

use Acme\Reports\Models\Report;
use Acme\Reports\Policies\ReportPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;

class ReportsServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Gate::policy(Report::class, ReportPolicy::class);
    }
}
```

`Gate::policy()` slaat de klassenaam van de policy op met de klassenaam van het model als sleutel. De registratie zelf maakt geen policy aan, voert geen databasequery uit en beoordeelt geen gebruiker. Pas wanneer de policy nodig is, bijvoorbeeld via `Gate::getPolicyFor()`, wordt die met `make()` van de container aangemaakt. Daardoor kun je afhankelijkheden in de constructor van de policy injecteren.

De plaats van `Report` en `ReportPolicy` past ook bij de standaard auto-discovery, maar hier wordt expliciet geregistreerd om duidelijk te maken welke koppeling het package levert. Registratie alleen past geen autorisatie toe op HTTP-endpoints. Roep vóór het bijwerken bijvoorbeeld in een controller het volgende aan.

```php theme={null}
use Acme\Reports\Models\Report;
use Illuminate\Support\Facades\Gate;

$report = Report::query()->findOrFail($id);

Gate::authorize('update', $report);
```

Bij weigering wordt een `AuthorizationException` gegooid, die bij normale HTTP-afhandeling een 403-response oplevert. De argumenten van de policymethode verschillen tussen `update`, dat een model ontvangt, en abilities zoals `create`, die de klassenaam van het model ontvangen. Volg de aanroepwijze uit de [autorisatiegids](https://kawax.biz/nl/authorization.md).

## De resolutievolgorde van policies lezen

`Gate::getPolicyFor()` zet een object om naar zijn klassenaam en zoekt in de volgende volgorde. Dit is **de volgorde waarin een policy wordt gekozen**, niet de uitvoeringsvolgorde van alle autorisatiehooks.

| Volgorde | Waar naar gezocht wordt |
| - | - |
| 1 | Een expliciete `Gate::policy()`-registratie voor de doelklasse zelf |
| 2 | `#[UsePolicy]` op de doelklasse zelf |
| 3 | Afleiding via de standaard naamgevingsconventie of `guessPolicyNamesUsing()` |
| 4 | Expliciete registraties voor ouderklassen en geïmplementeerde interfaces |
| 5 | `#[UsePolicy]`, gezocht via overerving van ouderklassen |

Zolang er dus een expliciete registratie voor dezelfde klasse `Report` bestaat, wordt de policy niet vervangen als je alleen `#[UsePolicy]` of de naamgeving aanpast. Gebruikt de applicatie daarentegen een model dat `Report` uitbreidt, dan gaat een policy die via het eigen attribuut van de kindklasse of via afleiding wordt gevonden vóór de expliciete registratie op de ouder. Controleer het ook met de modelklasse die je daadwerkelijk gebruikt.

> [!WARNING]
>
> `guessPolicyNamesUsing()` voegt geen callback toe aan een lijst, maar vervangt de ene afleidingscallback van de Gate. Als een onafhankelijk package die alleen voor zijn eigen modellen instelt, beïnvloedt dat ook de afleiding van de applicatie en van andere packages. Gebruik als basis `policy()`-registraties voor de modellen die het package bezit.

## Expliciet overschrijven in de applicatie

Roep je `policy()` opnieuw aan voor hetzelfde model, dan vervangt de latere registratie de koppeling. Het simpelweg in de `boot()` van een andere provider zetten hangt af van de laadvolgorde. Een mogelijkheid is om vanuit `AppServiceProvider::boot()` van de applicatie een callback te registreren voor wanneer de applicatie klaar is met opstarten.

```php theme={null}
use Acme\Reports\Models\Report;
use App\Policies\ApplicationReportPolicy;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    $this->app->booted(static function (): void {
        Gate::policy(Report::class, ApplicationReportPolicy::class);
    });
}
```

`ApplicationReportPolicy` is een policy die de applicatie implementeert. Registreert het package in een normale `boot()`, dan draait deze callback daarna. Het is geen mechanisme dat onvoorwaardelijk voorrang krijgt, ook niet boven herregistraties in andere booted-callbacks of later geladen providers. De registrerende provider van het package is in dit voorbeeld geen deferred provider.

Als alternatief kun je de koppeling ongewijzigd laten en in de container vervangen waarnaar `ReportPolicy::class` wordt opgelost. `resolvePolicy()` vraagt de container elke keer om de policy op te lossen, maar of je echt een nieuwe instantie krijgt, hangt af van de binding van die policy. Vermijd ontwerpen die gebruikers- of requestinformatie in de constructor ophalen en in een singleton-policy bewaren.

## Hooks die alle autorisatie raken gescheiden houden

Bij een normale Gate-controle draait eerst de globale `Gate::before()`. Geeft die iets anders dan `null` terug, dan wordt de beoordeling door de policy niet uitgevoerd.

```mermaid theme={null}
flowchart TD
    A["Gate::authorize / allows"] --> B["Globale Gate::before"]
    B --> C{"Is de returnwaarde null?"}
    C -->|No| D["Dat resultaat gebruiken"]
    C -->|Yes| E["Doel-policy en<br>aanroepbare methode zoeken"]
    E --> F["before van de policy aanroepen indien aanwezig"]
    F --> G["Als before null is,<br>beslist de policymethode"]
```

Het diagram toont alleen het pad waarin een policy wordt gevonden en de bijbehorende methode kan worden aangeroepen. De globale `after`-hooks daarna en de terugval op Gate-definities wanneer de policy de doelmethode niet heeft, zijn weggelaten.

Registreer geen globale `Gate::before()` die alle abilities toestaat, alleen om beheerdersafhandeling binnen je package te implementeren. Daarmee sta je ook de autorisatie van andere packages en van de applicatie toe. Een `before()` binnen de policy geldt alleen voor die policy, maar wordt niet aangeroepen als de methode voor de ability ontbreekt. Wil je de volgende methode laten beslissen, geef dan `null` terug in plaats van `false`.

Daarnaast vereist `update()` in dit voorbeeld een niet-nullable `Authenticatable`, waardoor de policymethode voor gasten niet wordt uitgevoerd. Zonder overschrijving van het resultaat door bijvoorbeeld globale hooks wordt de request geweigerd. Wil je gasten toestaan, bijvoorbeeld voor openbaar bekijken, voorzie dan apart in een nullable gebruikersargument en een expliciete controle.

## Registratie en beoordeling apart testen

We gaan uit van de Testbench-basisklasse uit [packagetests](https://kawax.biz/nl/advanced/package-testing.md), die `ReportsServiceProvider::class` laadt in `getPackageProviders()`. Hieronder staat een voorbeeld van `tests/Feature/ReportPolicyTest.php`. Voor gebruikers wordt het generieke authenticatiemodel van Laravel gebruikt, zodat je zonder op te slaan in de database kunt beoordelen.

```php theme={null}
<?php

namespace Acme\Reports\Tests\Feature;

use Acme\Reports\Models\Report;
use Acme\Reports\Policies\ReportPolicy;
use Acme\Reports\Tests\TestCase;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\User;

class ReportPolicyTest extends TestCase
{
    public function test_provider_registers_the_policy(): void
    {
        $gate = $this->app->make(GateContract::class);

        $this->assertInstanceOf(
            ReportPolicy::class,
            $gate->getPolicyFor(Report::class),
        );
    }

    public function test_only_the_owner_can_update(): void
    {
        $gate = $this->app->make(GateContract::class);
        $report = (new Report)->forceFill(['owner_id' => 7]);
        $owner = (new User)->forceFill(['id' => 7]);
        $other = (new User)->forceFill(['id' => 8]);

        $this->assertTrue($gate->forUser($owner)->allows('update', $report));
        $this->assertFalse($gate->forUser($other)->allows('update', $report));
        $this->assertFalse($gate->forUser(null)->allows('update', $report));
        $this->assertFalse($gate->forUser($owner)->allows('update', new Report));
    }

    public function test_an_override_does_not_change_an_existing_user_gate(): void
    {
        $gate = $this->app->make(GateContract::class);
        $owner = (new User)->forceFill(['id' => 7]);
        $report = (new Report)->forceFill(['owner_id' => 7]);
        $existingUserGate = $gate->forUser($owner);

        $gate->policy(Report::class, DenyReportPolicy::class);

        $this->assertInstanceOf(
            DenyReportPolicy::class,
            $gate->getPolicyFor(Report::class),
        );
        $this->assertFalse($gate->forUser($owner)->allows('update', $report));
        $this->assertTrue($existingUserGate->allows('update', $report));
    }
}

class DenyReportPolicy
{
    public function update(Authenticatable $user, Report $report): bool
    {
        return false;
    }
}
```

`forUser()` herschrijft de gebruiker van de gedeelde Gate niet. Het maakt een aparte Gate aan en geeft daarbij de koppelingen, abilities en callbacks van dat moment mee. Een al aangemaakte Gate per gebruiker ziet daarom latere wijzigingen in policyregistraties op de gedeelde Gate niet. Rond registraties af tijdens het opstarten en bewaar Gates per gebruiker niet in langlevende services.

In de applicatie heb je ook controles met de echte guard en het echte gebruikersmodel nodig, plus HTTP-tests voor het update-endpoint. Alleen met de tests hierboven verifieer je niet dat autorisatie op de route is toegepast of in welk formaat de eigenaar-identifier in de database wordt opgeslagen.

### Contracten om te behouden bij updates

* Zijn de resultaten voor eigenaar, niet-eigenaren, gasten en records zonder eigenaar ongewijzigd?
* Komen de gepubliceerde ability-namen overeen met de argumenten van de policymethoden?
* Werkt het vervangen van de policy door de applicatie nog steeds?
* Als je kindmodellen ondersteunt: is de policy die daadwerkelijk voor de kindklasse wordt gekozen correct?
* Worden echte updaterequests, inclusief de globale hooks van de applicatie, toegestaan en geweigerd zoals verwacht?

Updates die bestaande abilities verwijderen of het standaard toestaan of weigeren veranderen, beïnvloeden het gedrag van applicaties. Behandel autorisatiewijzigingen samen met [versiecompatibiliteitsbeheer](https://kawax.biz/nl/advanced/package-versioning.md) als publiek contract.

## Geraadpleegde primaire bronnen

* [Officiële Laravel-documentatie: Autorisatie](https://github.com/laravel/docs/blob/13.x/authorization.md)
* [Officiële Laravel-documentatie: Packageontwikkeling](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Framework v13.42.0: registratie, resolutie en forUser van de Gate](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/Access/Gate.php)
* [Framework v13.42.0: AuthServiceProvider, die de Gate als singleton registreert](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/AuthServiceProvider.php)
* [Framework v13.42.0: boot en booted van Application](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Foundation/Application.php)
* [Framework v13.42.0: officiële tests van de autorisatie-Gate](https://github.com/laravel/framework/blob/v13.42.0/tests/Auth/AuthAccessGateTest.php)


## Related topics

- [Autorisatie (gates en policies)](https://kawax.biz/nl/authorization.md)
- [Geavanceerde onderwerpen](https://kawax.biz/nl/advanced/index.md)
- [Views van packages overschrijven en bijwerken](https://kawax.biz/nl/advanced/package-views.md)
- [Middleware van packages registreren en het toepassingsbereik](https://kawax.biz/nl/advanced/package-middleware.md)
- [Routes van packages registreren en cachen](https://kawax.biz/nl/advanced/package-routes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.