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

# Enregistrer et remplacer les policies d'un package

> Lisez l'implémentation du Gate de Laravel 13 : enregistrement des policies d'un package, conception sans dépendre du modèle utilisateur, ordre de résolution, remplacement et tests dans l'application cliente.

Il arrive que vous vouliez ajouter une autorisation aux modèles fournis par un package tout en laissant à l'application cliente la possibilité de les remplacer par ses propres règles. En déclarant explicitement la correspondance entre modèle et policy, et en n'évaluant l'utilisateur qu'au moment de l'autorisation, vous séparez l'enregistrement des décisions prises à chaque requête.

Cette page s'appuie sur la branche par défaut `13.x` de la documentation officielle de Laravel ainsi que sur l'implémentation et les tests officiels de la dernière version de Framework, `v13.42.0`, vérifiés le 10 novembre 2026. Pour les bases de l'autorisation, consultez le [guide d'autorisation](https://kawax.biz/fr/authorization.md). Pour le chargement des fournisseurs, consultez [la découverte automatique des packages](https://kawax.biz/fr/advanced/package-discovery.md).

## Ne pas dépendre de la classe User de l'application cliente

Prenons l'exemple de l'ajout d'une autorisation `update` au modèle `Acme\Reports\Models\Report` du package. On suppose que la table `acme_reports` possède une colonne `owner_id` qui stocke l'identifiant d'authentification du propriétaire. La manière de fournir les migrations est traitée dans [les migrations de package](https://kawax.biz/fr/advanced/package-migrations.md).

`src/Models/Report.php` est un modèle Eloquent ordinaire.

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

namespace Acme\Reports\Models;

use Illuminate\Database\Eloquent\Model;

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

Si la policy du package type `App\Models\User`, elle dépend de l'espace de noms et de la structure des modèles de l'application cliente. Si vous n'avez besoin que de l'identifiant d'authentification de l'utilisateur, utilisez le contrat `Authenticatable` de Laravel.

Placez le code suivant dans `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;
    }
}
```

Les identifiants `null` sont d'abord exclus, puis la différence de type entre un entier issu de la base de données et une chaîne est absorbée en comparant les deux sous forme de chaînes. Dans cet exemple, seul le propriétaire peut mettre à jour. Dans les applications qui gèrent plusieurs types d'utilisateurs ou plusieurs guards, vérifiez aussi que l'identifiant suffit à identifier le sujet de manière unique. Si ce n'est pas le cas, remplacez-la par votre propre policy qui tient compte des frontières de type d'utilisateur ou de tenant.

> [!NOTE]
>
> `Authenticatable` est un contrat portant notamment sur l'identifiant d'authentification. Il ne fournit ni `isAdministrator()` ni méthodes pour récupérer des rôles. Si vous avez besoin d'informations de permission propres au package, définissez un autre contrat et laissez l'application cliente injecter une implémentation.

## Enregistrer la correspondance dans le fournisseur de services

Dans le `boot()` de `src/ReportsServiceProvider.php`, n'enregistrez que les modèles qui appartiennent au package.

```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()` enregistre le nom de classe de la policy avec le nom de classe du modèle comme clé. L'enregistrement lui-même ne crée pas la policy, n'interroge pas la base de données et n'évalue aucun utilisateur. La policy est créée avec `make()` du conteneur lorsqu'elle devient nécessaire, par exemple via `Gate::getPolicyFor()`. Vous pouvez donc injecter des dépendances dans son constructeur.

L'emplacement de `Report` et `ReportPolicy` convient aussi à la découverte automatique standard, mais l'enregistrement est ici explicite pour rendre clair ce que fournit le package. L'enregistrement seul n'applique aucune autorisation aux endpoints HTTP. Avant la mise à jour, appelez ce qui suit, par exemple dans un contrôleur.

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

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

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

En cas de refus, une `AuthorizationException` est levée, ce qui produit une réponse 403 dans le traitement HTTP habituel. Les arguments de la méthode de policy diffèrent entre `update`, qui reçoit un modèle, et les capacités comme `create`, qui reçoivent le nom de classe du modèle. Suivez les modes d'appel décrits dans le [guide d'autorisation](https://kawax.biz/fr/authorization.md).

## Lire l'ordre de résolution des policies

`Gate::getPolicyFor()` convertit une cible objet en son nom de classe, puis cherche dans l'ordre suivant. Il s'agit de **l'ordre de sélection de la policy**, pas de l'ordre d'exécution de l'ensemble des hooks d'autorisation.

| Ordre | Ce qui est recherché |
| - | - |
| 1 | Un enregistrement explicite via `Gate::policy()` pour la classe cible elle-même |
| 2 | `#[UsePolicy]` sur la classe cible elle-même |
| 3 | Une déduction par la convention de nommage standard ou par `guessPolicyNamesUsing()` |
| 4 | Les enregistrements explicites des classes parentes et des interfaces implémentées |
| 5 | `#[UsePolicy]` recherché par héritage dans les classes parentes |

Ainsi, tant qu'un enregistrement explicite existe pour la même classe `Report`, modifier seulement `#[UsePolicy]` ou la convention de nommage ne remplace pas la policy. En revanche, si l'application cliente utilise un modèle qui hérite de `Report`, une policy trouvée via l'attribut propre de la classe enfant ou par déduction passe avant l'enregistrement explicite du parent. Vérifiez également avec la classe de modèle réellement utilisée.

> [!WARNING]
>
> `guessPolicyNamesUsing()` n'ajoute pas un callback à une liste : il remplace l'unique callback de déduction détenu par le Gate. Si un package indépendant le définit uniquement pour ses propres modèles, cela affecte aussi la déduction de l'application cliente et des autres packages. Privilégiez l'enregistrement via `policy()` des modèles qui appartiennent au package.

## Remplacer explicitement dans l'application cliente

Appeler à nouveau `policy()` pour le même modèle remplace la correspondance par le dernier enregistrement. Le placer simplement dans le `boot()` d'un autre fournisseur dépend de l'ordre de chargement. Une solution consiste à enregistrer un callback de fin de démarrage depuis `AppServiceProvider::boot()` de l'application cliente.

```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` est une policy implémentée par l'application cliente. Si le package enregistre dans un `boot()` normal, ce callback s'exécute après. Ce n'est pas un mécanisme qui l'emporte inconditionnellement, y compris face aux réenregistrements dans d'autres callbacks booted ou aux fournisseurs chargés plus tard. Dans cet exemple, le fournisseur d'enregistrement du package n'est pas un fournisseur différé.

Autre possibilité : laisser la correspondance inchangée et remplacer, dans le conteneur, la cible de résolution de `ReportPolicy::class`. `resolvePolicy()` demande la résolution au conteneur à chaque fois, mais l'obtention effective d'une nouvelle instance dépend du binding de cette policy. Évitez les conceptions qui récupèrent des informations d'utilisateur ou de requête dans le constructeur et les conservent dans une policy singleton.

## Séparer les hooks qui affectent toute l'autorisation

Lors d'une vérification Gate normale, le `Gate::before()` global s'exécute en premier. S'il renvoie autre chose que `null`, l'évaluation par la policy n'a pas lieu.

```mermaid theme={null}
flowchart TD
    A["Gate::authorize / allows"] --> B["Gate::before global"]
    B --> C{"La valeur de retour est-elle null ?"}
    C -->|No| D["Utiliser ce résultat"]
    C -->|Yes| E["Chercher la policy cible et<br>une méthode appelable"]
    E --> F["Appeler le before de la policy s'il existe"]
    F --> G["Si before renvoie null,<br>la méthode de policy décide"]
```

Ce diagramme ne montre que le chemin où une policy est trouvée et où la méthode correspondante peut être appelée. Il omet les hooks `after` globaux qui suivent, ainsi que le repli sur les définitions du Gate lorsque la policy n'a pas la méthode visée.

N'enregistrez pas un `Gate::before()` global qui autorise toutes les capacités simplement pour gérer les administrateurs dans votre package. Vous autoriseriez aussi les vérifications des autres packages et de l'application cliente. Un `before()` dans la policy se limite à cette policy, mais il n'est pas appelé lorsque la méthode de la capacité n'existe pas. Pour laisser la méthode suivante décider, renvoyez `null` plutôt que `false`.

Par ailleurs, le `update()` de cet exemple exige un `Authenticatable` non nullable, si bien que la méthode de policy ne s'exécute pas pour les invités. Sauf si un hook global remplace le résultat, l'accès est refusé. Pour autoriser les invités, par exemple pour une consultation publique, prévoyez séparément un argument utilisateur nullable et une vérification explicite.

## Tester séparément l'enregistrement et l'évaluation

On suppose que vous utilisez la classe de base Testbench présentée dans [les tests de package](https://kawax.biz/fr/advanced/package-testing.md) et que `getPackageProviders()` charge `ReportsServiceProvider::class`. Voici un exemple de `tests/Feature/ReportPolicyTest.php`. Les utilisateurs sont représentés par le modèle d'authentification générique de Laravel, ce qui permet d'évaluer sans rien enregistrer en base.

```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()` ne modifie pas l'utilisateur du Gate partagé. Il crée un Gate distinct en lui transmettant les correspondances, capacités et callbacks du moment. Un Gate par utilisateur déjà créé ne reflète donc pas les modifications ultérieures des enregistrements de policies du Gate partagé. Terminez les enregistrements au démarrage et ne stockez pas de Gate par utilisateur dans des services à longue durée de vie.

Côté application cliente, il faut aussi des vérifications avec le guard et le modèle utilisateur réels, ainsi que des tests HTTP sur l'endpoint de mise à jour. Les tests ci-dessus ne vérifient ni que l'autorisation est appliquée à la route, ni le format de stockage de l'identifiant du propriétaire en base.

### Contrats à préserver lors des mises à jour

* Les résultats pour le propriétaire, les autres utilisateurs, les invités et les enregistrements sans propriétaire sont-ils inchangés ?
* Les noms de capacités publiés correspondent-ils aux arguments des méthodes de policy ?
* Le remplacement de la policy par l'application cliente fonctionne-t-il toujours ?
* Si vous prenez en charge des modèles enfants, la policy réellement sélectionnée pour la classe enfant est-elle correcte ?
* Les vraies requêtes de mise à jour sont-elles autorisées ou refusées comme prévu, hooks globaux de l'application compris ?

Les mises à jour qui suppriment des capacités existantes ou modifient l'autorisation ou le refus par défaut affectent le comportement des applications clientes. Avec la [gestion de la compatibilité des versions](https://kawax.biz/fr/advanced/package-versioning.md), traitez aussi les changements d'autorisation comme un contrat public.

## Sources primaires consultées

* [Documentation officielle de Laravel : Autorisation](https://github.com/laravel/docs/blob/13.x/authorization.md)
* [Documentation officielle de Laravel : Développement de packages](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Framework v13.42.0 : enregistrement, résolution et forUser du Gate](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/Access/Gate.php)
* [Framework v13.42.0 : AuthServiceProvider, qui enregistre le Gate en singleton](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/AuthServiceProvider.php)
* [Framework v13.42.0 : boot et booted de Application](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Foundation/Application.php)
* [Framework v13.42.0 : tests officiels du Gate d'autorisation](https://github.com/laravel/framework/blob/v13.42.0/tests/Auth/AuthAccessGateTest.php)


## Related topics

- [Autorisation (Gates et Policies)](https://kawax.biz/fr/authorization.md)
- [Sujets avancés](https://kawax.biz/fr/advanced/index.md)
- [Étendre et résoudre à nouveau les canaux de log d'un package](https://kawax.biz/fr/advanced/package-logging.md)
- [Enregistrer des drivers Storage dans un package et les résoudre à nouveau](https://kawax.biz/fr/advanced/package-filesystem-drivers.md)
- [Publier et mettre à jour les migrations d'un package](https://kawax.biz/fr/advanced/package-migrations.md)


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