在 PHP 中使用可变和不可变 DateTime

发布于 作者

Working with Mutable and Immutable DateTime in PHP image

可变日期可能导致代码中的混淆和意外错误。我的目标不是告诉您 DateTime 是邪恶的因为它可变,而是要考虑使用可变与不可变 DateTime 对象的权衡和优势。无论哪种方法,都需要良好的测试套件,并了解修改方法如何影响您的日期对象。

直到最近,我甚至没有意识到 PHP 为 DateTime 类提供了一个对应项:DateTimeImmutableDateTimeIummtable 类的工作方式与 DateTime 类完全相同,只是它永远不会修改自身,而是返回一个新对象。因此,如果您知道如何使用 DateTime,那么您可以立即使用 DateTimeImmutable

在 PHP 中使用可变 DateTime 对象

对我们这些凡人来说幸运的是,我们可以进一步抽象这些类,并使用像 Carbon 这样的库。稍后,我将向您介绍一个类似的基于 DateTimeIummtable 的不可变库。

Carbon 扩展了 DateTime 的可变变体,这意味着当您传递 Carbon 实例时,对其进行的任何修改调用(例如,addDay())都会 **导致更改**

$person = Person::find(1);
echo $person->born // 1998-05-29 02:35:53
$person->born->addDay(1);
echo $person->born // 1998-05-30 02:35:53
$person->save();

在 Eloquent 模型的上下文中,这是非常合理的。否则,每次我们想使用不可变方法更新日期时,我们都会有这种奇怪的赋值。

// This code doesn't work, it's what it would look like if Carbon instances were immutable
$newBirthday = $person->born->addDay(1);
$person->born = $newBirthday;
 
// Or...
$person->born = $person->born->addDay(1);

如果您打算进行一些需要修改的比较检查,您需要制作此日期的副本,以避免改变原始属性。例如,这将改变模型属性。

function birthdayIsTomorrow($birthday) {
return $birthday->addDay(1)->isTomorrow();
}
 
// Mutation takes place...
if (birthdayIsTomorrow($person->born)) {
 
}
 
// No mutation takes place...
if (birthdayIsTomorrow($person->born->copy())) {
 
}
 
// Via clone, no mutation takes place...
if (birthdayIsTomorrow(clone $person->born)) {
 
}

这里另一个有趣的事情是,如果您的代码改变了日期,但您没有 save() 模型,您可能会遇到一个难以追踪的奇怪边缘情况错误。这里最大的收获是要了解代码如何改变您的日期,并将日期的副本而不是整个模型传递给需要修改日期的方法。

我并不是想说使用像 Carbon 这样的可变库是 错误的;我专注于演示这种对象的行为方式以及您必须应用的防御性技术,以避免意外改变。

在 PHP 中使用不可变 DateTime 对象

如果您喜欢使用 Carbon,并且想要尝试具有类似 API 的不可变版本,您可能对 CakePHP 基金会提供的 Chronos 库 感兴趣。Chronos 最初是基于 Carbon 的,是一个独立的库,除了 PHP 版本 ^5.5.9|^7 之外,没有任何外部依赖项。

此外,Chronos 包含可变日期/时间变体,总共有五个类

  • Cake\Chronos\Chronos 是一个不可变的日期和时间对象。
  • Cake\Chronos\Date 是一个不可变的日期对象。
  • Cake\Chronos\MutableDateTime 是一个可变的日期和时间对象。
  • Cake\Chronos\MutableDate 是一个可变的日期对象。
  • Cake\Chronos\ChronosInterval 是 DateInterval 对象的扩展。

在前面的代码示例中使用 Chronos,日期对象不关心改变,因为任何更改都会返回一个新对象。

if (birthdayIsTomorrow($person->born)) {
// Immutable, $person->born would equal `today` still
}

每次您修改对象时,都会返回一个新副本,使您的代码免受基于顺序的依赖性问题的困扰。请看以下示例。

// i.e., 2018-05-29 04:23:01.342143
$meeting = \Cake\Chronos\Chronos::tomorrow();
$meeting->addDay(1); // Returns a new object
 
// No mutation, it returns the original 2018-05-29...
return $meeting;

要使用不可变日期,您需要在使用修改器时替换变量。

$meeting = \Cake\Chronos\Chronos::tomorrow();
$meeting = $meeting->addDay(1);
 
// Returns 2018-05-30...
return $meeting

即使您想将日期重置到一天的开始,您也需要执行相同的操作,或者将其链接到原始赋值。

// Assignment
$meeting = \Cake\Chronos\Chronos::tomorrow();
$meeting = $meeting->addDay(1);
$meeting = $meeting->startOfDay();
 
// Chain the original
// i.e., 2018-05-31 00:00:00.000000
$meeting = \Cake\Chronos\Chronos::tomorrow()
->addDay(1)
->startOfDay();

不可变性使您通过显式重新分配日期对象来强制执行有意更改,并且您不必担心传递原始对象。

了解更多

尽管差异很微妙,但使用不可变日期对象可以使您确信,传递日期不会导致原始对象发生改变,除非您显式重新分配。

我并不是说一种风格优于另一种,我只是指出我认为了解 PHP 的 DateTimeImmutable 可以帮助澄清不可变日期为您代码提供的某些防御性技术。

考虑到这一点,使用 Carbon 时,您需要注意打算传递给其他方法以进行比较或其他逻辑的任何日期。我建议始终复制 DateTime 实例以避免改变,并注意日期中可能导致意外结果的潜在改变点。

如果您想使用具有像 Carbon 这样好的 API 的不可变 DateTime 库进行更多尝试,请查看 Chronos 文档

Paul Redmond photo

Laravel 新闻的撰稿人。全栈 Web 开发人员和作者。

归档于
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

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

Kirschbaum
Shift logo

Shift

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

Shift
Bacancy logo

Bacancy

以每月 2500 美元的价格,为您的项目配备一名经验丰富的 Laravel 开发人员,拥有 4-6 年的经验。获得 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 应用程序添加评论功能

阅读文章