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

# 컨트롤러 PHP 어트리뷰트

> Laravel 13에서 추가된 #[Middleware], #[WithoutMiddleware], #[Authorize] 어트리뷰트를 사용해 컨트롤러에 미들웨어 및 인가 설정을 선언적으로 작성하는 방법을 설명합니다.

## 개요

Laravel 13에서는 컨트롤러에 대한 미들웨어 할당과 인가 검사를 PHP 어트리뷰트로 선언할 수 있게 되었습니다. 기존의 `middleware()` 메서드나 `can` 미들웨어 대신, 클래스나 메서드에 직접 어트리뷰트를 부여하는 것만으로 설정이 완료됩니다.

```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>
  컨트롤러 어트리뷰트는 모두 `Illuminate\Routing\Attributes\Controllers` 네임스페이스에 있습니다.
</Tip>

## `#[Middleware]` — 미들웨어 할당하기

### 클래스에 적용

클래스 레벨에 `#[Middleware]`를 부여하면, 해당 컨트롤러의 모든 액션에 미들웨어가 적용됩니다.

```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 { /* ... */ }
}
```

여러 미들웨어를 부여하려면 어트리뷰트를 반복해서 사용합니다.

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

### 메서드에 적용

메서드 레벨에 부여한 미들웨어는 클래스 레벨의 미들웨어와 병합됩니다.

```php theme={null}
#[Middleware('auth')]
class UserController
{
    // 모든 액션에 auth만 적용
    public function index(): View { /* ... */ }

    // auth + subscribed 두 가지 모두 적용됨
    #[Middleware('subscribed')]
    public function create(): View { /* ... */ }
}
```

### `only` / `except`로 대상 좁히기

클래스 레벨 어트리뷰트에 `only` 또는 `except`를 지정하면 대상 메서드를 좁힐 수 있습니다.

```php theme={null}
#[Middleware('auth')]
#[Middleware('subscribed', only: ['create', 'store', 'edit', 'update'])]
class ArticleController
{
    // 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 { /* ... */ }

    // auth만 (except로 제외한 것과 동일)
    public function destroy(Article $article): Response { /* ... */ }
}
```

### 클로저 미들웨어

어트리뷰트에는 클로저도 사용할 수 있습니다. 인라인으로 처리를 작성하고 싶을 때 편리합니다.

```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
    {
        // ...
    }
}
```

### 기존 `middleware()` 메서드와 비교

```php theme={null}
// 기존 방식 (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']),
        ];
    }
}

// 어트리뷰트를 사용한 방식
#[Middleware('auth')]
#[Middleware('log', only: ['index'])]
#[Middleware('subscribed', except: ['store'])]
class UserController
{
    // HasMiddleware 구현 불필요
}
```

<Info>
  어트리뷰트를 사용할 때는 `HasMiddleware` 인터페이스를 구현할 필요가 없습니다. 다만 `middleware()` 메서드와 어트리뷰트를 혼용하면 의도치 않은 동작이 발생할 수 있으므로, 한쪽으로 통일하는 것을 권장합니다.
</Info>

## `#[WithoutMiddleware]` — 미들웨어 제외하기

클래스 레벨에서 적용된 미들웨어를 특정 메서드 또는 클래스 전체에서 제외합니다.

### 메서드에 적용

```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
{
    // auth와 EnsureTokenIsValid가 적용됨
    public function show(Resource $resource): JsonResponse { /* ... */ }

    // auth는 적용되지만 EnsureTokenIsValid는 제외됨
    #[WithoutMiddleware(EnsureTokenIsValid::class)]
    public function index(): JsonResponse { /* ... */ }
}
```

### 클래스에 적용 및 `only` / `except`

`#[WithoutMiddleware]`를 클래스 레벨에 부여하면, 자식 클래스를 포함한 모든 액션에서 미들웨어가 제외됩니다. `only` / `except`로 제외 범위를 제한할 수 있습니다.

```php theme={null}
#[Middleware('auth')]
#[WithoutMiddleware('subscribed', except: ['index'])]
class AdminController
{
    // subscribed가 적용됨 (except: ['index']로 제외되지 않음)
    public function index(): View { /* ... */ }

    // subscribed는 제외됨
    public function dashboard(): View { /* ... */ }
    public function settings(): View { /* ... */ }
}
```

<Warning>
  `#[WithoutMiddleware]`는 **라우트 미들웨어**에만 유효합니다. `app/Http/Kernel.php`에 등록된 전역 미들웨어는 제외할 수 없습니다.
</Warning>

## `#[Authorize]` — 정책 기반 인가

`can` 미들웨어의 문법적 편의로, 정책 기반 인가 검사를 어트리뷰트로 선언할 수 있습니다.

