Laravel 模型事件指南

上次更新于 作者:

A guide to Laravel's model events image

模型事件是 Laravel 中一个非常方便的功能,它可以帮助你自动运行逻辑,当你的 Eloquent 模型执行某些操作时。但如果使用不当,有时会导致奇怪的副作用。

在这篇文章中,我们将介绍模型事件是什么以及如何在你的 Laravel 应用程序中使用它们。我们还将介绍如何测试模型事件以及使用它们时需要注意的一些问题。最后,我们将介绍一些替代模型事件的方法,你可能希望考虑使用它们。

什么是事件和监听器?

你可能已经听说过“事件”和“监听器”。但如果你还没有,这里简要介绍一下它们是什么

事件

这些是在你的应用程序中发生的你想要处理的事情——例如,用户在你的网站上注册、用户登录等等。

通常,在 Laravel 中,事件是 PHP 类。除了框架或第三方包提供的事件外,它们通常保存在 app/Events 目录中。

以下是一个简单的事件类示例,你可能希望在用户在你的网站上注册时派发它

declare(strict_types=1);
 
namespace App\Events;
 
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
 
final class UserRegistered
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
 
public function __construct(public User $user)
{
//
}
}

在上面的基本示例中,我们有一个 App\Events\UserRegistered 事件类,它在构造函数中接受一个 User 模型实例。这个事件类是一个简单的數據容器,它保存着已注册的用户实例。

派发时,该事件将触发任何监听它的监听器。

以下是一个简单示例,说明如何在用户注册时派发该事件

use App\Events\UserRegistered;
use App\Models\User;
 
$user = User::create([
'name' => 'Eric Barnes',
'email' => '[email protected]',
]);
 
UserRegistered::dispatch($user);

在上面的示例中,我们正在创建一个新用户,然后用用户实例派发 App\Events\UserRegistered 事件。假设监听器已正确注册,这将触发任何监听 App\Events\UserRegistered 事件的监听器。

监听器

监听器是你想要在特定事件发生时运行的代码块。

例如,坚持我们的用户注册示例,你可能希望在用户注册时向用户发送欢迎邮件。你可以创建一个监听器,监听 App\Events\UserRegistered 事件并发送欢迎邮件。

在 Laravel 中,监听器通常(但并不总是——我们将在后面介绍)是 app/Listeners 目录中的类。

一个监听器示例,它在用户注册时向用户发送欢迎邮件,可能如下所示

declare(strict_types=1);
 
namespace App\Listeners;
 
use App\Events\UserRegistered;
use App\Notifications\WelcomeNotification;
use Illuminate\Support\Facades\Mail;
 
final readonly class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
$event->user->notify(new WelcomeNotification());
}
}

正如我们在上面的代码示例中看到的,App\Listeners\SendWelcomeEmail 监听器类有一个 handle 方法,它接受一个 App\Events\UserRegistered 事件实例。该方法负责向用户发送欢迎邮件。

有关事件和监听器的更深入解释,你可能需要查看官方文档:https://laravel.net.cn/docs/11.x/events

什么是模型事件?

在你的 Laravel 应用程序中,你通常需要在发生特定操作时手动派发事件。正如我们在上面的示例中看到的,我们可以使用以下代码派发事件

UserRegistered::dispatch($user);

但是,在 Laravel 中使用 Eloquent 模型时,有一些事件会自动为我们派发,因此我们不需要手动派发它们。我们只需要为它们定义监听器,如果我们想在它们发生时执行操作。

以下列表显示了 Eloquent 模型自动派发的事件以及它们的触发器

  • retrieved - 从数据库中检索。
  • creating - 正在创建模型。
  • created - 模型已创建。
  • updating - 正在更新模型。
  • updated - 模型已更新。
  • saving - 正在创建或更新模型。
  • saved - 模型已创建或更新。
  • deleting - 正在删除模型。
  • deleted - 模型已删除。
  • trashed - 模型已被软删除。
  • forceDeleting - 正在强制删除模型。
  • forceDeleted - 模型已被强制删除
  • restoring - 模型正在从软删除中恢复。
  • restored - 模型已从软删除中恢复。
  • replicating - 模型正在被复制。

在上面的列表中,你可能注意到一些事件名称很相似;例如,creatingcreated。以 ing 结尾的事件在操作发生之前执行,并且更改已持久保存到数据库中。而以 ed 结尾的事件在操作发生之后执行,并且更改已持久保存到数据库中。

