Vai al contenuto principale

Cosa sono gli attributi PHP

Gli attributi PHP sono una sintassi nativa di metadati introdotta con PHP 8.0. Permettono di applicare meta-informazioni a classi, metodi, proprietà, funzioni ecc. nella forma #[AttributeName]. Laravel adotta attivamente gli attributi PHP nel framework, e ti permette di configurare in modo dichiarativo job e modelli Eloquent. In Laravel 13 (v13.2.0) gli attributi per le queue accettano enum. Al posto delle proprietà di classe o degli override di metodi puoi scrivere codice più leggibile e conciso.
// scrittura tradizionale
class ProcessOrder implements ShouldQueue
{
    public string $queue = 'orders';
    public string $connection = 'redis';
    public int $tries = 3;
    public array $backoff = [30, 60, 120];
}

// scrittura con attributi
#[Queue('orders')]
#[Connection('redis')]
#[Tries(3)]
#[Backoff(30, 60, 120)]
class ProcessOrder implements ShouldQueue
{
}
Gli attributi sono disponibili da PHP 8.0. Laravel 13 richiede PHP 8.3 o superiore, quindi puoi usare gli attributi in tutti gli ambienti.

Attributi per le code

Gli attributi relativi ai job in coda si trovano tutti nel namespace Illuminate\Queue\Attributes.

#[Queue] — indicare il nome della coda

Indica il nome della coda di default in cui il job viene inviato.
use Illuminate\Queue\Attributes\Queue;

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

    public function handle(): void
    {
        // ...
    }
}
Da v13.2.0 puoi passare anche un enum al posto della stringa.
enum QueueName: string
{
    case Emails = 'emails';
    case Orders = 'orders';
    case Notifications = 'notifications';
}

#[Queue(QueueName::Emails)]
class SendWelcomeEmail implements ShouldQueue
{
    // ...
}
L’attributo #[Queue] è impostato con target Attribute::TARGET_CLASS, quindi è applicabile solo alle classi.

#[Connection] — indicare la connessione

Indica la connection di coda di default usata dal job.
use Illuminate\Queue\Attributes\Connection;

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

    public function handle(): void
    {
        // ...
    }
}
Anche qui puoi usare un enum.
enum QueueConnection: string
{
    case Redis = 'redis';
    case Sqs = 'sqs';
    case Database = 'database';
}

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

#[Backoff] — tempo di backoff per i retry

Indica il tempo di attesa (in secondi) prima di ritentare quando il job fallisce. Passando più valori puoi impostare un tempo diverso per ogni retry (supporta argomenti variadic).
use Illuminate\Queue\Attributes\Backoff;

// attesa fissa (60 secondi comune a tutti i retry)
#[Backoff(60)]
class SendEmail implements ShouldQueue
{
    // ...
}

// attese diverse per ogni retry (exponential backoff)
#[Backoff(30, 60, 120)]
class ProcessOrder implements ShouldQueue
{
    // ...
}
Guardando l’implementazione della classe Backoff è progettata per ricevere argomenti variadic.
// implementazione di 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;
    }
}
Un singolo valore viene memorizzato come int, più valori come array.

#[Tries] — numero di retry

Indica il numero massimo di retry quando il job fallisce.
use Illuminate\Queue\Attributes\Tries;

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

#[Timeout] — timeout

Indica il tempo massimo di esecuzione del job (in secondi). Superata questa soglia il job viene terminato forzatamente.
use Illuminate\Queue\Attributes\Timeout;

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

#[MaxExceptions] — numero massimo di eccezioni tollerate

Se avvengono più eccezioni del numero indicato, il job viene considerato fallito. Da combinare con #[Tries].
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;

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

#[UniqueFor] — periodo di unicità

Indica il periodo di lock (in secondi) per evitare esecuzioni duplicate. Da usare con ShouldBeUnique.
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Attributes\UniqueFor;

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

#[DeleteWhenMissingModels] — cancella se il modello non esiste

