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

# Observateurs Eloquent et événements de modèle

> Découvrez le mécanisme des événements émis par les modèles Eloquent et comment les centraliser via des classes Observer. Inclut les nouveautés de Laravel 13 comme l'attribut `#[ObservedBy]`.

## Qu'est-ce qu'un événement de modèle

Les modèles Eloquent émettent automatiquement des événements aux différentes étapes de leur cycle de vie. En s'accrochant à ces événements, vous pouvez insérer des traitements avant et après les sauvegardes, suppressions, etc.

Les événements émis par Eloquent sont les suivants.

| Événement       | Moment de déclenchement                             |
| --------------- | --------------------------------------------------- |
| `retrieved`     | Lorsqu'un modèle est récupéré depuis la BDD         |
| `creating`      | Juste avant la sauvegarde d'un nouveau modèle       |
| `created`       | Juste après la sauvegarde d'un nouveau modèle       |
| `updating`      | Juste avant la mise à jour d'un modèle existant     |
| `updated`       | Juste après la mise à jour d'un modèle existant     |
| `saving`        | Juste avant la sauvegarde (création ou mise à jour) |
| `saved`         | Juste après la sauvegarde (création ou mise à jour) |
| `deleting`      | Juste avant la suppression d'un modèle              |
| `deleted`       | Juste après la suppression d'un modèle              |
| `trashed`       | Juste après un soft delete                          |
| `forceDeleting` | Juste avant une suppression physique                |
| `forceDeleted`  | Juste après une suppression physique                |
| `restoring`     | Juste avant la restauration d'un soft delete        |
| `restored`      | Juste après la restauration d'un soft delete        |
| `replicating`   | Lors de l'appel à `replicate()`                     |

Les événements terminant par `-ing` sont émis **avant** la persistance en BDD, ceux terminant par `-ed` sont émis **après**.

<Warning>
  Lors des mass updates ou mass deletes (par exemple `User::where(...)->update(...)`), les événements `saving`, `saved`, `updating`, `updated`, `deleting`, `deleted` ne sont pas émis. Les modèles ne sont en effet pas réellement récupérés.
</Warning>

## Écouteurs d'événements via closures

Pour traiter les événements simplement, vous pouvez enregistrer des closures dans la méthode `booted` du modèle.

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected static function booted(): void
    {
        static::created(function (User $user) {
            // Traitement à exécuter après la création de l'utilisateur
        });

        static::deleting(function (User $user) {
            // Traitement à exécuter avant la suppression de l'utilisateur
        });
    }
}
```

Pour exécuter le traitement en asynchrone via une queue, utilisez le helper `queueable`.

```php theme={null}
use function Illuminate\Events\queueable;

static::created(queueable(function (User $user) {
    // Exécuté en asynchrone via la queue
}));
```

## Propriété `$dispatchesEvents`

Pour interagir avec le système d'événements de Laravel, mappez les événements du modèle sur des classes d'événements personnalisées via la propriété `$dispatchesEvents`.

```php theme={null}
<?php

namespace App\Models;