让我们看看如何在我们的 Laravel 应用程序中使用这些模型事件。

使用 dispatchesEvents 监听模型事件

监听模型事件的一种方法是在你的模型上定义一个 dispatchesEvents 属性。

此属性允许你将 Eloquent 模型事件映射到事件发生时应该派发的事件类。这意味着你可以在需要时像处理其他事件一样定义你的监听器。

为了提供更多上下文,让我们看一个示例。

假设我们正在构建一个具有两个模型的博客应用程序:App\Models\PostApp\Models\Author。我们假设这两个模型都支持软删除。当我们保存一个新的 App\Models\Post 时,我们希望根据内容的长度计算文章的阅读时间。当我们软删除作者时,我们希望软删除作者的所有文章。

设置模型

我们可能有一个 App\Models\Author 模型,如下所示

declare(strict_types=1);
 
namespace App\Models;
 
use App\Events\AuthorDeleted;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
 
final class Author extends Model
{
use HasFactory;
use SoftDeletes;
 
protected $dispatchesEvents = [
'deleted' => AuthorDeleted::class,
];
 
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}

在上面的模型中,我们有

  • 添加了一个 dispatchesEvents 属性,它将 deleted 模型事件映射到 App\Events\AuthorDeleted 事件类。这意味着当模型被删除时,将派发一个新的 App\Events\AuthorDeleted 事件。我们将在稍后创建此事件类。
  • 定义了一个 posts 关系。
  • 通过使用 Illuminate\Database\Eloquent\SoftDeletes 特性,在模型上启用了软删除。

现在让我们创建我们的 App\Models\Post 模型

declare(strict_types=1);
 
namespace App\Models;
 
use App\Events\PostSaving;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
 
final class Post extends Model
{
use HasFactory;
use SoftDeletes;
 
protected $dispatchesEvents = [
'saving' => PostSaving::class,
];
 
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
}

在上面的 App\Models\Post 模型中,我们有

  • 添加了一个 dispatchesEvents 属性,它将 saving 模型事件映射到 App\Events\PostSaving 事件类。这意味着当模型被创建或更新时,将派发一个新的 App\Events\PostSaving 事件。我们将在稍后创建此事件类。
  • 定义了一个 author 关系。
  • 通过使用 Illuminate\Database\Eloquent\SoftDeletes 特性,在模型上启用了软删除。

我们的模型现在已准备就绪,所以让我们创建我们的 App\Events\AuthorDeletedApp\Events\PostSaving 事件类。

创建事件类

我们将创建一个 App\Events\PostSaving 事件类,它将在保存新文章时派发

declare(strict_types=1);
 
namespace App\Events;
 
use App\Models\Post;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
 
final class PostSaving
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
 
public function __construct(public Post $post)
{
//
}
}

在上面的代码中,我们可以看到 App\Events\PostSaving 事件类,它在构造函数中接受一个 App\Models\Post 模型实例。这个事件类是一个简单的數據容器,它保存着正在保存的文章实例。

类似地,我们可以创建一个 App\Events\AuthorDeleted 事件类,它将在删除作者时派发

declare(strict_types=1);
 
namespace App\Events;
 
use App\Models\Author;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
 
final class AuthorDeleted
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
 
public function __construct(public Author $author)
{
//
}
}

在上面的 App\Events\AuthorDeleted 类中,我们可以看到构造函数接受一个 App\Models\Author 模型实例。

现在我们可以继续创建我们的监听器。

创建监听器

首先,让我们创建一个监听器,它可以用来计算帖子的预计阅读时间。

我们将创建一个新的 App\Listeners\CalculateReadTime 监听器类

declare(strict_types=1);
 
namespace App\Listeners;
 
use App\Events\PostSaving;
use Illuminate\Support\Str;
 
final readonly class CalculateReadTime
{
public function handle(PostSaving $event): void
{
$event->post->read_time_in_seconds = (int) ceil(
(Str::wordCount($event->post->content) / 265) * 60
);
}
}

从上面的代码可以看到,我们有一个 handle 方法。当 App\Events\PostSaving 事件被派发时,这个方法会被自动调用。它接收一个 App\Events\PostSaving 事件类实例,该实例包含正在保存的帖子。

handle 方法中,我们使用了一个简单的公式来计算帖子的阅读时间。在这种情况下,我们假设平均阅读速度是每分钟 265 个字。我们以秒为单位计算阅读时间,然后将 read_time_in_seconds 属性设置到帖子模型上。

