Passer au contenu principal

Que sont les attributs PHP

Les attributs PHP (PHP Attributes) sont une syntaxe native de métadonnées introduite avec PHP 8.0. Ils permettent de rattacher des méta-informations à des classes, méthodes, propriétés ou fonctions sous la forme #[AttributeName]. Laravel les adopte activement dans son cœur : la configuration des jobs et des modèles Eloquent peut être écrite de manière déclarative. Depuis Laravel 13 (v13.2.0), les attributs de queue acceptent les enum. À la place des propriétés de classe et des surcharges de méthodes traditionnelles, on peut écrire un code plus lisible et concis avec des attributs.
// Écriture classique
class ProcessOrder implements ShouldQueue
{
    public string $queue = 'orders';
    public string $connection = 'redis';
    public int $tries = 3;
    public array $backoff = [30, 60, 120];
}

// Écriture avec attributs
#[Queue('orders')]
#[Connection('redis')]
#[Tries(3)]
#[Backoff(30, 60, 120)]
class ProcessOrder implements ShouldQueue
{
}
Les attributs sont disponibles à partir de PHP 8.0. Comme Laravel 13 requiert PHP 8.3 ou plus, vous pouvez les utiliser dans tous les environnements.

Attributs liés aux queues

Tous les attributs relatifs aux jobs de queue résident dans le namespace Illuminate\Queue\Attributes.

#[Queue] — spécifier le nom de queue

Indique la queue par défaut dans laquelle le job sera envoyé.
use Illuminate\Queue\Attributes\Queue;

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

    public function handle(): void
    {
        // ...
    }
}
Depuis la v13.2.0, vous pouvez passer un enum à la place d’une chaîne.
enum QueueName: string
{
    case Emails = 'emails';
    case Orders = 'orders';
    case Notifications = 'notifications';
}

#[Queue(QueueName::Emails)]
class SendWelcomeEmail implements ShouldQueue
{
    // ...
}
L’attribut #[Queue] est déclaré avec la cible Attribute::TARGET_CLASS, il ne peut donc s’appliquer qu’à une classe.

#[Connection] — spécifier la connexion

Indique la connexion de queue par défaut utilisée par le job.
use Illuminate\Queue\Attributes\Connection;

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

    public function handle(): void
    {
        // ...
    }
}
Ici aussi, on peut utiliser un enum.
enum QueueConnection: string
{
    case Redis = 'redis';
    case Sqs = 'sqs';
    case Database = 'database';
}

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

#[Backoff] — spécifier le temps d’attente entre tentatives

Indique le temps d’attente (en secondes) avant de retenter un job en échec. On peut passer plusieurs valeurs pour définir un délai différent par tentative (arguments variadiques).
use Illuminate\Queue\Attributes\Backoff;

// Attente fixe (60 secondes à chaque tentative)
#[Backoff(60)]
class SendEmail implements ShouldQueue
{
    // ...
}

// Attente différente par tentative (backoff exponentiel)
#[Backoff(30, 60, 120)]
class ProcessOrder implements ShouldQueue
{
    // ...
}
L’implémentation de la classe Backoff reçoit des arguments variadiques.
// Implémentation de 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;
    }
}
Une seule valeur est stockée en int, plusieurs en array.

#[Tries] — spécifier le nombre de tentatives

Indique le nombre maximum de tentatives en cas d’échec du job.
use Illuminate\Queue\Attributes\Tries;

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

#[Timeout] — spécifier un délai maximal

Indique la durée maximale d’exécution (en secondes) du job. Au-delà, le job est forcé à s’arrêter.
use Illuminate\Queue\Attributes\Timeout;

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

#[MaxExceptions] — spécifier le nombre d’exceptions tolérées

Considère le job comme échoué si plus d’un certain nombre d’exceptions surviennent. À combiner avec #[Tries].
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;

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

#[UniqueFor] — spécifier la durée d’unicité

Indique la période de verrouillage (en secondes) empêchant l’exécution en double d’un job. À combiner avec ShouldBeUnique.
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Attributes\UniqueFor;

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

#[DeleteWhenMissingModels] — supprimer si les modèles n’existent plus