### 기본 사용법

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

class PostController
{
    // 'viewAny' 능력 검사 (Post 정책의 viewAny 메서드)
    #[Authorize('viewAny', Post::class)]
    public function index(): View { /* ... */ }

    // 'view' 능력 검사 (라우트 파라미터 'post'를 전달)
    #[Authorize('view', 'post')]
    public function show(Post $post): View { /* ... */ }

    // 'create' 능력 검사
    #[Authorize('create', Post::class)]
    public function create(): View { /* ... */ }

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

    // 'update' 능력 검사 (라우트 파라미터 전달)
    #[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 { /* ... */ }
}
```

### 정책 인수

두 번째 인수에는 다음과 같은 값을 전달할 수 있습니다.

| 전달 값                       | 설명                                       |
| -------------------------- | ---------------------------------------- |
| `Post::class`              | 모델 클래스 (`viewAny` 등 모델 인스턴스가 필요하지 않은 경우) |
| `'post'`                   | 라우트 파라미터 이름 (모델 바인딩된 인스턴스가 전달됨)          |
| `[Comment::class, 'post']` | 여러 인수 (정책 메서드에 배열로 전달됨)                  |

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

class CommentController
{
    // $post(라우트 파라미터)와 Comment 클래스를 정책에 전달
    #[Authorize('create', [Comment::class, 'post'])]
    public function store(Post $post, Request $request): Response
    {
        // ...
    }

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

### `can` 미들웨어와 비교

```php theme={null}
// 기존 방식 (Route 파사드)
Route::get('/posts', [PostController::class, 'index'])->middleware('can:viewAny,App\Models\Post');
Route::put('/posts/{post}', [PostController::class, 'update'])->middleware('can:update,post');

// 어트리뷰트를 사용한 방식
class PostController
{
    #[Authorize('viewAny', Post::class)]
    public function index(): View { /* ... */ }

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

라우트 파일에 미들웨어를 작성할 필요가 없어지고, 인가 로직이 컨트롤러에 집중됩니다.

## 실전 예제

### 리소스 컨트롤러에 적용

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

### API 컨트롤러에 적용

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

#[Middleware('auth:sanctum')]
#[Middleware('throttle:api')]
class ApiPostController
{
    // 목록 조회는 인증 없이 가능
    #[WithoutMiddleware('auth:sanctum')]
    public function index(): JsonResponse
    {
        return response()->json(Post::paginate());
    }

    public function store(Request $request): JsonResponse
    {
        // 인증된 사용자만
    }
}
```

## 어트리뷰트 처리 순서

여러 어트리뷰트가 부여된 경우, 처리 순서는 다음과 같습니다.

```mermaid theme={null}
flowchart LR
    A["요청"] --> B["클래스 레벨<br>#[Middleware]"]
    B --> C["메서드 레벨<br>#[Middleware]"]
    C --> D["#[WithoutMiddleware]<br>제외 처리"]
    D --> E["#[Authorize]<br>인가 검사"]
    E --> F["컨트롤러<br>메서드 실행"]
```

<Info>
  `#[Authorize]`는 내부적으로 `can` 미들웨어로 동작하므로, `#[Middleware]`와 동일한 파이프라인에서 처리됩니다.
</Info>

## 정리: 어느 것을 사용해야 할까

| 상황                             | 권장 접근법                      |
| ------------------------------ | --------------------------- |
| 신규 컨트롤러 (Laravel 13)           | 어트리뷰트 사용                    |
| 기존 `middleware()` 메서드를 가진 컨트롤러 | 점진적으로 어트리뷰트로 마이그레이션         |
| 동적인 미들웨어 설정이 필요                | `middleware()` 메서드 계속 사용    |
| 라우트 파일에서 일괄 관리하고 싶음            | `Route::middleware()` 계속 사용 |

## 다음 단계

<Columns cols={2}>
  <Card title="PHP 어트리뷰트 (큐·Eloquent)" icon="code" href="/ko/advanced/php-attributes">
    큐 잡이나 Eloquent 모델에서 사용할 수 있는 PHP 어트리뷰트를 설명합니다.
  </Card>

  <Card title="중급: 컨트롤러" icon="layer-group" href="/ko/controllers">
    Laravel 컨트롤러의 기본적인 사용법을 배웁니다.
  </Card>
</Columns>


## Related topics

- [Laravel 13 신기능 정리](/ko/blog/laravel-13-new-features.md)
- [2026년 3월 Laravel 업데이트](/ko/blog/changelog/202603.md)
- [PHP 어트리뷰트](/ko/advanced/php-attributes.md)
- [PHP Reflection API](/ko/advanced/php-reflection.md)
- [서비스 컨테이너](/ko/service-container.md)