由于这个监听器将在 saving 模型事件被触发时被调用,这意味着每次创建或更新帖子并在将其持久化到数据库之前,read_time_in_seconds 属性都会被计算。

我们还可以创建一个监听器,当作者被软删除时,它会软删除所有相关的帖子。

我们可以创建一个新的 App\Listeners\SoftDeleteAuthorRelationships 监听器类

declare(strict_types=1);
 
namespace App\Listeners;
 
use App\Events\AuthorDeleted;
 
final readonly class SoftDeleteAuthorRelationships
{
public function handle(AuthorDeleted $event): void
{
$event->author->posts()->delete();
 
// Soft delete any other relationships here...
}
}

在上面的监听器中,handle 方法接收一个 App\Events\AuthorDeleted 事件类实例。这个事件类包含正在被删除的作者。然后,我们使用 posts 关系上的 delete 方法来删除作者的帖子。

因此,每当 App\Models\Author 模型被软删除时,所有作者的帖子也会被软删除。

作为旁注,值得注意的是,你可能希望使用更健壮、可重用的解决方案来实现这一点。但是为了本文的目的,我们将其保持简单。

使用闭包监听模型事件

你可以使用的另一种方法是在模型本身定义监听器作为闭包。

让我们以之前删除作者时软删除帖子的例子为例。我们可以更新我们的 App\Models\Author 模型,让它包含一个监听 deleted 模型事件的闭包

declare(strict_types=1);
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
 
final class Author extends Model
{
use HasFactory;
use SoftDeletes;
 
protected static function booted(): void
{
self::deleted(static function (Author $author): void {
$author->posts()->delete();
});
}
 
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}

从上面的模型可以看到,我们在模型的 booted 方法中定义了我们的监听器。我们想要监听 deleted 模型事件,所以我们使用了 self::deleted。同样,如果我们想要为 created 模型事件创建一个监听器,我们可以使用 self::created,等等。self::deleted 方法接受一个闭包,该闭包接收正在被删除的 App\Models\Author。当模型被删除时,这个闭包将被执行,从而删除所有作者的帖子。

我非常喜欢这种方法,因为它适用于非常简单的监听器。它将逻辑保留在模型类内部,这样开发人员更容易看到它。有时,将逻辑提取到单独的监听器类中会使代码更难理解和跟踪,这可能会使代码逻辑难以跟踪,尤其是在你不熟悉代码库的情况下。但是,如果这些闭包内部的代码变得更复杂,可能值得将逻辑提取到单独的监听器类中。

一个需要了解的实用技巧是,你还可以使用 Illuminate\Events\queueable 函数使闭包可排队。这意味着监听器的代码将被推送到队列中,在后台运行而不是在同一个请求生命周期内运行。我们可以像这样更新我们的监听器,使其可排队

declare(strict_types=1);
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use function Illuminate\Events\queueable;
 
final class Author extends Model
{
// ...
 
protected static function booted(): void
{
self::deleted(queueable(static function (Author $author): void {
$author->posts()->delete();
}));
}
 
// ...
}

从上面的例子可以看到,我们使用 Illuminate\Events\queueable 函数将闭包包装起来。

使用观察者监听模型事件

你可以采取的另一种监听模型事件的方法是使用模型观察者。模型观察者允许你在一个类中定义所有模型的监听器。

通常,它们是位于 app/Observers 目录中的类,并且它们具有与你想要监听的模型事件相对应的方法。例如,如果你想要监听 deleted 模型事件,你将在你的观察者类中定义一个 deleted 方法。如果你想要监听 created 模型事件,你将在你的观察者类中定义一个 created 方法,等等。

让我们看看如何为我们的 App\Models\Author 模型创建一个模型观察者,该观察者监听 deleted 模型事件

declare(strict_types=1);
 
namespace App\Observers;
 
use App\Models\Author;
 
final readonly class AuthorObserver
{
public function deleted(Author $author): void
{
$author->posts()->delete();
}
}

从上面的代码可以看到,我们创建了一个观察者,它有一个 deleted 方法。这个方法接受正在被删除的 App\Models\Author 模型实例。然后,我们使用 posts 关系上的 delete 方法来删除作者的帖子。

假设,作为示例,我们还想要为 createdupdated 模型事件定义监听器。我们可以像这样更新我们的观察者

declare(strict_types=1);
 
namespace App\Observers;
 
use App\Models\Author;
 