Se un modello Eloquent da cui dipende il job non viene trovato, il job viene cancellato (saltato) anziché fallito.
use Illuminate\Queue\Attributes\DeleteWhenMissingModels;

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

    public function handle(): void
    {
        // se $order non esiste, questo job viene cancellato
    }
}

#[WithoutRelations] — escludere le relation

Fa in modo che, al momento della serializzazione, le relation del modello non vengano incluse. Alleggerisce i dati inviati in coda.
use Illuminate\Queue\Attributes\WithoutRelations;

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

#[FailOnTimeout] — timeout come fallimento

Registra il job come fallito quando avviene un timeout (di default il timeout non viene registrato come fallimento).
use Illuminate\Queue\Attributes\FailOnTimeout;
use Illuminate\Queue\Attributes\Timeout;

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

Combinare più attributi delle code

Combinando questi attributi configuri in modo dichiarativo il comportamento del job.
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);
    }
}

Attributi per Eloquent

Gli attributi per i modelli Eloquent si trovano nel namespace Illuminate\Database\Eloquent\Attributes. In Laravel 13 ne sono stati aggiunti molti.

#[ScopedBy] — indicare un global scope

Indica con un attributo la classe di global scope da applicare automaticamente al modello. Supporta l’ereditarietà; con il flag IS_REPEATABLE puoi indicare più scope.
use Illuminate\Database\Eloquent\Attributes\ScopedBy;

#[ScopedBy(ActiveScope::class)]
class User extends Model
{
    // non serve più registrare lo scope in booted()
}
Per più scope puoi ripetere l’attributo o passare un array.
// specifica ripetuta (supporta IS_REPEATABLE)
#[ScopedBy(ActiveScope::class)]
#[ScopedBy(VerifiedScope::class)]
class User extends Model
{
}

// specifica raggruppata in array
#[ScopedBy([ActiveScope::class, VerifiedScope::class])]
class User extends Model
{
}
Confronto con il metodo booted() tradizionale.
// scrittura tradizionale
class User extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);
        static::addGlobalScope(new VerifiedScope);
    }
}

#[ObservedBy] — indicare un observer

Indica con un attributo la classe observer associata al modello. Come ScopedBy, è IS_REPEATABLE.
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy(UserObserver::class)]
class User extends Model
{
}
Puoi indicare più observer.
#[ObservedBy(UserObserver::class)]
#[ObservedBy(AuditObserver::class)]
class User extends Model
{
}
Non serve più la registrazione in AppServiceProvider.
// registrazione tradizionale (AppServiceProvider)
public function boot(): void
{
    User::observe(UserObserver::class);
}

#[UseEloquentBuilder] — indicare un query builder custom

Indica con un attributo l’Eloquent builder personalizzato usato dal modello.
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');
    }
}
// utilizzo (chiamata dei metodi custom con type safety)
$users = User::query()->active()->verified()->get();

#[CollectedBy] — indicare una collection custom

Indica con un attributo la classe di collection personalizzata usata per il modello.
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] — configurazione tabella in un attributo

Puoi indicare in un unico attributo più impostazioni della tabella: nome, primary key, timestamps.
use Illuminate\Database\Eloquent\Attributes\Table;

#[Table(name: 'system_users', key: 'user_id', timestamps: false)]
class SystemUser extends Model
{
}
Opzioni configurabili con l’attributo Table:
ParametroProprietà corrispondenteDescrizione
name$tableNome della tabella
key$primaryKeyNome della colonna della primary key
keyType$keyTypeTipo della primary key ('int', 'string', ecc.)
incrementing$incrementingAutoincremento della primary key
timestamps$timestampsAbilitazione dei timestamps
dateFormat$dateFormatFormato delle date

#[Scope] — definire un metodo come local scope

Puoi definire un metodo come local scope Eloquent senza prefisso scope.
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');
    }
}
// in passato serviva il prefisso "scope" nel nome
// public function scopeActive(Builder $query): void

// con l'attributo il nome del metodo è direttamente il nome dello scope
User::query()->active()->verified()->get();

