메인 콘텐츠로 건너뛰기

PHP 어트리뷰트란

PHP 어트리뷰트(PHP Attributes)는 PHP 8.0에서 도입된 네이티브 메타데이터 문법입니다. 클래스, 메서드, 프로퍼티, 함수 등에 대해 #[AttributeName] 형식으로 메타 정보를 부여할 수 있습니다. Laravel은 프레임워크 본체에서 PHP 어트리뷰트를 적극적으로 채택하고 있으며, 잡이나 Eloquent 모델의 설정을 선언적으로 작성할 수 있습니다. Laravel 13(v13.2.0)에서는 큐 어트리뷰트가 enum을 받을 수 있게 되었습니다. 기존의 클래스 프로퍼티나 메서드 오버라이드 대신 어트리뷰트를 사용하여 더 읽기 쉽고 간결한 코드를 작성할 수 있습니다.
// 기존 방식
class ProcessOrder implements ShouldQueue
{
    public string $queue = 'orders';
    public string $connection = 'redis';
    public int $tries = 3;
    public array $backoff = [30, 60, 120];
}

// 어트리뷰트를 사용한 방식
#[Queue('orders')]
#[Connection('redis')]
#[Tries(3)]
#[Backoff(30, 60, 120)]
class ProcessOrder implements ShouldQueue
{
}
어트리뷰트는 PHP 8.0 이상에서 사용할 수 있습니다. Laravel 13은 PHP 8.3 이상을 요구하기 때문에 모든 환경에서 어트리뷰트를 사용할 수 있습니다.

큐 관련 어트리뷰트

큐 잡과 관련된 어트리뷰트는 모두 Illuminate\Queue\Attributes 네임스페이스에 있습니다.

#[Queue] — 큐 이름 지정

잡이 전송되는 기본 큐 이름을 지정합니다.
use Illuminate\Queue\Attributes\Queue;

#[Queue('emails')]
class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle(): void
    {
        // ...
    }
}
v13.2.0부터는 문자열 대신 enum을 전달할 수도 있습니다.
enum QueueName: string
{
    case Emails = 'emails';
    case Orders = 'orders';
    case Notifications = 'notifications';
}

#[Queue(QueueName::Emails)]
class SendWelcomeEmail implements ShouldQueue
{
    // ...
}
#[Queue] 어트리뷰트는 Attribute::TARGET_CLASS를 대상으로 설정되어 있으므로 클래스에만 적용할 수 있습니다.

#[Connection] — 연결 지정

잡이 사용하는 기본 큐 연결을 지정합니다.
use Illuminate\Queue\Attributes\Connection;

#[Connection('sqs')]
class ProcessPayment implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle(): void
    {
        // ...
    }
}
이쪽도 enum을 사용할 수 있습니다.
enum QueueConnection: string
{
    case Redis = 'redis';
    case Sqs = 'sqs';
    case Database = 'database';
}

#[Connection(QueueConnection::Sqs)]
class ProcessPayment implements ShouldQueue
{
    // ...
}

#[Backoff] — 재시도 백오프 시간 지정

잡이 실패했을 때 재시도까지의 대기 시간(초)을 지정합니다. 여러 값을 전달하면 재시도마다 다른 대기 시간을 설정할 수 있습니다(가변 인자 지원).
use Illuminate\Queue\Attributes\Backoff;

// 고정 대기 시간(모든 재시도에 공통으로 60초 대기)
#[Backoff(60)]
class SendEmail implements ShouldQueue
{
    // ...
}

// 재시도마다 다른 대기 시간(지수 백오프)
#[Backoff(30, 60, 120)]
class ProcessOrder implements ShouldQueue
{
    // ...
}
Backoff 클래스의 구현을 보면 가변 인자를 받도록 설계되어 있습니다.
// Illuminate\Queue\Attributes\Backoff의 구현
#[Attribute(Attribute::TARGET_CLASS)]
class Backoff
{
    public array|int $backoff;

    public function __construct(array|int ...$backoff)
    {
        $this->backoff = count($backoff) === 1 ? $backoff[0] : $backoff;
    }
}
단일 값인 경우 int로, 여러 개인 경우 array로 저장됩니다.

#[Tries] — 재시도 횟수 지정

잡이 실패했을 때의 최대 재시도 횟수를 지정합니다.
use Illuminate\Queue\Attributes\Tries;

#[Tries(5)]
class ProcessPayment implements ShouldQueue
{
    // ...
}

#[Timeout] — 타임아웃 지정