final readonly class AuthorObserver
{
public function created(Author $author): void
{
// Logic to run when the author is created...
}
 
public function updated(Author $author): void
{
// Logic to run when the author is updated...
}
 
public function deleted(Author $author): void
{
$author->posts()->delete();
}
}

为了让 App\Observers\AuthorObserver 方法运行,我们需要指示 Laravel 使用它。为此,我们可以使用 #[Illuminate\Database\Eloquent\Attributes\ObservedBy] 属性。这允许我们将观察者与模型关联起来,类似于我们使用 #[ScopedBy] 属性注册全局查询范围的方式(如在 了解如何在 Laravel 中掌握查询范围 中所示)。我们可以像这样更新我们的 App\Models\Author 模型来使用观察者

declare(strict_types=1);
 
namespace App\Models;
 
use App\Observers\AuthorObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model;
 
#[ObservedBy(AuthorObserver::class)]
final class Author extends Model
{
// ...
}

我非常喜欢这种定义监听器逻辑的方式,因为它在打开模型类时可以立即清楚地看到它注册了一个观察者。因此,尽管逻辑仍然“隐藏”在单独的文件中,但我们可以意识到我们至少有一个模型事件的监听器。

测试你的模型事件

无论你使用哪种模型事件方法,你都可能想编写一些测试来确保你的逻辑按预期运行。

让我们看看如何测试我们在上面的例子中创建的模型事件。

首先,我们将编写一个测试,确保当作者被软删除时,作者的帖子也被软删除。该测试可能类似于

declare(strict_types=1);
 
namespace Tests\Feature\Models;
 
use App\Models\Author;
use App\Models\Post;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
 
final class AuthorTest extends TestCase
{
use LazilyRefreshDatabase;
 
#[Test]
public function author_can_be_soft_deleted(): void
{
// Create our author and post.
$author = Author::factory()->create();
 
$post = Post::factory()->for($author)->create();
 
// Delete the author.
$author->delete();
 
// Assert the author and their associated post
// is soft-deleted.
$this->assertSoftDeleted($author);
$this->assertSoftDeleted($post);
}
}

在上面的测试中,我们创建了一个新的作者和该作者的帖子。然后我们软删除作者,并断言作者和帖子都被软删除了。

这是一个非常简单但有效的测试,我们可以用它来确保我们的逻辑按预期工作。这种测试的妙处在于,它应该适用于本文中讨论的所有方法。因此,如果你在本文中讨论的任何方法之间切换,你的测试应该仍然通过。

同样,我们还可以编写一些测试来确保当帖子被创建或更新时,会计算帖子的阅读时间。测试可能类似于

declare(strict_types=1);
 
namespace Tests\Feature\Models;
 
use App\Models\Author;
use App\Models\Post;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
 
final class PostTest extends TestCase
{
use LazilyRefreshDatabase;
 
#[Test]
public function read_time_is_calculated_when_storing_post(): void
{
$post = Post::factory()
->for(Author::factory())
->create([
'content' => 'This is a post with some content.'
]);
 
$this->assertSame(2, $post->read_time_in_seconds);
}
 
#[Test]
public function read_time_is_calculated_when_updating_post(): void
{
$post = Post::factory()
->for(Author::factory())
->create();
 
$post->content = 'This is a post with some content. ...';
$post->save();
 
$this->assertSame(8, $post->read_time_in_seconds);
}
}

我们在上面有两个测试

  • 第一个测试确保在帖子被创建时,会计算帖子的阅读时间。
  • 第二个测试确保在帖子被更新时,会计算帖子的阅读时间。

使用模型事件时的注意事项

虽然模型事件非常方便,但在使用它们时需要注意一些注意事项。

模型事件只从 Eloquent 模型中派发。这意味着,如果你使用 Illuminate\Support\Facades\DB 门面与模型的底层数据库数据进行交互,它的事件将不会被派发。

例如,让我们来看一个简单的例子,我们使用 Illuminate\Support\Facades\DB 门面来删除作者

use Illuminate\Support\Facades\DB;
 
DB::table('authors')
->where('id', $author->id)
->delete();

运行上面的代码将按预期从数据库中删除作者。但 deletingdeleted 模型事件将不会被派发。因此,如果你在删除作者时为这些模型事件定义了任何监听器,它们将不会被运行。

同样,如果你使用 Eloquent 批量更新或删除模型,savedupdateddeletingdeleted 模型事件将不会被派发给受影响的模型。这是因为事件是从模型本身派发的。但在批量更新和删除时,模型实际上并没有从数据库中检索出来,所以事件不会被派发。

