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

# Atributos PHP en controladores

> Explica cómo utilizar los atributos #[Middleware], #[WithoutMiddleware] y #[Authorize] añadidos en Laravel 13 para declarar de forma declarativa la asignación de middleware y las comprobaciones de autorización en controladores.

## Descripción general

En Laravel 13, la asignación de middleware y las comprobaciones de autorización en los controladores pueden declararse mediante atributos PHP. En lugar de utilizar el método tradicional `middleware()` o el middleware `can`, basta con añadir los atributos directamente a la clase o al método para completar la configuración.

```php theme={null}
use Illuminate\Routing\Attributes\Controllers\Authorize;
use Illuminate\Routing\Attributes\Controllers\Middleware;

#[Middleware('auth')]
class PostController
{
    #[Middleware('subscribed')]
    #[Authorize('create', Post::class)]
    public function store(Request $request): Response
    {
        // ...
    }
}
```

<Tip>
  Todos los atributos de controlador se encuentran en el espacio de nombres `Illuminate\Routing\Attributes\Controllers`.
</Tip>

## `#[Middleware]` — asignar middleware

### Aplicación a la clase

Si añades `#[Middleware]` a nivel de clase, el middleware se aplicará a todas las acciones de ese controlador.

```php theme={null}
use Illuminate\Routing\Attributes\Controllers\Middleware;

#[Middleware('auth')]
class UserController
{
    public function index(): View { /* ... */ }
    public function show(User $user): View { /* ... */ }
    public function store(Request $request): Response { /* ... */ }
}
```

Para aplicar varios middleware, repite el atributo.

```php theme={null}
#[Middleware('auth')]
#[Middleware('verified')]
class ProfileController
{
    // ...
}
```

### Aplicación al método

El middleware añadido a nivel de método se combina con el middleware definido a nivel de clase.

```php theme={null}
#[Middleware('auth')]
class UserController
{
    // Todos con solo auth
    public function index(): View { /* ... */ }

    // Se aplican auth + subscribed
    #[Middleware('subscribed')]
    public function create(): View { /* ... */ }
}
```

### Filtrado con `only` / `except`

Si añades `only` o `except` al atributo a nivel de clase, puedes limitar los métodos a los que se aplica.

```php theme={null}
#[Middleware('auth')]
#[Middleware('subscribed', only: ['create', 'store', 'edit', 'update'])]
class ArticleController
{
    // Solo auth
    public function index(): View { /* ... */ }
    public function show(Article $article): View { /* ... */ }

    // auth + subscribed
    public function create(): View { /* ... */ }
    public function store(Request $request): Response { /* ... */ }
    public function edit(Article $article): View { /* ... */ }
    public function update(Request $request, Article $article): Response { /* ... */ }

    // Solo auth (equivalente a estar excluido mediante except)
    public function destroy(Article $article): Response { /* ... */ }
}
```

### Middleware de tipo closure

También puedes pasar un closure al atributo. Resulta útil cuando quieres escribir el proceso en línea.

```php theme={null}
use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Attributes\Controllers\Middleware;

class ReportController
{
    #[Middleware(static function (Request $request, Closure $next) {
        if (! $request->user()->hasRole('analyst')) {
            abort(403);
        }

        return $next($request);
    })]
    public function generate(): Response
    {
        // ...
    }
}
```

### Comparación con el método tradicional `middleware()`

```php theme={null}
// Forma tradicional (implementando la interfaz HasMiddleware)
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;

class UserController implements HasMiddleware
{
    public static function middleware(): array
    {
        return [
            'auth',
            new Middleware('log', only: ['index']),
            new Middleware('subscribed', except: ['store']),
        ];
    }
}

// Forma con atributos
#[Middleware('auth')]
#[Middleware('log', only: ['index'])]
#[Middleware('subscribed', except: ['store'])]
class UserController
{
    // No se requiere implementar HasMiddleware
}
```

<Info>
  Si utilizas atributos no necesitas implementar la interfaz `HasMiddleware`. Sin embargo, mezclar el método `middleware()` con los atributos puede provocar comportamientos inesperados, por lo que se recomienda utilizar solo uno de los dos enfoques.
</Info>

## `#[WithoutMiddleware]` — excluir middleware

Excluye el middleware aplicado a nivel de clase de un método concreto o de toda la clase.