Si un modèle Eloquent dont dépend le job est introuvable, celui-ci est traité comme une suppression (skip) plutôt qu’un échec.
use Illuminate\Queue\Attributes\DeleteWhenMissingModels;

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

    public function handle(): void
    {
        // Ce job sera supprimé si $order n'existe pas
    }
}

#[WithoutRelations] — exclure les relations

Exclut les relations d’un modèle lors de la sérialisation du job. Cela allège les données envoyées à la queue.
use Illuminate\Queue\Attributes\WithoutRelations;

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

#[FailOnTimeout] — traiter le timeout comme un échec

Enregistre un job dépassant son timeout comme un échec (par défaut, le timeout ne l’est pas).
use Illuminate\Queue\Attributes\FailOnTimeout;
use Illuminate\Queue\Attributes\Timeout;

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

Combiner plusieurs attributs de queue

Vous pouvez combiner ces attributs pour configurer le comportement du job de façon déclarative.
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);
    }
}

Attributs liés à Eloquent

Les attributs des modèles Eloquent sont dans le namespace Illuminate\Database\Eloquent\Attributes. Laravel 13 en introduit un grand nombre.

#[ScopedBy] — spécifier un scope global

Applique automatiquement une classe de scope global au modèle via un attribut. L’héritage est pris en charge, et le flag IS_REPEATABLE permet d’indiquer plusieurs scopes.
use Illuminate\Database\Eloquent\Attributes\ScopedBy;

#[ScopedBy(ActiveScope::class)]
class User extends Model
{
    // Il n'est plus nécessaire d'enregistrer le scope dans booted()
}
Pour plusieurs scopes, répétez l’attribut ou passez un tableau.
// Spécification par répétition (grâce à IS_REPEATABLE)
#[ScopedBy(ActiveScope::class)]
#[ScopedBy(VerifiedScope::class)]
class User extends Model
{
}

// Spécification groupée par tableau
#[ScopedBy([ActiveScope::class, VerifiedScope::class])]
class User extends Model
{
}
Comparaison avec l’ancienne méthode booted().
// Écriture classique
class User extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);
        static::addGlobalScope(new VerifiedScope);
    }
}

#[ObservedBy] — spécifier un observer

Associe un observer à un modèle via un attribut. Comme ScopedBy, il est IS_REPEATABLE.
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy(UserObserver::class)]
class User extends Model
{
}
Vous pouvez aussi spécifier plusieurs observers.
#[ObservedBy(UserObserver::class)]
#[ObservedBy(AuditObserver::class)]
class User extends Model
{
}
L’enregistrement dans AppServiceProvider n’est plus nécessaire.
// Écriture classique (AppServiceProvider)
public function boot(): void
{
    User::observe(UserObserver::class);
}

#[UseEloquentBuilder] — spécifier un query builder personnalisé

Spécifie via attribut le builder Eloquent personnalisé qu’utilise le modèle.
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');
    }
}
// Exemple d'utilisation (les méthodes personnalisées peuvent être appelées de manière typée)
$users = User::query()->active()->verified()->get();

#[CollectedBy] — spécifier une collection personnalisée

Spécifie via attribut la classe de collection à utiliser pour les collections du modèle.
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] — regrouper la configuration de table

Nom de table, clé primaire, timestamps : plusieurs paramètres de configuration liés à la table se spécifient via un unique attribut.
use Illuminate\Database\Eloquent\Attributes\Table;

#[Table(name: 'system_users', key: 'user_id', timestamps: false)]
class SystemUser extends Model
{
}
Les options configurables via l’attribut Table sont les suivantes.
ParamètrePropriété correspondanteDescription
name$tableNom de la table
key$primaryKeyNom de la colonne clé primaire
keyType$keyTypeType de la clé primaire ('int', 'string', etc.)
incrementing$incrementingAuto-incrémentation de la clé primaire
timestamps$timestampsActive/désactive les timestamps
dateFormat$dateFormatFormat de date

#[Scope] — définir une méthode comme scope local

Vous pouvez définir une méthode comme scope local Eloquent sans le préfixe 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');
    }
}
// Auparavant, il fallait le préfixe « scope »
// public function scopeActive(Builder $query): void

// Avec l'attribut, le nom de méthode devient directement le nom du scope
User::query()->active()->verified()->get();