잡의 최대 실행 시간(초)을 지정합니다. 이 시간을 초과하면 잡이 강제 종료됩니다.
use Illuminate\Queue\Attributes\Timeout;

#[Timeout(120)]
class GenerateReport implements ShouldQueue
{
    // ...
}

#[MaxExceptions] — 허용 예외 수 지정

지정한 횟수 이상의 예외가 발생한 경우 잡을 실패로 간주합니다. #[Tries]와 조합하여 사용합니다.
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;

#[Tries(10)]
#[MaxExceptions(3)]
class ProcessWebhook implements ShouldQueue
{
    // ...
}

#[UniqueFor] — 유니크 기간 지정

잡의 중복 실행을 방지하는 잠금 기간(초)을 지정합니다. ShouldBeUnique와 조합하여 사용합니다.
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Attributes\UniqueFor;

#[UniqueFor(3600)]
class SyncUserData implements ShouldQueue, ShouldBeUnique
{
    // ...
}

#[DeleteWhenMissingModels] — 모델이 존재하지 않을 때 삭제

잡이 의존하는 Eloquent 모델이 발견되지 않는 경우, 잡을 실패가 아닌 삭제(스킵)로 처리합니다.
use Illuminate\Queue\Attributes\DeleteWhenMissingModels;

#[DeleteWhenMissingModels]
class SendOrderConfirmation implements ShouldQueue
{
    public function __construct(
        protected Order $order,
    ) {}

    public function handle(): void
    {
        // $order가 존재하지 않는 경우 이 잡이 삭제됨
    }
}

#[WithoutRelations] — 릴레이션 제외

잡을 직렬화할 때 모델의 릴레이션을 포함하지 않도록 합니다. 큐로 전송되는 데이터를 경량화할 수 있습니다.
use Illuminate\Queue\Attributes\WithoutRelations;

#[WithoutRelations]
class ExportUser implements ShouldQueue
{
    public function __construct(
        protected User $user,
    ) {}
}

#[FailOnTimeout] — 타임아웃 시 실패로 처리

타임아웃이 발생한 경우 잡을 실패로 기록합니다(기본적으로 타임아웃은 실패로 기록되지 않습니다).
use Illuminate\Queue\Attributes\FailOnTimeout;
use Illuminate\Queue\Attributes\Timeout;

#[Timeout(30)]
#[FailOnTimeout]
class ProcessLongTask implements ShouldQueue
{
    // ...
}

여러 큐 어트리뷰트 조합하기

이러한 어트리뷰트들을 조합하여 잡의 동작을 선언적으로 설정할 수 있습니다.
use Illuminate\Queue\Attributes\Backoff;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\DeleteWhenMissingModels;
use Illuminate\Queue\Attributes\FailOnTimeout;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Queue;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Tries;

#[Queue('payments')]
#[Connection('redis')]
#[Tries(3)]
#[Backoff(30, 60, 120)]
#[Timeout(60)]
#[MaxExceptions(2)]
#[DeleteWhenMissingModels]
class ProcessPayment implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        protected Order $order,
    ) {}

    public function handle(PaymentService $payment): void
    {
        $payment->charge($this->order);
    }
}

Eloquent 관련 어트리뷰트

Eloquent 모델의 어트리뷰트는 Illuminate\Database\Eloquent\Attributes 네임스페이스에 있습니다. Laravel 13에서는 다수의 어트리뷰트가 추가되었습니다.

#[ScopedBy] — 글로벌 스코프 지정

모델에 자동으로 적용할 글로벌 스코프 클래스를 어트리뷰트로 지정합니다. 상속을 지원하며, IS_REPEATABLE 플래그로 여러 스코프를 지정할 수 있습니다.
use Illuminate\Database\Eloquent\Attributes\ScopedBy;

#[ScopedBy(ActiveScope::class)]
class User extends Model
{
    // booted()에서 스코프를 등록할 필요가 없어짐
}
여러 스코프를 부여할 경우, 어트리뷰트를 반복하거나 배열로 전달합니다.
// 반복 지정(IS_REPEATABLE 대응)
#[ScopedBy(ActiveScope::class)]
#[ScopedBy(VerifiedScope::class)]
class User extends Model
{
}

// 배열로 일괄 지정
#[ScopedBy([ActiveScope::class, VerifiedScope::class])]
class User extends Model
{
}
기존 booted() 메서드와의 비교입니다.
// 기존 방식
class User extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);
        static::addGlobalScope(new VerifiedScope);
    }
}

