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

# Registro y sobrescritura de policies en paquetes

> Analiza la implementación de Gate en Laravel 13: registro de policies en un paquete, diseño sin depender del modelo de usuario, orden de resolución, y sobrescritura y pruebas en la aplicación que lo usa.

A veces quieres añadir autorización a los modelos que proporciona un paquete y, al mismo tiempo, dejar margen para que la aplicación que lo usa los sustituya por sus propias reglas. Si declaras explícitamente la correspondencia entre modelo y policy y evalúas al usuario solo en el momento de autorizar, separas el registro de las decisiones de cada petición.

Esta página toma como referencia la rama por defecto `13.x` de la documentación oficial de Laravel y la implementación y las pruebas oficiales de la última versión de Framework, `v13.42.0`, comprobadas el 10 de noviembre de 2026. Para los fundamentos de la autorización, consulta la [guía de autorización](https://kawax.biz/es/authorization.md). Para la carga de proveedores, consulta [autodescubrimiento de paquetes](https://kawax.biz/es/advanced/package-discovery.md).

## No depender de la clase User de la aplicación

Como ejemplo, añadimos autorización de `update` al modelo `Acme\Reports\Models\Report` del paquete. Se supone que la tabla `acme_reports` tiene una columna `owner_id` que guarda el identificador de autenticación del propietario. La forma de proporcionar migraciones se trata en [migraciones de paquetes](https://kawax.biz/es/advanced/package-migrations.md).

`src/Models/Report.php` es un modelo Eloquent normal.

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

namespace Acme\Reports\Models;

use Illuminate\Database\Eloquent\Model;

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

Si la policy del paquete declara el tipo `App\Models\User`, depende del espacio de nombres y de la estructura de modelos de la aplicación. Si solo necesitas el identificador de autenticación del usuario, usa el contrato `Authenticatable` de Laravel.

Coloca lo siguiente en `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;
    }
}
```

Primero se descartan los identificadores `null`, y la diferencia de tipo entre un entero obtenido de la base de datos y una cadena se resuelve comparando ambos como cadenas. En este ejemplo solo el propietario puede actualizar. En aplicaciones con varios tipos de usuario o guards, comprueba también si el identificador basta para identificar al sujeto de forma única. Si no basta, sustitúyela por una policy propia que incluya los límites de tipo de usuario o de tenant.

> [!NOTE]
>
> `Authenticatable` es un contrato para datos como el identificador de autenticación. No proporciona `isAdministrator()` ni métodos para obtener roles. Si necesitas información de permisos propia del paquete, define otro contrato y deja que la aplicación inyecte una implementación.

## Registrar la correspondencia en el proveedor de servicios

En el `boot()` de `src/ReportsServiceProvider.php`, registra solo los modelos que pertenecen al paquete.

```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()` guarda el nombre de la clase de la policy usando como clave el nombre de la clase del modelo. El registro en sí no crea la policy, no consulta la base de datos ni evalúa a ningún usuario. La policy se crea con `make()` del contenedor cuando se necesita, por ejemplo en `Gate::getPolicyFor()`. Por eso puedes inyectar dependencias en el constructor de la policy.

La ubicación de `Report` y `ReportPolicy` también encaja con el autodescubrimiento estándar, pero aquí se registra de forma explícita para dejar claro qué correspondencia proporciona el paquete. El registro por sí solo no aplica autorización a los endpoints HTTP. Antes de la actualización, llama a lo siguiente, por ejemplo en un controlador.

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

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

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

Si se deniega, se lanza una `AuthorizationException`, que el procesamiento HTTP habitual convierte en una respuesta 403. Los argumentos del método de la policy difieren entre `update`, que recibe un modelo, y habilidades como `create`, que reciben el nombre de la clase del modelo. Sigue la forma de invocación de la [guía de autorización](https://kawax.biz/es/authorization.md).

## Leer el orden de resolución de las policies

`Gate::getPolicyFor()` convierte el objetivo en su nombre de clase si es un objeto y busca en el siguiente orden. Este es **el orden de selección de la policy**, no el orden de ejecución de todos los hooks de autorización.

| Orden | Qué busca |
| - | - |
| 1 | Registro explícito con `Gate::policy()` para la propia clase objetivo |
| 2 | `#[UsePolicy]` en la propia clase objetivo |
| 3 | Inferencia por la convención de nombres estándar o por `guessPolicyNamesUsing()` |
| 4 | Registros explícitos para clases padre e interfaces implementadas |
| 5 | `#[UsePolicy]` buscado por herencia en las clases padre |

Por tanto, mientras exista un registro explícito para la misma clase `Report`, cambiar solo `#[UsePolicy]` o la convención de nombres no la sustituye. En cambio, si la aplicación usa un modelo que hereda de `Report`, la policy encontrada por el atributo propio de la clase hija o por inferencia va antes que el registro explícito del padre. Compruébalo también con la clase de modelo que realmente se usa.

> [!WARNING]
>
> `guessPolicyNamesUsing()` no añade un callback a una lista, sino que reemplaza el único callback de inferencia que tiene el Gate. Si un paquete independiente lo configura solo para sus propios modelos, también afecta a la inferencia de la aplicación y de otros paquetes. Usa como base el registro con `policy()` de los modelos que pertenecen al paquete.

## Sobrescribir explícitamente en la aplicación

Si vuelves a llamar a `policy()` para el mismo modelo, el registro posterior reemplaza la correspondencia. Ponerlo simplemente en el `boot()` de otro proveedor depende del orden de carga, así que una opción es registrar un callback de arranque completado desde `AppServiceProvider::boot()` de la aplicación.

```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` es una policy que implementa la aplicación. Si el paquete registra en un `boot()` normal, este callback se ejecuta después. No es un mecanismo que tenga prioridad incondicional, incluidos los nuevos registros en otros callbacks de arranque o en proveedores cargados después. En este ejemplo, el proveedor que registra del paquete no es un proveedor diferido.

Como alternativa, puedes dejar la correspondencia sin cambios y sustituir en el contenedor el destino de resolución de `ReportPolicy::class`. `resolvePolicy()` pide al contenedor que la resuelva cada vez, pero que realmente se obtenga una instancia nueva depende del binding de esa policy. Evita diseños que obtengan información del usuario o de la petición en el constructor y la guarden en una policy singleton.

## Separar los hooks que afectan a toda la autorización

En una comprobación normal de Gate, el `Gate::before()` global se ejecuta primero. Si devuelve algo distinto de `null`, la evaluación de la policy no se ejecuta.

```mermaid theme={null}
flowchart TD
    A["Gate::authorize / allows"] --> B["Gate::before global"]
    B --> C{"¿El valor devuelto es null?"}
    C -->|No| D["Usar ese resultado"]
    C -->|Yes| E["Buscar la policy objetivo y<br>un método invocable"]
    E --> F["Llamar al before de la policy si existe"]
    F --> G["Si before es null,<br>decide el método de la policy"]
```

El diagrama muestra solo el camino en el que se encuentra una policy y se puede invocar el método correspondiente. Omite los hooks `after` globales posteriores y la vuelta a las definiciones del Gate cuando la policy no tiene el método objetivo.

No registres un `Gate::before()` global que permita todas las habilidades solo para implementar el trato de administrador dentro del paquete. Permitiría también la autorización de otros paquetes y de la aplicación. Un `before()` dentro de la policy se limita a esa policy, pero no se llama si no existe el método de la habilidad correspondiente. Si quieres que decida el método siguiente, devuelve `null` en lugar de `false`.

Además, el `update()` de este ejemplo exige un `Authenticatable` no nullable, por lo que el método de la policy no se ejecuta para invitados. Si ningún hook global sobrescribe el resultado, se deniega. Para permitir invitados, por ejemplo en una visualización pública, prepara por separado un argumento de usuario nullable y una comprobación explícita.

## Probar el registro y la evaluación por separado

Se supone que usas la clase base de Testbench presentada en [pruebas de paquetes](https://kawax.biz/es/advanced/package-testing.md) y que cargas `ReportsServiceProvider::class` en `getPackageProviders()`. El siguiente es un ejemplo de `tests/Feature/ReportPolicyTest.php`. Para los usuarios se usa el modelo de autenticación genérico de Laravel, de modo que puedes evaluar sin guardar en la base de datos.

```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()` no reescribe el usuario del Gate compartido. Crea un Gate independiente al que pasa las correspondencias, habilidades y callbacks de ese momento. Por eso, un Gate por usuario ya creado no refleja los cambios posteriores en el registro de policies del Gate compartido. Completa el registro durante el arranque y no guardes Gates por usuario en servicios de larga duración.

En la aplicación también necesitas comprobaciones con el guard y el modelo de usuario reales, y pruebas HTTP del endpoint de actualización. Las pruebas anteriores por sí solas no verifican que la ruta tenga la autorización aplicada ni el formato en que se guarda el identificador del propietario en la base de datos.

### Contratos que mantener al actualizar

* ¿Se mantienen los resultados para el propietario, otros usuarios, invitados y registros sin propietario?
* ¿Coinciden los nombres de habilidad publicados con los argumentos de los métodos de la policy?
* ¿Sigue funcionando la sustitución de la policy por parte de la aplicación?
* Si admites modelos hijos, ¿es correcta la policy que realmente se selecciona para la clase hija?
* ¿Se permiten y deniegan las peticiones de actualización reales como se espera, incluidos los hooks globales de la aplicación?

Las actualizaciones que eliminan habilidades existentes o cambian el permiso o la denegación por defecto afectan al comportamiento de las aplicaciones. Junto con la [gestión de compatibilidad de versiones](https://kawax.biz/es/advanced/package-versioning.md), trata también los cambios de autorización como un contrato público.

## Fuentes primarias consultadas

* [Documentación oficial de Laravel: Autorización](https://github.com/laravel/docs/blob/13.x/authorization.md)
* [Documentación oficial de Laravel: Desarrollo de paquetes](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Framework v13.42.0: registro, resolución y forUser de Gate](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/Access/Gate.php)
* [Framework v13.42.0: AuthServiceProvider, que registra Gate como singleton](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Auth/AuthServiceProvider.php)
* [Framework v13.42.0: boot y booted de Application](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Foundation/Application.php)
* [Framework v13.42.0: pruebas oficiales del Gate de autorización](https://github.com/laravel/framework/blob/v13.42.0/tests/Auth/AuthAccessGateTest.php)


## Related topics

- [Autorización (gates y policies)](https://kawax.biz/es/authorization.md)
- [Temas avanzados](https://kawax.biz/es/advanced/index.md)
- [Registro de reglas de validación de cadena en paquetes](https://kawax.biz/es/advanced/package-validation-extensions.md)
- [Registro de rutas y caché en paquetes](https://kawax.biz/es/advanced/package-routes.md)
- [Registro y mantenimiento de tareas programadas en paquetes](https://kawax.biz/es/advanced/package-scheduling.md)


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