使用 Eloquent 写入数据库

上次更新于 作者

Writing to the Database with Eloquent image

Laravel Eloquent 是当今现代框架中最强大、最令人惊叹的功能之一。从数据转换到值对象和类,使用可填充字段、事务、作用域、全局作用域和关系保护数据库。Eloquent 使你能够在需要处理数据库的任何情况下取得成功。

开始使用 Eloquent 有时会让人感到害怕,因为它可以做很多事情,你永远不知道从哪里开始。在本教程中,我将重点关注我认为任何应用程序中必不可少的方面之一 - 写入数据库。

你可以在任何应用程序区域写入数据库:控制器、作业、中间件、Artisan 命令。那么,处理数据库写入的最佳方法是什么呢?

简单的 Eloquent 模型

让我们从一个没有关系的简单 Eloquent 模型开始。

final class Post extends Model
{
protected $fillable = [
'title',
'slug',
'content',
'published',
];
 
protected $casts = [
'published' => 'boolean',
];
}

我们有一个 Post 模型,它代表一个博客文章;它有一个标题、slug、内容和一个布尔标志,表示它是否已发布。在这个例子中,让我们假设已发布属性在数据库中默认为 true。现在,首先,我们告诉 Eloquent 我们希望能够填写 titleslugcontentpublished 属性或列。因此,如果我们传递任何未在 fillable 数组中注册的内容,则会抛出异常 - 从潜在问题中保护我们的应用程序。

现在我们知道了哪些字段可以填写,我们可以看看如何将数据写入数据库,无论是创建、更新还是删除。如果你的模型继承了 SoftDeletes 特性,那么删除记录是一个写入操作 - 但在这个例子中,我会保持简单;删除就是删除。

你最有可能看到的,特别是在文档中,是以下内容

Post::create($request->only('title', 'slug', 'content'));

这是我所能做的标准 Eloquent,你有一个模型,你调用静态方法来创建一个新实例 - 传递一个来自请求的特定数组。这种方法有很多好处;它干净简单,每个人都能理解。我可能是一个非常固执己见的开发者。但是,我仍然会使用这种方法,尤其是在原型设计阶段,重点是测试想法而不是构建长期的东西。

我们可以通过在模型上启动一个新的 Eloquent 查询构建器实例,然后再请求创建一个新实例,从而更进一步。这将如下所示

Post::query()->create($request->only('title', 'slug', 'content'));

如你所见,它仍然非常简单,并且正成为在 Laravel 中启动查询的更标准化的方法。这种方法最大的好处之一是,query 之后的所有内容都遵循最近引入的 Query Builder Contract。由于 Laravel 的工作原理,你的 IDE 不会很好地理解静态调用 - 因为它是一个使用 __callStatic 的静态代理方法,而不是一个实际的静态方法。幸运的是,对于 query 方法来说情况并非如此,它是一个你正在扩展的 Eloquent 模型上的静态方法。

还有“较旧”的构建模型以保存到数据库的方法。但是,我很少看到它经常被使用。不过,出于清楚起见,我会提到它

$post = new Post();
$post->title = $request->get('title');
$post->slug = $request->get('slug');
$post->content = $request->get('content');
$post->save();

在这里,我们将以编程方式构建模型,将值分配给属性,然后将其保存到数据库。这样做有点繁琐,而且总是感觉为了实现目标付出了太多努力。但是,如果这是你首选的方法,那么这仍然是创建新模型的一种可接受的方法。

到目前为止,我们已经研究了三种在数据库中创建新数据的不同方法。我们可以使用类似的方法更新数据库中的数据,静态调用 update 或使用查询构建契约 query()->where('column', 'value')->update(),最后以编程方式设置属性,然后 save。我不会在这里重复自己,因为它与上面的内容几乎相同。

如果我们不确定记录是否已存在,该怎么办?例如,我们想要创建一个新的帖子或更新现有的帖子。我们将有一个列,我们想根据它来检查唯一性 - 然后我们传递一个我们要创建或更新的值数组,具体取决于它是否存在。