例如,假设我们使用以下代码删除作者

use App\Models\Author;
 
Author::query()->whereKey($author->id)->delete();

由于 delete 方法直接在查询构建器上调用,因此 deletingdeleted 模型事件将不会被派发给该作者。

考虑替代方法

我喜欢在我的项目中使用模型事件。它们是解耦代码的好方法,也让我能够在对影响模型的代码没有太多控制权的情况下自动运行逻辑。例如,如果我在 Laravel Nova 中删除一个作者,我仍然可以运行一些逻辑来删除作者。

但是,重要的是要知道何时考虑使用不同的方法。

为了解释这一点,让我们来看一个我们可能想要避免使用模型事件的基本例子。扩展我们之前简单的博客应用程序示例,让我们想象一下,我们想要在每次创建新帖子时运行以下操作

  • 计算帖子的阅读时间。
  • 向 X/Twitter 发出 API 调用来分享帖子。
  • 向平台上的每个订阅者发送通知。

因此,我们可能会创建三个单独的监听器(每个任务一个),它们在每次创建 App\Models\Post 的新实例时都会运行。

但现在让我们回过头来看看我们之前的一个测试

declare(strict_types=1);
 
namespace Tests\Feature\Models;
 
use App\Models\Author;
use App\Models\Post;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
 
final class AuthorTest extends TestCase
{
use LazilyRefreshDatabase;
 
#[Test]
public function author_can_be_soft_deleted(): void
{
$author = Author::factory()->create();
 
$post = Post::factory()->for($author)->create();
 
$author->delete();
 
$this->assertSoftDeleted($author);
$this->assertSoftDeleted($post);
}
}

如果我们运行上面的测试,当 App\Models\Post 模型通过其工厂创建时,它也会触发这三个操作。当然,计算阅读时间是一个次要的任务,所以它并不重要。但我们不想在测试期间尝试进行 API 调用或发送通知。这些都是意外的副作用。如果编写测试的开发人员没有意识到这些副作用,可能会更难跟踪为什么这些操作会发生。

我们还希望避免在监听器中编写任何特定于测试的逻辑,这会阻止这些操作在测试期间运行。这会使应用程序代码更复杂,更难维护。

这是你可能想要考虑更明确的方法而不是依赖于自动模型事件的一种情况。

一种方法可以是将你的 App\Models\Post 创建代码提取到服务或操作类中。例如,一个简单的服务类可能类似于

declare(strict_types=1);
 
namespace App\Services;
 
use App\DataTransferObjects\PostData;
use App\Models\Post;
use Illuminate\Support\Str;
 
final readonly class PostService
{
public function createPost(PostData $postData): void
{
$post = Post::create([
'title' => $postData->title,
'content' => $postData->content,
'author_id' => $postData->authorId,
'read_time_in_seconds' => $this->calculateReadTime($postData->content),
]);
 
$this->sendPostCreatedNotification($post);
$this->publishToTwitter($post);
}
 
public function updatePost(Post $post, PostData $postData): void
{
$post->update([
'title' => $postData->title,
'content' => $postData->content,
'read_time_in_seconds' => $this->calculateReadTime($postData->content),
]);
}
 
private function calculateReadTime(string $content): int
{
return (int) ceil(
(Str::wordCount($content) / 265) * 60
);
}
 
private function sendPostCreatedNotification(Post $post): void
{
// Send a notification to all subscribers...
}
 
private function publishToTwitter(Post $post): void
{
// Make an API call to Twitter...
}
}

在上面的类中,我们手动调用了计算阅读时间、发送通知和发布到 Twitter 的代码。这意味着我们对这些操作何时运行有更多控制权。我们还可以在测试中轻松模拟这些方法,以防止它们运行。我们仍然可以将这些操作排队(在这种情况下很可能需要这样做)。

因此,我们可以删除使用模型事件和监听器来执行这些操作。这意味着我们可以在应用程序代码中使用新的 App\Services\PostService 类,并在测试代码中安全地使用模型工厂。

这样做的好处是,它还可以使代码更易于理解。正如我之前简要提到的,使用事件和监听器的一个常见批评是它可能将业务逻辑隐藏在意外的地方。因此,如果新开发者加入团队,他们可能不知道某些操作在哪里或为什么发生,如果它们是由模型事件触发的。