### Aplicación al método

```php theme={null}
use App\Http\Middleware\EnsureTokenIsValid;
use Illuminate\Routing\Attributes\Controllers\Middleware;
use Illuminate\Routing\Attributes\Controllers\WithoutMiddleware;

#[Middleware('auth')]
#[Middleware(EnsureTokenIsValid::class)]
class ApiController
{
    // Se aplican auth y EnsureTokenIsValid
    public function show(Resource $resource): JsonResponse { /* ... */ }

    // auth se aplica, pero EnsureTokenIsValid se excluye
    #[WithoutMiddleware(EnsureTokenIsValid::class)]
    public function index(): JsonResponse { /* ... */ }
}
```

### Aplicación a la clase con `only` / `except`

Si añades `#[WithoutMiddleware]` a nivel de clase, el middleware se excluirá de todas las acciones, incluyendo las de las clases hijas. Con `only` / `except` puedes limitar el alcance de la exclusión.

```php theme={null}
#[Middleware('auth')]
#[WithoutMiddleware('subscribed', except: ['index'])]
class AdminController
{
    // Se aplica subscribed (no se excluye porque está en except: ['index'])
    public function index(): View { /* ... */ }

    // subscribed queda excluido
    public function dashboard(): View { /* ... */ }
    public function settings(): View { /* ... */ }
}
```

<Warning>
  `#[WithoutMiddleware]` solo es válido para el **middleware de ruta**. No puede excluir el middleware global registrado en `app/Http/Kernel.php`.
</Warning>

## `#[Authorize]` — autorización mediante policies

Como azúcar sintáctico del middleware `can`, permite declarar las comprobaciones de autorización basadas en policies mediante un atributo.

### Uso básico

```php theme={null}
use App\Models\Post;
use Illuminate\Routing\Attributes\Controllers\Authorize;

class PostController
{
    // Comprueba la habilidad 'viewAny' (método viewAny de la policy de Post)
    #[Authorize('viewAny', Post::class)]
    public function index(): View { /* ... */ }

    // Comprueba la habilidad 'view' (pasando el parámetro de ruta 'post')
    #[Authorize('view', 'post')]
    public function show(Post $post): View { /* ... */ }

    // Comprueba la habilidad 'create'
    #[Authorize('create', Post::class)]
    public function create(): View { /* ... */ }

    #[Authorize('create', Post::class)]
    public function store(Request $request): Response { /* ... */ }

    // Comprueba la habilidad 'update' (pasando el parámetro de ruta)
    #[Authorize('update', 'post')]
    public function edit(Post $post): View { /* ... */ }

    #[Authorize('update', 'post')]
    public function update(Request $request, Post $post): Response { /* ... */ }

    #[Authorize('delete', 'post')]
    public function destroy(Post $post): Response { /* ... */ }
}
```

### Argumentos de la policy

Como segundo argumento puedes pasar los siguientes valores.

| Valor                      | Descripción                                                                     |
| -------------------------- | ------------------------------------------------------------------------------- |
| `Post::class`              | Clase del modelo (cuando no se necesita una instancia, como en `viewAny`)       |
| `'post'`                   | Nombre del parámetro de ruta (se pasa la instancia vinculada por model binding) |
| `[Comment::class, 'post']` | Varios argumentos (se pasan al método de la policy como un array)               |

```php theme={null}
use App\Models\Comment;
use App\Models\Post;
use Illuminate\Routing\Attributes\Controllers\Authorize;

class CommentController
{
    // Pasa $post (parámetro de ruta) y la clase Comment a la policy
    #[Authorize('create', [Comment::class, 'post'])]
    public function store(Post $post, Request $request): Response
    {
        // ...
    }

    #[Authorize('delete', 'comment')]
    public function destroy(Comment $comment): Response
    {
        // ...
    }
}
```

### Comparación con el middleware `can`

```php theme={null}
// Forma tradicional (fachada Route)
Route::get('/posts', [PostController::class, 'index'])->middleware('can:viewAny,App\Models\Post');
Route::put('/posts/{post}', [PostController::class, 'update'])->middleware('can:update,post');

// Forma con atributos
class PostController
{
    #[Authorize('viewAny', Post::class)]
    public function index(): View { /* ... */ }

    #[Authorize('update', 'post')]
    public function update(Request $request, Post $post): Response { /* ... */ }
}
```