#[ObservedBy] — 옵저버 지정

모델에 연결할 옵저버 클래스를 어트리뷰트로 지정합니다. ScopedBy와 마찬가지로 IS_REPEATABLE입니다.
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy(UserObserver::class)]
class User extends Model
{
}
여러 옵저버도 지정할 수 있습니다.
#[ObservedBy(UserObserver::class)]
#[ObservedBy(AuditObserver::class)]
class User extends Model
{
}
기존의 AppServiceProvider에서의 등록이 필요 없어집니다.
// 기존 방식(AppServiceProvider)
public function boot(): void
{
    User::observe(UserObserver::class);
}

#[UseEloquentBuilder] — 커스텀 쿼리 빌더 지정

모델이 사용하는 커스텀 Eloquent 빌더를 어트리뷰트로 지정합니다.
use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder;

#[UseEloquentBuilder(UserQueryBuilder::class)]
class User extends Model
{
}
namespace App\Builders;

use Illuminate\Database\Eloquent\Builder;

class UserQueryBuilder extends Builder
{
    public function active(): static
    {
        return $this->where('active', true);
    }

    public function verified(): static
    {
        return $this->whereNotNull('email_verified_at');
    }
}
// 사용 예시(커스텀 메서드를 타입 안전하게 호출 가능)
$users = User::query()->active()->verified()->get();

#[CollectedBy] — 커스텀 컬렉션 지정

모델의 컬렉션으로 사용할 커스텀 컬렉션 클래스를 어트리뷰트로 지정합니다.
use Illuminate\Database\Eloquent\Attributes\CollectedBy;

#[CollectedBy(UserCollection::class)]
class User extends Model
{
}
namespace App\Collections;

use Illuminate\Database\Eloquent\Collection;

class UserCollection extends Collection
{
    public function admins(): static
    {
        return $this->filter(fn (User $user) => $user->is_admin);
    }

    public function active(): static
    {
        return $this->filter(fn (User $user) => $user->active);
    }
}

#[Table] — 테이블 설정을 일괄 지정

테이블 이름, 기본 키, 타임스탬프 등 여러 테이블 관련 설정을 하나의 어트리뷰트로 지정할 수 있습니다.
use Illuminate\Database\Eloquent\Attributes\Table;

#[Table(name: 'system_users', key: 'user_id', timestamps: false)]
class SystemUser extends Model
{
}
Table 어트리뷰트에서 설정할 수 있는 옵션은 다음과 같습니다.
파라미터대응하는 프로퍼티설명
name$table테이블 이름
key$primaryKey기본 키 컬럼 이름
keyType$keyType기본 키의 타입('int', 'string' 등)
incrementing$incrementing기본 키의 자동 증가
timestamps$timestamps타임스탬프 활성화/비활성화
dateFormat$dateFormat날짜 포맷

#[Scope] — 메서드를 로컬 스코프로 정의

scope 접두사 없는 메서드를 Eloquent의 로컬 스코프로 정의할 수 있습니다.
use Illuminate\Database\Eloquent\Attributes\Scope;

class User extends Model
{
    #[Scope]
    public function active(Builder $query): void
    {
        $query->where('active', true);
    }

    #[Scope]
    public function verified(Builder $query): void
    {
        $query->whereNotNull('email_verified_at');
    }
}
// 기존에는 메서드 이름에 "scope" 접두사가 필요했음
// public function scopeActive(Builder $query): void

// 어트리뷰트를 사용하면 메서드 이름이 그대로 스코프 이름이 됨
User::query()->active()->verified()->get();

#[UseFactory] — 팩토리 클래스 지정

모델이 사용하는 커스텀 팩토리 클래스를 어트리뷰트로 지정합니다.
use Illuminate\Database\Eloquent\Attributes\UseFactory;

#[UseFactory(UserFactory::class)]
class User extends Model
{
}

그 밖의 Eloquent 어트리뷰트

어트리뷰트설명
#[Fillable(...$attributes)]매스 어사인먼트에서 허용할 컬럼 지정
#[Guarded(...$attributes)]매스 어사인먼트에서 보호할 컬럼 지정
#[Unguarded]매스 어사인먼트 보호 비활성화
#[Hidden(...$attributes)]직렬화 시 제외할 컬럼 지정
#[Visible(...$attributes)]직렬화 시 포함할 컬럼 지정
#[Appends(...$attributes)]직렬화 시 추가할 액세서 지정
#[Touches(...$relations)]업데이트 시 updated_at을 갱신할 릴레이션 지정
#[WithoutTimestamps]타임스탬프 비활성화
#[WithoutIncrementing]기본 키 자동 증가 비활성화
#[DateFormat(format: '...')]날짜 포맷 지정
#[UsePolicy(policyClass: '...')]관련 폴리시 클래스 지정
#[UseResource(resourceClass: '...')]관련 API 리소스 클래스 지정
#[UseResourceCollection(resourceCollectionClass: '...')]관련 리소스 컬렉션 클래스 지정