但是,如果您仍然想使用事件和监听器来处理这种逻辑,您可以考虑使用更明确的方法。例如,您可以从服务类中调度一个事件,该事件会触发监听器。这样,您仍然可以使用事件和监听器的解耦优势,但可以更好地控制事件调度的时间。

例如,我们可以更新上面的 App\Services\PostService 示例中的 createPost 方法来调度一个事件

declare(strict_types=1);
 
namespace App\Services;
 
use App\DataTransferObjects\PostData;
use App\Events\PostCreated;
use App\Models\Post;
use Illuminate\Support\Str;
 
final readonly class PostService
{
public function createPost(PostData $postData): void
{
$post = Post::create([
'title' => $postData->title,
'content' => $postData->content,
'author_id' => $postData->authorId,
'read_time_in_seconds' => $this->calculateReadTime($postData->content),
]);
 
PostCreated::dispatch($post);
}
 
// ...
 
}

通过使用上述方法,我们仍然可以拥有独立的监听器来向 Twitter 发送 API 请求和发送通知。但我们可以更好地控制这些操作何时运行,这样在使用模型工厂时,它们就不会在测试中运行。

在决定使用这些方法中的哪一种时,没有绝对的规则。一切都要看什么最适合您、您的团队以及您正在构建的功能。但是,我倾向于遵循以下经验法则

  • 如果监听器中的操作只是对模型进行一些微小的更改,请考虑使用模型事件。例如:生成 slug、计算阅读时间等。
  • 如果操作将影响另一个模型(无论是自动创建、更新还是删除),那么请更明确,不要使用模型事件。
  • 如果操作将与外部进程交互(API 调用、文件处理、触发通知、排队作业),那么请更明确,不要使用模型事件。

使用模型事件的优缺点

为了快速总结我们在本文中涵盖的内容,这里列出了使用模型事件的优缺点

优点

  • 鼓励您解耦代码。
  • 允许您自动触发操作,无论模型是在哪里创建/更新/删除的。例如,如果模型是在 Laravel Nova 中创建的,您可以触发业务逻辑。
  • 您无需每次创建/更新/删除模型时都记住调度事件。

缺点

  • 可能会导致意外的副作用。您可能希望创建/更新/删除模型而不会触发某些监听器,但这可能会导致意外的行为。这在编写测试时尤其成问题。
  • 可能会将业务逻辑隐藏在难以追踪的意外地方。这会使您的代码流程更难理解。

结论

希望本文能为您提供有关模型事件是什么以及使用它们的各种方法的概述。它还应该向您展示如何测试模型事件代码,以及在使用它们时需要注意的一些问题。

您现在应该有足够的信心在 Laravel 应用程序中使用模型事件。

Ashley Allen photo

我是一名自由 Laravel 网页开发者,热爱为开源项目做出贡献、构建激动人心的系统以及帮助他人学习网页开发。

归档于
Cube

Laravel 新闻

加入 40,000 多名其他开发者,绝不错过新的提示、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和对本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

没有妥协

Joel 和 Aaron,来自“没有妥协”播客的两名经验丰富的开发者,现在可以为您的 Laravel 项目提供服务。 ⬧ 固定价格 $7500/月。 ⬧ 没有冗长的销售流程。 ⬧ 没有合同。 ⬧ 100% 退款保证。

没有妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的网页应用程序取得成功。

Kirschbaum
Shift logo

Shift

正在运行旧版本的 Laravel?即时、自动的 Laravel 升级和代码现代化,让您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

使用经验丰富的 Laravel 开发者为您的项目注入活力,只需每月 $2500。获得 160 小时的专业知识和 15 天无风险试用。立即预约电话!

Bacancy
Lucky Media logo

Lucky Media

立即幸运 - Laravel 开发的理想选择,拥有超过十年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 的电子商务。一个开源包,将现代无头电子商务功能的强大功能带入 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动套件

SaaSykit 是一款 Laravel SaaS 启动套件,包含运行现代 SaaS 所需的所有功能。支付、美观的结账、管理面板、用户仪表板、身份验证、现成的组件、统计数据、博客、文档等。

SaaSykit: Laravel SaaS 启动套件
Rector logo

Rector

您无缝升级 Laravel、降低成本和加速创新的合作伙伴,帮助公司取得成功

Rector
MongoDB logo

MongoDB

使用 MongoDB 和 Laravel 的强大集成增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务性、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。了解 MongoDB 的灵活的现代数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的无依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

在 Laravel 应用程序中添加 Swagger UI

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel Prompts 构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为您的 Laravel 应用程序添加评论

阅读文章