use App\Events\UserDeleted;
use App\Events\UserSaved;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    /**
     * Mapping des événements du modèle vers les classes d'événements
     *
     * @var array<string, string>
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}
```

Les classes d'événements mappées reçoivent l'instance du modèle dans leur constructeur.

```php theme={null}
<?php

namespace App\Events;

use App\Models\User;

class UserSaved
{
    public function __construct(
        public readonly User $user,
    ) {}
}
```

## Création d'une classe Observer

Lorsqu'un même modèle doit traiter plusieurs événements, regrouper le tout dans une classe Observer est plus propre que d'aligner des closures.

<Steps>
  <Step title="Générer la classe avec Artisan">
    Générez le squelette avec la commande `make:observer`. L'option `--model` ajoute automatiquement les méthodes correspondant au modèle.

    ```bash theme={null}
    php artisan make:observer UserObserver --model=User
    ```

    `app/Observers/UserObserver.php` est généré.
  </Step>

  <Step title="Implémenter les méthodes de chaque événement">
    Le nom de la méthode correspond au nom de l'événement. L'argument reçoit l'instance du modèle.

    ```php theme={null}
    <?php

    namespace App\Observers;

    use App\Models\User;

    class UserObserver
    {
        public function created(User $user): void
        {
            // Traitement après la création de l'utilisateur
        }

        public function updated(User $user): void
        {
            // Traitement après la mise à jour de l'utilisateur
        }

        public function deleted(User $user): void
        {
            // Traitement après la suppression de l'utilisateur
        }

        public function restored(User $user): void
        {
            // Traitement après la restauration d'un soft delete
        }

        public function forceDeleted(User $user): void
        {
            // Traitement après une suppression physique
        }
    }
    ```
  </Step>

  <Step title="Enregistrer l'Observer sur le Model">
    Il existe deux méthodes d'enregistrement. Sous Laravel 13, l'utilisation de l'attribut `#[ObservedBy]` est recommandée.

    **Méthode 1 : attribut `#[ObservedBy]` (recommandée)**

    Il suffit d'appliquer l'attribut à la classe du modèle pour terminer l'enregistrement. Aucun changement dans `AppServiceProvider` n'est nécessaire.

    ```php theme={null}
    <?php

    namespace App\Models;

    use App\Observers\UserObserver;
    use Illuminate\Database\Eloquent\Attributes\ObservedBy;
    use Illuminate\Foundation\Auth\User as Authenticatable;

    #[ObservedBy(UserObserver::class)]
    class User extends Authenticatable
    {
        //
    }
    ```

    Pour enregistrer plusieurs Observers, répétez l'attribut ou passez un tableau.

    ```php theme={null}
    #[ObservedBy(UserObserver::class)]
    #[ObservedBy(AuditObserver::class)]
    class User extends Authenticatable
    {
        //
    }
    ```

    **Méthode 2 : enregistrement dans `AppServiceProvider`**

    Appelez `observe` dans la méthode `boot` de `AppServiceProvider`.

    ```php theme={null}
    <?php

    namespace App\Providers;

    use App\Models\User;
    use App\Observers\UserObserver;
    use Illuminate\Support\ServiceProvider;

    class AppServiceProvider extends ServiceProvider
    {
        public function boot(): void
        {
            User::observe(UserObserver::class);
        }
    }
    ```
  </Step>
</Steps>

<Info>
  L'attribut `#[ObservedBy]` se trouve dans le namespace `Illuminate\Database\Eloquent\Attributes`. C'est une syntaxe native PHP 8.0+ activement adoptée dans Laravel 13.
</Info>

## Observers dans les transactions BDD

Lorsqu'un modèle est créé ou mis à jour dans une transaction, il est parfois souhaitable d'exécuter l'Observer après le commit de la transaction. Implémentez l'interface `ShouldHandleEventsAfterCommit` pour obtenir ce comportement.

```php theme={null}
<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;

class UserObserver implements ShouldHandleEventsAfterCommit
{
    public function created(User $user): void
    {
        // Exécuté après le commit de la transaction
    }
}
```

En dehors d'une transaction, l'exécution est immédiate comme d'habitude.

## Désactiver temporairement les événements

### `withoutEvents` pour désactiver les événements sur un traitement précis

Dans la closure passée à `User::withoutEvents()`, aucun événement de modèle n'est émis.

```php theme={null}
use App\Models\User;

$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();

    return User::find(2);
});
```

### `saveQuietly` pour désactiver les événements à la sauvegarde

Pour sauvegarder sans émettre d'événement, utilisez `saveQuietly`.

```php theme={null}
$user = User::findOrFail(1);

$user->name = 'Victoria Faith';

$user->saveQuietly();
```

Des méthodes similaires existent pour la suppression, la restauration et la réplication.

```php theme={null}
$user->deleteQuietly();
$user->restoreQuietly();
```

## Cas d'usage pratiques

### Nettoyage automatique du cache

Lorsqu'un modèle est mis à jour ou supprimé, nettoyez automatiquement le cache associé.

```php theme={null}
<?php

namespace App\Observers;

use App\Models\Post;
use Illuminate\Support\Facades\Cache;

class PostObserver
{
    public function saved(Post $post): void
    {
        Cache::forget("post:{$post->id}");
        Cache::forget('posts:latest');
    }

    public function deleted(Post $post): void
    {
        Cache::forget("post:{$post->id}");
        Cache::forget('posts:latest');
    }
}
```

### Journal d'audit

Consignez automatiquement l'historique des modifications d'un modèle. `getDirty()` permet de récupérer les valeurs modifiées.

```php theme={null}
<?php

namespace App\Observers;

use App\Models\AuditLog;
use App\Models\User;

class UserObserver
{
    public function updating(User $user): void
    {
        AuditLog::create([
            'model_type' => User::class,
            'model_id'   => $user->id,
            'changes'    => $user->getDirty(),
            'user_id'    => auth()->id(),
        ]);
    }

    public function deleted(User $user): void
    {
        AuditLog::create([
            'model_type' => User::class,
            'model_id'   => $user->id,
            'changes'    => ['deleted' => true],
            'user_id'    => auth()->id(),
        ]);
    }
}
```

<Tip>
  L'événement `updating` est émis **avant** la sauvegarde en BDD, donc `getDirty()` retourne les valeurs sur le point d'être modifiées. Appelé après l'événement `updated`, `getDirty()` sera vide.
</Tip>

### Mise à jour automatique de modèles associés

Exemple de mise à jour automatique des stocks à la finalisation d'une commande.

```php theme={null}
<?php

namespace App\Observers;

use App\Models\Order;

class OrderObserver
{
    public function created(Order $order): void
    {
        foreach ($order->items as $item) {
            $item->product->decrement('stock', $item->quantity);
        }
    }

    public function deleted(Order $order): void
    {
        foreach ($order->items as $item) {
            $item->product->increment('stock', $item->quantity);
        }
    }
}
```

## Étapes suivantes

<Card title="Avancé : attributs PHP" icon="tag" href="/fr/advanced/php-attributes">
  Faites un tour d'ensemble des attributs PHP dans Laravel 13, y compris `#[ObservedBy]`.
</Card>


## Related topics

- [Casts personnalisés Eloquent](/fr/advanced/eloquent-casts.md)
- [Événements et listeners](/fr/events.md)
- [Introduction à Eloquent](/fr/eloquent.md)
- [Laravel Telescope](/fr/telescope.md)
- [Événements](/fr/packages/laravel-copilot-sdk/events.md)
