Especialização em Backend com PHP & Laravel • Laravel 11 & PHP 8.3+ • Eloquent ORM, Sanctum, Filas, Horizon e APIs Corporativas


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Gate::define('acao', closure)"] --> B["Ability solta, sem modelo específico"]
    C["ProdutoPolicy::update(user, produto)"] --> D["Ability atrelada a um modelo Eloquent"]
    D --> E["before(user, ability) — bypass de Super Admin"]
    B --> F["$this->authorize('acao', $modelo)"]
    D --> F
    F --> G["403 automático via AuthorizationException"]

    subgraph RBAC["RBAC em Escala"]
        H["spatie/laravel-permission"]
        I["$user->assignRole('admin')"]
        J["middleware('permission:faturar-pedidos')"]
    end

    G --> RBAC

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style C fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style H fill:#fce4ec,stroke:#e91e63,stroke-width:2px

🏛️ 1. Gates: Autorização como Closures

Gates são a forma mais simples de autorização no Laravel: closures registradas em AppServiceProvider::boot() que recebem o usuário e retornam um booleano. São ideais para ações que não giram em torno de um modelo Eloquent específico:

<?php
Gate::define('gerenciar-produtos', function (User $user) {
    return $user->is_admin === true;
});
if (Gate::allows('gerenciar-produtos')) { /* ... */ }

🧩 2. Policies: Autorização Orientada a Modelos

Quando a regra depende de comparar o usuário com uma instância de modelo (o post é do próprio usuário? o pedido pertence ao cliente logado?), uma Policy dedicada organiza isso melhor que Gates soltos. O Laravel convenciona métodos view, create, update, delete mapeados às ações CRUD:

<?php
// app/Policies/ProdutoPolicy.php
class ProdutoPolicy
{
    public function update(User $user, Produto $produto): bool
    {
        return $user->id === $produto->user_id || $user->is_admin;
    }
}

No controller, $this->authorize() resolve automaticamente qual Policy usar com base no tipo do modelo, e lança AuthorizationException (convertida em HTTP 403) se a regra retornar false:

public function update(Request $request, Produto $produto)
{
    $this->authorize('update', $produto);
    // só chega aqui se autorizado
}

O método before()

Repetir “ou é admin” em toda regra de toda Policy é ruído. O método especial before() roda antes de qualquer outro método da Policy e intercepta a decisão:

public function before(User $user, string $ability): ?bool
{
    if ($user->hasRole('SUPER_ADMIN')) {
        return true; // aprova QUALQUER ability, sem checar as regras individuais
    }
    return null; // null = "não decidi", segue para o método específico (ex: update())
}

Retornar null (não false) é o que permite a Policy continuar a avaliação normal para usuários que não são super admin.


🏢 3. RBAC Corporativo com Spatie Laravel-Permission

Gates e Policies resolvem autorização por regra de negócio, mas não modelam papéis e permissões dinâmicos gerenciados pelo banco (um admin cadastra novos cargos sem deploy de código). O pacote spatie/laravel-permission é o padrão de mercado para isso: adiciona a trait HasRoles ao User e persiste roles/permissions em tabelas próprias, com cache automático para não penalizar performance:

$user->assignRole('admin');
$user->hasPermissionTo('editar-produtos'); // true/false
// routes/api.php
Route::middleware(['permission:faturar-pedidos'])->group(function () {
    Route::post('/pedidos/{pedido}/faturar', FaturarController::class);
});

A diferença estrutural: Gates/Policies são código versionado; Spatie é dado, o que o torna a escolha certa quando o RBAC precisa mudar em produção sem novo deploy — ao custo de uma camada extra de indireção que precisa ser mantida em sincronia com o domínio da aplicação.