#[UseFactory] — indicare la classe factory

Indica con un attributo la classe factory personalizzata del modello.
use Illuminate\Database\Eloquent\Attributes\UseFactory;

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

Altri attributi Eloquent

AttributoDescrizione
#[Fillable(...$attributes)]Indica le colonne consentite dal mass assignment
#[Guarded(...$attributes)]Indica le colonne protette dal mass assignment
#[Unguarded]Disabilita la protezione del mass assignment
#[Hidden(...$attributes)]Indica le colonne da escludere in serializzazione
#[Visible(...$attributes)]Indica le colonne da includere in serializzazione
#[Appends(...$attributes)]Indica gli accessor da aggiungere in serializzazione
#[Touches(...$relations)]Indica le relation di cui aggiornare updated_at in update
#[WithoutTimestamps]Disabilita i timestamp
#[WithoutIncrementing]Disabilita l’autoincremento della primary key
#[DateFormat(format: '...')]Indica il formato delle date
#[UsePolicy(policyClass: '...')]Indica la policy class collegata
#[UseResource(resourceClass: '...')]Indica la resource API collegata
#[UseResourceCollection(resourceCollectionClass: '...')]Indica la resource collection collegata

Supporto agli enum (aggiunto in v13.2.0)

In v13.2.0 #[Queue] e #[Connection] accettano enum. Al posto delle stringhe letterali puoi indicare queue e connection in modo type-safe con enum PHP.
// definizione enum del nome coda
enum Queue: string
{
    case Default = 'default';
    case High = 'high';
    case Low = 'low';
    case Emails = 'emails';
    case Orders = 'orders';
}

// definizione enum della connection
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;

// specifica type-safe con enum
#[Queue(Queue::Orders)]
#[Connection(Connection::Redis)]
class ProcessOrder implements ShouldQueue
{
    // ...
}
Con gli enum eviti errori di battitura in nomi di code e connection e sfrutti l’autocompletamento dell’IDE. È utile per gestire in modo centralizzato nomi di coda e connection nell’intera applicazione.

Confronto con le proprietà di classe tradizionali

Vantaggi degli attributi

  • Dichiarativi — guardando l’inizio della classe capisci a colpo d’occhio il comportamento del job
  • Type-safe — con gli enum ottieni autocompletamento e type check dell’IDE
  • Buoni con l’ereditarietà — puoi sovrascrivere in una classe figlia gli attributi della classe padre
  • Meno codice — niente dichiarazioni di proprietà o override di metodi

Svantaggi

  • Non puoi impostare valori dinamici — gli argomenti degli attributi sono solo costanti a compile time: variabili o valori di config non funzionano
  • Serve familiarità — a volte il team ha bisogno di prendere confidenza con la sintassi degli attributi PHP 8

Se ti servono valori dinamici

Se vuoi decidere i valori a runtime, usa il tradizionale override di metodo.
class ProcessOrder implements ShouldQueue
{
    // backoff dinamico definito come metodo
    public function backoff(): array
    {
        return [
            config('queue.backoff.first'),
            config('queue.backoff.second'),
        ];
    }
}
Gli attributi vengono analizzati al momento della compilazione di PHP. Non puoi usare valori a runtime come config() o env(). Se ti servono impostazioni dinamiche, continua a usare proprietà o metodi di classe.

Meccanismo di implementazione

Internamente Laravel usa la Reflection API per leggere gli attributi. Quando il queue worker fa dispatch del job, il trait ReadsQueueAttributes (incluso in InteractsWithQueue) individua gli attributi via reflection e imposta i valori sulle proprietà corrispondenti.
// idea della lettura interna (semplificata)
$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;
}
Anche gli attributi dei modelli Eloquent vengono letti in modo analogo, al momento equivalente a Model::booted().

Prossimi passi

Intermedio: code e job

Impara l’uso di base del sistema code di Laravel.

PHP Reflection API

Approfondisci il meccanismo della Reflection API usata da Laravel per leggere gli attributi.
Ultima modifica il 13 luglio 2026