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

# Registrieren und Überschreiben von Policies in Paketen

> Anhand der Gate-Implementierung von Laravel 13: Policies im Paket registrieren, Abhängigkeiten vom Benutzermodell vermeiden, Auflösungsreihenfolge verstehen sowie Überschreiben und Testen in der nutzenden Anwendung.

Manchmal möchten Sie den Modellen eines Pakets Autorisierung hinzufügen und der nutzenden Anwendung trotzdem Spielraum lassen, eigene Regeln einzusetzen. Wenn Sie die Zuordnung von Modell und Policy explizit angeben und den Benutzer erst zum Zeitpunkt der Autorisierung prüfen, trennen Sie die Registrierung von den Entscheidungen pro Request.

Diese Seite bezieht sich auf den Standard-Branch `13.x` der offiziellen Laravel-Dokumentation sowie auf die offizielle Implementierung und die Tests des neuesten Framework-Releases `v13.42.0`, geprüft am 10. November 2026. Die Grundlagen der Autorisierung finden Sie im [Autorisierungs-Leitfaden](https://kawax.biz/de/authorization.md), das Laden von Providern unter [Paket-Auto-Discovery](https://kawax.biz/de/advanced/package-discovery.md).

## Nicht von der User-Klasse der nutzenden Anwendung abhängen

Als Beispiel fügen wir dem Modell `Acme\Reports\Models\Report` des Pakets eine Autorisierung für `update` hinzu. Vorausgesetzt wird, dass die Tabelle `acme_reports` eine Spalte `owner_id` hat, in der der Authentifizierungs-Identifier des Eigentümers gespeichert ist. Wie Sie Migrationen ausliefern, behandelt [Paket-Migrationen](https://kawax.biz/de/advanced/package-migrations.md).

`src/Models/Report.php` ist ein gewöhnliches Eloquent-Modell.

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

namespace Acme\Reports\Models;

use Illuminate\Database\Eloquent\Model;

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

Wenn die Policy des Pakets `App\Models\User` als Typ angibt, hängt sie vom Namespace und Modellaufbau der nutzenden Anwendung ab. Wenn Sie nur den Authentifizierungs-Identifier des Benutzers benötigen, verwenden Sie den Laravel-Vertrag `Authenticatable`.

Legen Sie Folgendes in `src/Policies/ReportPolicy.php` ab.

```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;
    }
}
```

Zuerst werden `null`-Identifier ausgeschlossen. Den Typunterschied zwischen einer Ganzzahl aus der Datenbank und einem String gleicht der Vergleich über Strings aus. In diesem Beispiel darf nur der Eigentümer aktualisieren. In Anwendungen mit mehreren Benutzertypen oder Guards prüfen Sie außerdem, ob der Identifier allein das Subjekt eindeutig bestimmt. Falls nicht, ersetzen Sie die Policy durch eine eigene, die Benutzertyp- oder Mandantengrenzen berücksichtigt.

> [!NOTE]
>
> `Authenticatable` ist ein Vertrag für Dinge wie den Authentifizierungs-Identifier. Er stellt weder `isAdministrator()` noch Methoden zum Abrufen von Rollen bereit. Wenn Sie paketeigene Berechtigungsinformationen benötigen, definieren Sie einen separaten Vertrag und lassen die nutzende Anwendung eine Implementierung injizieren.

## Die Zuordnung im Service-Provider registrieren

Registrieren Sie in `boot()` von `src/ReportsServiceProvider.php` nur die Modelle, die dem Paket gehören.

```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()` speichert den Klassennamen der Policy unter dem Klassennamen des Modells. Die Registrierung selbst erzeugt keine Policy, führt keine Datenbankabfrage aus und prüft keinen Benutzer. Erst wenn die Policy benötigt wird, etwa durch `Gate::getPolicyFor()`, wird sie über `make()` des Containers erzeugt. Deshalb können Sie Abhängigkeiten in den Konstruktor der Policy injizieren.

Die Ablage von `Report` und `ReportPolicy` passt auch zur Standard-Auto-Discovery. Hier wird aber explizit registriert, um deutlich zu machen, welche Zuordnung das Paket bereitstellt. Die Registrierung allein wendet keine Autorisierung auf HTTP-Endpunkte an. Rufen Sie vor der Aktualisierung zum Beispiel im Controller Folgendes auf.

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

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

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

Bei Ablehnung wird eine `AuthorizationException` ausgelöst, die in der normalen HTTP-Verarbeitung zu einer 403-Antwort wird. Die Argumente der Policy-Methode unterscheiden sich zwischen `update`, das ein Modell erhält, und Fähigkeiten wie `create`, die den Klassennamen des Modells erhalten. Richten Sie sich nach den Aufrufkonventionen im [Autorisierungs-Leitfaden](https://kawax.biz/de/authorization.md).

## Die Auflösungsreihenfolge der Policies lesen

`Gate::getPolicyFor()` wandelt ein Objekt in seinen Klassennamen um und sucht in der folgenden Reihenfolge. Das ist die **Reihenfolge der Policy-Auswahl**, nicht die Ausführungsreihenfolge aller Autorisierungs-Hooks.

| Reihenfolge | Gesucht wird |
| - | - |
| 1 | Explizite Registrierung per `Gate::policy()` für die Zielklasse selbst |
| 2 | `#[UsePolicy]` an der Zielklasse selbst |
| 3 | Ableitung über die Standard-Namenskonvention oder `guessPolicyNamesUsing()` |
| 4 | Explizite Registrierungen für Elternklassen und implementierte Interfaces |
| 5 | `#[UsePolicy]`, geerbt über die Elternklassen |

Solange es also eine explizite Registrierung für dieselbe Klasse `Report` gibt, wird die Policy nicht ersetzt, wenn Sie nur `#[UsePolicy]` oder die Namenskonvention ändern. Verwendet die nutzende Anwendung dagegen ein Modell, das von `Report` erbt, haben Policies, die über das eigene Attribut der Kindklasse oder die Ableitung gefunden werden, Vorrang vor der expliziten Registrierung der Elternklasse. Prüfen Sie das auch mit der Modellklasse, die tatsächlich verwendet wird.

> [!WARNING]
>
> `guessPolicyNamesUsing()` fügt keinen Callback zu einer Liste hinzu, sondern ersetzt den einen Ableitungs-Callback des Gates. Setzt ein unabhängiges Paket ihn nur für seine eigenen Modelle, beeinflusst das auch die Ableitung der nutzenden Anwendung und anderer Pakete. Nutzen Sie als Grundlage `policy()`-Registrierungen für die Modelle, die dem Paket gehören.

## In der nutzenden Anwendung explizit überschreiben

Wenn Sie `policy()` für dasselbe Modell erneut aufrufen, ersetzt die spätere Registrierung die Zuordnung. Sie einfach in das `boot()` eines anderen Providers zu legen, hängt von der Ladereihenfolge ab. Eine Möglichkeit ist, in `AppServiceProvider::boot()` der nutzenden Anwendung einen Callback für den Abschluss des Bootvorgangs zu registrieren.

```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` ist eine Policy, die die nutzende Anwendung implementiert. Registriert das Paket in einem normalen `boot()`, läuft dieser Callback danach. Es ist kein Mechanismus, der bedingungslos Vorrang hat, auch nicht gegenüber erneuten Registrierungen in anderen Booted-Callbacks oder später geladenen Providern. Der registrierende Provider des Paketes ist in diesem Beispiel kein Deferred Provider.

Alternativ können Sie die Zuordnung unverändert lassen und im Container das Auflösungsziel von `ReportPolicy::class` ersetzen. `resolvePolicy()` lässt die Policy jedes Mal vom Container auflösen, ob dabei tatsächlich eine neue Instanz entsteht, hängt aber vom Binding dieser Policy ab. Vermeiden Sie Designs, die Benutzer- oder Request-Informationen im Konstruktor holen und in einer Singleton-Policy festhalten.

## Hooks mit Auswirkung auf die gesamte Autorisierung trennen

Bei einer normalen Gate-Prüfung läuft zuerst das globale `Gate::before()`. Gibt es einen anderen Wert als `null` zurück, wird die Entscheidung der Policy nicht ausgeführt.

```mermaid theme={null}
flowchart TD
    A["Gate::authorize / allows"] --> B["Globales Gate::before"]
    B --> C{"Rückgabewert null?"}
    C -->|No| D["Dieses Ergebnis verwenden"]
    C -->|Yes| E["Ziel-Policy und<br>aufrufbare Methode suchen"]
    E --> F["before der Policy aufrufen, falls vorhanden"]
    F --> G["Ist before null,<br>entscheidet die Policy-Methode"]
```

Das Diagramm zeigt nur den Pfad, auf dem eine Policy gefunden wird und die passende Methode aufgerufen werden kann. Die anschließenden globalen `after`-Hooks und den Rückfall auf Gate-Definitionen, wenn der Policy die Zielmethode fehlt, lässt es aus.

Registrieren Sie kein globales `Gate::before()`, das alle Fähigkeiten erlaubt, nur um eine Administrator-Behandlung im Paket umzusetzen. Damit würden Sie auch die Autorisierung anderer Pakete und der nutzenden Anwendung freigeben. Ein `before()` innerhalb der Policy gilt nur für diese Policy, wird aber nicht aufgerufen, wenn es keine Methode für die Fähigkeit gibt. Wenn die folgende Methode entscheiden soll, geben Sie `null` statt `false` zurück.

Außerdem verlangt `update()` in diesem Beispiel ein nicht-nullable `Authenticatable`, sodass die Policy-Methode für Gäste nicht ausgeführt wird. Ohne Überschreiben des Ergebnisses, etwa durch globale Hooks, wird die Anfrage abgelehnt. Wenn Sie Gäste zulassen wollen, etwa für öffentliches Lesen, sehen Sie separat ein nullable Benutzerargument und eine explizite Prüfung vor.

## Registrierung und Entscheidung getrennt testen

Vorausgesetzt wird die in [Paket-Tests](https://kawax.biz/de/advanced/package-testing.md) vorgestellte Testbench-Basisklasse, die `ReportsServiceProvider::class` in `getPackageProviders()` lädt. Das folgende Beispiel ist `tests/Feature/ReportPolicyTest.php`. Als Benutzer dient das generische Authentifizierungsmodell von Laravel, sodass Sie ohne Speichern in der Datenbank prüfen können.

```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()` schreibt den Benutzer des gemeinsamen Gates nicht um. Es erzeugt ein separates Gate und übergibt ihm die Zuordnungen, Fähigkeiten und Callbacks zum jeweiligen Zeitpunkt. Ein bereits erzeugtes benutzerspezifisches Gate sieht daher spätere Änderungen an Policy-Registrierungen des gemeinsamen Gates nicht. Schließen Sie die Registrierung beim Booten ab und speichern Sie benutzerspezifische Gates nicht in langlebigen Services.

In der nutzenden Anwendung brauchen Sie außerdem Prüfungen mit dem echten Guard und Benutzermodell sowie HTTP-Tests für den Update-Endpunkt. Die obigen Tests allein prüfen weder, ob die Route autorisiert ist, noch das Speicherformat des Eigentümer-Identifiers in der Datenbank.

### Bei Updates einzuhaltende Verträge

* Sind die Ergebnisse für Eigentümer, Nicht-Eigentümer, Gäste und Datensätze ohne Eigentümer unverändert?
* Stimmen die veröffentlichten Fähigkeitsnamen mit den Argumenten der Policy-Methoden überein?
* Funktioniert das Ersetzen der Policy durch die nutzende Anwendung weiterhin?
* Wird bei unterstützten Kindmodellen für die Kindklasse tatsächlich die richtige Policy gewählt?
* Werden echte Update-Requests einschließlich der globalen Hooks der Anwendung wie erwartet erlaubt oder abgelehnt?

Updates, die bestehende Fähigkeiten entfernen oder das Standardverhalten von Erlauben und Ablehnen ändern, wirken sich auf nutzende Anwendungen aus. Behandeln Sie Änderungen der Autorisierung zusammen mit der [Versionskompatibilität](https://kawax.biz/de/advanced/package-versioning.md) als öffentlichen Vertrag.

## Primärquellen

* [Offizielle Laravel-Dokumentation: Autorisierung](https://github.com/laravel/docs/blob/13.x/authorization.md)
* [Offizielle Laravel-Dokumentation: Paketentwicklung](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Framework v13.42.0: Registrierung, Auflösung und forUser des Gates](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/Access/Gate.php)
* [Framework v13.42.0: AuthServiceProvider, der das Gate als Singleton registriert](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/AuthServiceProvider.php)
* [Framework v13.42.0: boot und booted der Application](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Foundation/Application.php)
* [Framework v13.42.0: Offizielle Tests des Autorisierungs-Gates](https://github.com/laravel/framework/blob/v13.42.0/tests/Auth/AuthAccessGateTest.php)


## Related topics

- [Autorisierung (Gates und Policies)](https://kawax.biz/de/authorization.md)
- [Weiterführende Themen](https://kawax.biz/de/advanced/index.md)
- [String-Validierungsregeln in Paketen registrieren](https://kawax.biz/de/advanced/package-validation-extensions.md)
- [Artisan-Befehle in Paketen registrieren und Abhängigkeiten auflösen](https://kawax.biz/de/advanced/package-commands.md)
- [Paketübersetzungen überschreiben und aktualisieren](https://kawax.biz/de/advanced/package-translations.md)


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