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

# Eloquent Observer 与模型事件

> 讲解 Eloquent 模型触发事件的机制，以及通过 Observer 类集中管理事件的方法。包含 `#[ObservedBy]` 属性等 Laravel 13 的最新特性。

## 什么是模型事件

Eloquent 模型会在生命周期的各时点自动触发事件。通过挂钩这些事件，可以在模型保存、删除等操作的前后插入自定义处理。

Eloquent 触发的事件如下。

| 事件              | 触发时机               |
| --------------- | ------------------ |
| `retrieved`     | 从 DB 取回模型时         |
| `creating`      | 保存新模型前             |
| `created`       | 保存新模型后             |
| `updating`      | 更新已有模型前            |
| `updated`       | 更新已有模型后            |
| `saving`        | 新增或更新保存前           |
| `saved`         | 新增或更新保存后           |
| `deleting`      | 删除模型前              |
| `deleted`       | 删除模型后              |
| `trashed`       | 软删除后               |
| `forceDeleting` | 物理删除前              |
| `forceDeleted`  | 物理删除后              |
| `restoring`     | 恢复软删除前             |
| `restored`      | 恢复软删除后             |
| `replicating`   | 调用 `replicate()` 时 |

以 `-ing` 结尾的事件在变更**持久化到 DB 之前**触发，以 `-ed` 结尾的则在**之后**触发。

<Warning>
  批量更新与批量删除（如 `User::where(...)->update(...)`）不会触发 `saving`、`saved`、`updating`、`updated`、`deleting`、`deleted` 事件，因为模型并未真正被实例化。
</Warning>

## 用闭包注册事件监听

若希望简单处理事件，可以在模型的 `booted` 方法内注册闭包。

```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) {
            // 用户创建后执行的处理
        });

        static::deleting(function (User $user) {
            // 用户删除前执行的处理
        });
    }
}
```

若要将处理放入队列异步执行，使用 `queueable` 辅助函数。

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

static::created(queueable(function (User $user) {
    // 会通过队列异步执行
}));
```

## `$dispatchesEvents` 属性

如果想与 Laravel 的事件系统联动，可以通过 `$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
{
    /**
     * 模型事件与事件类的映射
     *
     * @var array<string, string>
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}
```

映射到的事件类会在构造函数中接收模型实例。

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

namespace App\Events;

use App\Models\User;

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

## 创建 Observer 类

若要处理同一模型的多个事件，将闭包排成一列不如整合到 Observer 类中更加清爽。

<Steps>
  <Step title="用 Artisan 命令生成类">
    使用 `make:observer` 命令生成模板。通过 `--model` 选项指定模型时，会自动附上相应方法。

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

    将生成 `app/Observers/UserObserver.php`。
  </Step>

  <Step title="实现各事件方法">
    方法名对应事件名。参数中会传入模型实例。

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

    namespace App\Observers;

    use App\Models\User;

    class UserObserver
    {
        public function created(User $user): void
        {
            // 用户创建后的处理
        }

        public function updated(User $user): void
        {
            // 用户更新后的处理
        }

        public function deleted(User $user): void
        {
            // 用户删除后的处理
        }

        public function restored(User $user): void
        {
            // 软删除恢复后的处理
        }

        public function forceDeleted(User $user): void
        {
            // 物理删除后的处理
        }
    }
    ```
  </Step>

  <Step title="将 Observer 注册到 Model">
    注册方式有 2 种。Laravel 13 推荐使用 `#[ObservedBy]` 属性。

    **方式 1：`#[ObservedBy]` 属性（推荐）**

    只需在模型类上添加属性即可完成注册，无需修改 `AppServiceProvider`。

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

    要注册多个 Observer 时，可重复使用属性或以数组形式传入。

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

    **方式 2：在 `AppServiceProvider` 中注册**

    在 `AppServiceProvider` 的 `boot` 方法中调用 `observe`。

    ```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>
  `#[ObservedBy]` 属性位于 `Illuminate\Database\Eloquent\Attributes` 命名空间。它使用 PHP 8.0 以后的原生语法，在 Laravel 13 中被积极采用。
</Info>

## 数据库事务中的 Observer

若模型是在事务内创建或更新，有时你希望 Observer 在事务提交之后执行。实现 `ShouldHandleEventsAfterCommit` 接口即可获得该行为。

```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
    {
        // 事务提交后执行
    }
}
```

在事务外部执行时，会像往常一样立即执行。

## 暂时禁用事件

### 使用 `withoutEvents` 在特定处理中停止事件

在传给 `User::withoutEvents()` 的闭包内，任何模型事件都不会触发。

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

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

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

### 使用 `saveQuietly` 停止保存事件

想不触发事件地保存模型时使用 `saveQuietly`。

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

$user->name = 'Victoria Faith';

$user->saveQuietly();
```

对应的方法在删除、恢复、复制中也提供。

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

## 实战用例

### 自动清理缓存

模型更新或删除时，自动清除相关缓存。

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

### 记录审计日志

自动记录模型的变更历史。使用 `getDirty()` 可以获取变更前后的值。

```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>
  `updating` 事件在写入 DB **之前**触发，因此 `getDirty()` 可以取到即将变更的值。如果在 `updated` 事件之后调用，`getDirty()` 会为空。
</Tip>

### 关联模型的自动更新

订单完成时自动更新库存的示例。

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

## 下一步

<Card title="进阶：PHP 属性" icon="tag" href="/zh/advanced/php-attributes">
  一起学习包括 `#[ObservedBy]` 在内的 Laravel 13 PHP 属性。
</Card>


## Related topics

- [Eloquent 自定义类型转换](/zh/advanced/eloquent-casts.md)
- [查询构建器](/zh/query-builder.md)
- [Eloquent 入门](/zh/eloquent.md)
- [Laravel Telescope](/zh/telescope.md)
- [数据库填充（Seeding）](/zh/seeding.md)