Enum 지원(v13.2.0에서 추가)

v13.2.0에서는 #[Queue]#[Connection]이 enum을 받을 수 있게 되었습니다. 이를 통해 문자열 리터럴 대신 PHP enum을 사용하여 타입 안전하게 큐와 연결을 지정할 수 있습니다.
// 큐 이름 enum 정의
enum Queue: string
{
    case Default = 'default';
    case High = 'high';
    case Low = 'low';
    case Emails = 'emails';
    case Orders = 'orders';
}

// 연결 enum 정의
enum Connection: string
{
    case Redis = 'redis';
    case Sqs = 'sqs';
    case Database = 'database';
    case Sync = 'sync';
}
use Illuminate\Queue\Attributes\Queue;
use Illuminate\Queue\Attributes\Connection;

// enum을 사용한 타입 안전한 지정
#[Queue(Queue::Orders)]
#[Connection(Connection::Redis)]
class ProcessOrder implements ShouldQueue
{
    // ...
}
enum을 사용하면 큐 이름이나 연결 이름의 오타를 방지하고 IDE 자동 완성도 활용할 수 있습니다. 애플리케이션 전체에서 큐 이름과 연결 이름을 일원화하여 관리하기에 편리합니다.

기존 클래스 프로퍼티와의 비교

어트리뷰트의 장점

  • 선언적 — 클래스의 상단만 봐도 잡의 동작을 한눈에 파악할 수 있음
  • 타입 안전 — enum을 사용하면 IDE 자동 완성과 타입 검사가 동작함
  • 상속과의 친화성 — 부모 클래스의 어트리뷰트를 자식 클래스에서 덮어쓸 수 있음
  • 코드 감소 — 프로퍼티 선언이나 메서드 오버라이드가 불필요

어트리뷰트의 단점

  • 동적 값을 설정할 수 없음 — 어트리뷰트의 인자는 컴파일 시 상수만 가능. 변수나 설정 파일의 값은 사용할 수 없음
  • 익숙해질 시간이 필요 — 팀에서 PHP 8의 어트리뷰트 문법에 익숙해질 필요가 있는 경우도 있음

동적 값이 필요한 경우

실행 시에 값을 결정하고 싶은 경우에는 기존의 메서드 오버라이드를 사용합니다.
class ProcessOrder implements ShouldQueue
{
    // 동적인 백오프는 메서드로 정의
    public function backoff(): array
    {
        return [
            config('queue.backoff.first'),
            config('queue.backoff.second'),
        ];
    }
}
어트리뷰트는 PHP 컴파일 시에 분석됩니다. config()env()와 같은 런타임 값을 사용할 수는 없습니다. 동적인 설정이 필요한 경우에는 계속해서 클래스 프로퍼티나 메서드를 사용하세요.

구현 방식

Laravel은 내부적으로 Reflection API를 사용하여 어트리뷰트를 읽어 옵니다. 큐 워커가 잡을 디스패치할 때 ReadsQueueAttributes 트레이트(InteractsWithQueue에 포함되어 있음)가 리플렉션으로 어트리뷰트를 검출하여 해당하는 프로퍼티에 값을 설정합니다.
// 내부에서의 읽기 이미지(간략화)
$reflection = new ReflectionClass($job);
$attributes = $reflection->getAttributes(Queue::class);

foreach ($attributes as $attribute) {
    $instance = $attribute->newInstance();
    $job->queue = $instance->queue instanceof UnitEnum
        ? $instance->queue->value
        : $instance->queue;
}
Eloquent 모델의 어트리뷰트도 마찬가지로 Model::booted()에 해당하는 시점에 리플렉션에 의해 읽힙니다.

다음 단계

중급: 큐와 잡

Laravel 큐 시스템의 기본적인 사용법을 배웁니다.

PHP Reflection API

Laravel이 어트리뷰트를 읽어 오기 위해 사용하는 Reflection API의 구조를 자세히 설명합니다.
마지막 수정일 2026년 7월 13일