#[UseFactory] — spécifier une classe factory

Spécifie via attribut la classe factory utilisée par le modèle.
use Illuminate\Database\Eloquent\Attributes\UseFactory;

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

Autres attributs Eloquent

AttributDescription
#[Fillable(...$attributes)]Colonnes autorisées en assignation en masse
#[Guarded(...$attributes)]Colonnes protégées de l’assignation en masse
#[Unguarded]Désactive la protection contre l’assignation en masse
#[Hidden(...$attributes)]Colonnes à exclure de la sérialisation
#[Visible(...$attributes)]Colonnes à inclure dans la sérialisation
#[Appends(...$attributes)]Accesseurs à ajouter lors de la sérialisation
#[Touches(...$relations)]Relations dont l’updated_at doit être mis à jour
#[WithoutTimestamps]Désactive les timestamps
#[WithoutIncrementing]Désactive l’auto-incrémentation de la clé primaire
#[DateFormat(format: '...')]Format de date
#[UsePolicy(policyClass: '...')]Classe de policy associée
#[UseResource(resourceClass: '...')]Classe de ressource API associée
#[UseResourceCollection(resourceCollectionClass: '...')]Classe de collection de ressource associée

Prise en charge des enums (ajoutée en v13.2.0)

Depuis la v13.2.0, #[Queue] et #[Connection] acceptent les enums. Vous pouvez ainsi désigner la queue et la connexion de façon typée avec des enums PHP, au lieu de littéraux de chaîne.
// Définition d'un enum de noms de queue
enum Queue: string
{
    case Default = 'default';
    case High = 'high';
    case Low = 'low';
    case Emails = 'emails';
    case Orders = 'orders';
}

// Définition d'un enum de connexions
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;

// Spécification typée via enums
#[Queue(Queue::Orders)]
#[Connection(Connection::Redis)]
class ProcessOrder implements ShouldQueue
{
    // ...
}
L’usage d’enums évite les fautes de frappe sur les noms de queues et de connexions, et l’auto-complétion de l’IDE est possible. Pratique pour centraliser dans toute l’application la gestion des noms de queue et de connexion.

Comparaison avec les propriétés de classe traditionnelles

Avantages des attributs

  • Déclaratif — un regard sur le début de la classe suffit à comprendre son comportement.
  • Typé — avec les enums, l’auto-complétion et la vérification de type fonctionnent.
  • Compatible avec l’héritage — les attributs du parent peuvent être remplacés dans les enfants.
  • Moins de code — pas besoin de déclarer des propriétés ou d’écraser des méthodes.

Inconvénients des attributs

  • Pas de valeurs dynamiques — les arguments d’attributs sont des constantes de compilation. Ni variables ni valeurs de config ne peuvent y être passées.
  • Nécessite une prise en main — l’équipe peut avoir besoin de temps pour s’habituer à la syntaxe d’attributs de PHP 8.

Lorsque des valeurs dynamiques sont nécessaires

Pour décider d’une valeur à l’exécution, utilisez la surcharge de méthode classique.
class ProcessOrder implements ShouldQueue
{
    // Backoff dynamique défini via une méthode
    public function backoff(): array
    {
        return [
            config('queue.backoff.first'),
            config('queue.backoff.second'),
        ];
    }
}
Les attributs sont analysés au moment de la compilation PHP. Vous ne pouvez pas y utiliser des valeurs runtime comme config() ou env(). Pour la configuration dynamique, continuez d’utiliser des propriétés ou des méthodes.

Fonctionnement interne

Laravel utilise en interne l’API Reflection pour lire les attributs. Lorsqu’un worker de queue dispatche un job, le trait ReadsQueueAttributes (inclus dans InteractsWithQueue) détecte les attributs par réflexion et positionne les valeurs sur les propriétés correspondantes.
// Représentation simplifiée de la lecture interne
$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;
}
Les attributs des modèles Eloquent sont lus de la même façon, au moment équivalent à Model::booted().

Étapes suivantes

Niveau intermédiaire : queues et jobs

Découvrez les bases du système de queue Laravel.

API PHP Reflection

Découvrez en détail l’API Reflection utilisée par Laravel pour lire les attributs.
Dernière modification le 13 juillet 2026