Ya no necesitas declarar el middleware en el archivo de rutas y la lógica de autorización queda concentrada en el controlador.

## Ejemplos prácticos

### Aplicación a un controlador de recursos

```php theme={null}
use App\Models\Article;
use Illuminate\Routing\Attributes\Controllers\Authorize;
use Illuminate\Routing\Attributes\Controllers\Middleware;

#[Middleware('auth')]
class ArticleController
{
    #[Authorize('viewAny', Article::class)]
    public function index(): View
    {
        return view('articles.index', [
            'articles' => Article::paginate(),
        ]);
    }

    #[Authorize('view', 'article')]
    public function show(Article $article): View
    {
        return view('articles.show', compact('article'));
    }

    #[Authorize('create', Article::class)]
    public function create(): View
    {
        return view('articles.create');
    }

    #[Authorize('create', Article::class)]
    public function store(StoreArticleRequest $request): RedirectResponse
    {
        $article = Article::create($request->validated());

        return redirect()->route('articles.show', $article);
    }

    #[Authorize('update', 'article')]
    public function edit(Article $article): View
    {
        return view('articles.edit', compact('article'));
    }

    #[Authorize('update', 'article')]
    public function update(UpdateArticleRequest $request, Article $article): RedirectResponse
    {
        $article->update($request->validated());

        return redirect()->route('articles.show', $article);
    }

    #[Authorize('delete', 'article')]
    public function destroy(Article $article): RedirectResponse
    {
        $article->delete();

        return redirect()->route('articles.index');
    }
}
```

### Aplicación a un controlador de API

```php theme={null}
use Illuminate\Routing\Attributes\Controllers\Middleware;
use Illuminate\Routing\Attributes\Controllers\WithoutMiddleware;

#[Middleware('auth:sanctum')]
#[Middleware('throttle:api')]
class ApiPostController
{
    // El listado se puede consultar sin autenticación
    #[WithoutMiddleware('auth:sanctum')]
    public function index(): JsonResponse
    {
        return response()->json(Post::paginate());
    }

    public function store(Request $request): JsonResponse
    {
        // Solo usuarios autenticados
    }
}
```

## Orden de procesamiento de los atributos

Cuando hay varios atributos aplicados, el orden de procesamiento es el siguiente.

```mermaid theme={null}
flowchart LR
    A["Petición"] --> B["#[Middleware]<br>a nivel de clase"]
    B --> C["#[Middleware]<br>a nivel de método"]
    C --> D["Exclusión de<br>#[WithoutMiddleware]"]
    D --> E["Comprobación de<br>autorización #[Authorize]"]
    E --> F["Ejecución del método<br>del controlador"]
```

<Info>
  `#[Authorize]` funciona internamente como el middleware `can`, por lo que se procesa en el mismo pipeline que `#[Middleware]`.
</Info>

## Resumen: ¿cuál deberías usar?

| Situación                                                        | Enfoque recomendado                    |
| ---------------------------------------------------------------- | -------------------------------------- |
| Controlador nuevo (Laravel 13)                                   | Usar atributos                         |
| Controlador existente con método `middleware()`                  | Migrar progresivamente a atributos     |
| Necesitas configurar middleware de forma dinámica                | Continuar con el método `middleware()` |
| Quieres gestionarlo de forma centralizada en el archivo de rutas | Continuar con `Route::middleware()`    |

## Próximos pasos

<Columns cols={2}>
  <Card title="Atributos PHP (colas y Eloquent)" icon="code" href="/es/advanced/php-attributes">
    Explicación de los atributos PHP que puedes usar en jobs de cola y modelos Eloquent.
  </Card>

  <Card title="Nivel intermedio: controladores" icon="layer-group" href="/es/controllers">
    Aprende el uso básico de los controladores de Laravel.
  </Card>
</Columns>


## Related topics

- [Controladores](/es/controllers.md)
- [Resumen de las novedades de Laravel 13](/es/blog/laravel-13-new-features.md)
- [Actualización de Laravel — Marzo de 2026](/es/blog/changelog/202603.md)
- [Serialización en Eloquent](/es/eloquent-serialization.md)
- [Atributos de PHP](/es/advanced/php-attributes.md)