Post::query()->updateOrCreate(
attributes: ['slug' => $request->get('slug'),
values: [
'title' => $request->get('title'),
'content' => $request->get('content'),
],
);

如果你不确定记录是否存在,这将有一些巨大的好处,我最近在想要“确保”记录无论如何都存在时自己实现了它。例如,对于 OAuth 2.0 社交登录,你可以在身份验证用户之前接受来自提供者的信息并更新或创建新记录。

我们可以更进一步吗?好处是什么?你可以使用类似于 Repository 模式这样的模式来基本上“代理”你将发送给 Eloquent 的调用,通过一个不同的类。这样做有一些好处,或者至少在 Eloquent 成为今天的样子之前是有好处的。让我们看一个例子

class PostRepository
{
private Model $model;
 
public function __construct()
{
$this->model = Post::query();
}
 
public function create(array $attributes): Model
{
return $this->model->create(
attributes: $attributes,
);
}
}

如果我们使用 DB Facade 或纯 PDO,那么 Repository 模式可能会在保持一致性方面给我们带来很多好处。让我们继续。

在某个时候,人们认为从 Repository 类转移到 Service 类是一个好主意。但是,这是同样的事情……我们不要谈论这个。

因此,我们想要一种处理与 Eloquent 交互的方式,这种方式不是那么“内联”或程序化。几年前,我采用了一种现在被称为“动作”的方法。它类似于 Repository 模式。但是,每次与 Eloquent 的交互都是它自己的类,而不是一个类中的一个方法。

让我们看看这个例子,我们为每个交互都定义了一个专门的类,称为“动作”

final class CreateNewPostAction implements CreateNewPostContract
{
public function handle(array $attributes): Model|Post
{
return Post::query()
->create(
attributes: $attributes,
);
}
}

我们的类实现了一个契约,以将其很好地绑定到容器,使我们能够将其注入构造函数并根据需要使用我们的数据调用 handle 方法。这越来越受欢迎,许多人(以及软件包)已经开始采用这种方法,因为你创建的实用程序类只做一件事 - 并且可以轻松地为它们创建测试替身。另一个好处是,我们使用了一个接口;如果我们决定放弃 Eloquent(不知道你为什么要这样做),我们可以快速更改代码以反映这一点,而无需查找任何东西。

同样,这是一种非常好的方法 - 并且原则上没有任何真正的缺点。我说过我是一个非常挑剔的开发者,对吧?嗯……

我在使用“动作”这么长时间后遇到的最大问题是,我们将所有写入、更新和删除集成都放在一个地方。对于我来说,动作的划分还不够细致。如果我仔细想想,我们想要实现两件事 - 我们想要写入,我们想要读取。这部分反映了另一种设计模式,称为 CQRS(命令查询责任分离),这是我借鉴了一些东西。在 CQRS 中,通常你会使用命令总线和查询总线来读取和写入数据,通常会发出事件以便使用事件溯源来存储。但是,有时这比你需要的要多得多。别误会我的意思,这种方法肯定有它的时间和地点,但你应该只有在需要的时候才使用它 - 否则,你会从最小的部分过度设计你的解决方案。

因此,我将我的写入操作分为“命令”和我的读取操作分为“查询”,以便我的交互分离并专注。让我们看看一个命令

final class CreateNewPost implements CreateNewPostContract
{
public function handle(array $attributes): Model|Post
{
return Post::query()
->create(
attributes: $attributes,
);
}
}

看看吧,除了类名,它跟 action 一样。这是故意的。Action 是写入数据库的绝佳方式。我发现它们容易过早变得拥挤。

我们还能如何改进?引入一个 Domain Transfer Object 会是一个不错的开始,因为它提供了类型安全性、上下文和一致性。

final class CreateNewPost implements CreateNewPostContract
{
public function handle(CreatePostRequest $post): Model|Post
{
return Post::query()
->create(
attributes: $post->toArray(),
);
}
}

所以我们现在在一个数组中引入了类型安全性,而之前我们依赖于数组,并希望一切顺利。是的,我们可以尽情地验证——但对象具有更好的一致性。

我们还能进一步改进吗?总有改进的空间,但我们需要吗?目前的方法是可靠的、类型安全的、易于记忆的。但是,如果数据库表在我们可以写入之前被锁定,或者网络连接出现问题,比如 Cloudflare 在错误的时间宕机,我们该怎么办呢?

数据库事务将在这里拯救我们。它们的使用频率可能没有它们应该有的那么高,但它们是一个强大的工具,你应该尽快考虑采用。

final class CreateNewPost implements CreateNewPostContract
{
public function handle(CreatePostRequest $post): Model|Post
{
return DB::transaction(
fn() => Post::query()->create(
attributes: $post->toArray(),
)
);
}
}

我们最终做到了!如果我在 PR 或代码审查中看到这样的代码,我会欣喜若狂。但是,不要觉得你必须这样写代码。记住,如果对你有用,直接内联静态 create 也是完全可以的!重要的是做你习惯的,做能让你有效率的事情——而不是做社区中其他人说你应该做的事情。

采用我们刚刚看到的方法,我们可以用相同的方式来处理从数据库中读取数据。分解问题,找出步骤以及可以改进的地方,但要始终质疑你是否做得过火了。如果感觉自然,那可能是个好兆头。

你是如何处理写入数据库的?你会走多远,什么时候是太远了?在 Twitter 上分享你的想法吧!

Steve McDougall photo

技术作家,在 Laravel News,开发者倡导者,在 Treblle。API 专家,资深 PHP/Laravel 工程师。YouTube 直播主

Cube

Laravel 新闻

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

Laravel Forge logo

Laravel Forge

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

Laravel Forge
Tinkerwell logo

Tinkerwell

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

Tinkerwell
No Compromises logo

无妥协

来自 No Compromises 播客的两位经验丰富的开发者 Joel 和 Aaron 现已开放接单,为您进行 Laravel 项目开发。⬧ 固定价格 7500 美元/月。⬧ 无漫长的销售流程。⬧ 无合同。⬧ 100% 退款保证。

无妥协
Kirschbaum logo

Kirschbaum

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

Kirschbaum
Shift logo

Shift

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

Shift
Bacancy logo

Bacancy

让您拥有经验丰富的 Laravel 开发人员,拥有 4-6 年的经验,每月仅需 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

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
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 提示构建 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 应用程序中添加评论

阅读文章