ramsey/uuid 常见问题(FAQ)实战指南:修复 rhumsaa/uuid 弃用警告、理解 final 类设计与测试策略
ramsey/uuid 常见问题FAQ实战指南修复 rhumsaa/uuid 弃用警告、理解 final 类设计与测试策略【免费下载链接】uuid:snowflake: A PHP library for generating universally unique identifiers (UUIDs).项目地址: https://gitcode.com/gh_mirrors/uui/uuid本篇技术指南以 ramsey/uuid当前仓库gh_mirrors/uui/uuid即该 PHP UUID 库的开源代码库官方 FAQ 文档为核心系统解答开发者最常见的三大疑问如何消除 Composer 安装时出现的rhumsaa/uuid is abandoned弃用警告、为什么库中大量类被标记为final以及这背后的设计哲学、以及面对final类时如何优雅地为代码编写单元测试。读完本文你将掌握 2.x 系列的迁移命令、理解 ramsey/uuid 基于不可变类型的 API 设计原则并能在测试中绕过final限制注入指定类型的 UUID。1. 修复 rhumsaa/uuid is abandoned 提示在使用 Composer 安装项目依赖时如果项目或其依赖链中仍包含老旧的rhumsaa/uuid包你可能会看到如下警告Package rhumsaa/uuid is abandoned; you should avoid using it. Use ramsey/uuid instead.看到这条消息不必惊慌。rhumsaa/uuid是 ramsey/uuid 的历史命名该库在早期版本2.x 系列使用Rhumsaa命名空间发布后来项目更名为 ramsey/uuid但为了兼容老用户2.x 系列在很长一段时间内仍保留Rhumsaa命名空间。出现这条警告说明依赖树中引入了已经停止维护的旧包名。1.1 迁移步骤只需执行两条 Composer 命令即可完成迁移composer remove rhumsaa/uuid composer require ramsey/uuid^2.9执行完毕后你将获得2.x 系列中最新的 ramsey/uuid 包并且不需要修改任何业务代码——因为 2.x 系列中的命名空间仍然是Rhumsaa你的use Rhumsaa\Uuid\Uuid;这类引用依然有效。注意这里指定^2.9是官方 FAQ 针对零代码改动迁移给出的推荐约束。如果你正在进行更大版本的升级例如升到 3.x、4.x请参考仓库中的 docs/upgrading/2-to-3.rst 与 docs/upgrading/3-to-4.rst 了解命名空间与 API 的变化细节。2. 为什么 ramsey/uuid 大量使用final很多初次接触 ramsey/uuid 的开发者会发现库中返回的几乎所有具体类都被标记为final包括UuidV1、UuidV4以及Type\Integer、Type\Hexadecimal等值对象。这不是随意为之而是经过深思熟虑的设计决策。2.1 根本原因UUID 由规则定义UUID 由一套公开的规范定义——RFC 9562其前身为RFC 4122。这套规则不应该被改变一旦被改变它就不再是 UUID至少不是 RFC 9562 所定义的 UUID。以 src/Rfc4122/UuidV1.php 为例假设你的应用想对这个类型做特殊处理可能会使用instanceof运算符判断某个变量是否是UuidV1或者在方法参数上做类型提示。如果某个第三方库传入一个继承了 UuidV1 并重写了某些关键内部逻辑的子类对象那么你拿到的可能就不再是一个真正的版本 1 UUID。也许在理想世界中大家可以自律、和平共处但 ramsey/uuid 无法为UuidV1的任意子类作出任何保证。而final让这一点变得确定它能够对实现Ramsey\Uuid\UuidInterface或Ramsey\Uuid\Rfc4122\UuidInterface的类作出保证只要是与final类打交道的实例就可以确信该对象的创建规则不会被改变即使第三方库传来的是同一个类的实例。2.2 值对象也不能被继承类型即契约这也是为什么 ramsey/uuid 在 docs/reference/types.rst 中规定的参数与返回类型大量使用final——这些值对象必须是不可变的、数据内容可预期的。Type\Integer除了数字字符外不应包含任何其他字符。为了在 64 位和 32 位系统上支持超过PHP_INT_MAX/PHP_INT_MIN的大整数它内部将整数以字符串形式存储并在prepareValue()中通过正则/^\d$/严格校验Type\Hexadecimal除了十六进制字符外不应包含任何其他字符构造时会统一转小写并去除可选的0x前缀再通过/^[A-Fa-f0-9]$/校验Type\Time封装秒与微秒确保时间戳确实是时间戳整数同理还有Type\Decimal、MaxUuid、NilUuid等。如果其他库能够继承这些类并把它们从 UUID 实例中返回出来ramsey/uuid 就无法再保证这些值的内容。从源码结构看src/Rfc4122/目录下的UuidV1~UuidV8、MaxUuid、NilUuid以及src/Type/目录下的Integer、Hexadecimal、Time、Decimal全部是final class与 FAQ 的描述完全一致。你可以把final类理解成与严格类型strict types中的int、float、bool类似这些类型本身不可改变因此 ramsey/uuid 中的 final 类就是不可改变的类型。2.3 扩展与自定义final不等于死板尽管用了finalramsey/uuid 依然非常灵活你可以尽可能多地覆盖它的默认行为。官方文档给出了大量自定义入口例如版本 1 UUID 的随机节点配置避免泄露机器信息自定义 Timestamp-First COMB codec用于数据库索引优化的 COMB 格式替换默认工厂UuidFactory全局改变Uuid静态方法的行为更多定制方式参见 docs/customize.rst。这种灵活性来源于三个核心手段接口interfaces、工厂factories与依赖注入dependency injection。生成层有RandomGeneratorInterface、TimeGeneratorInterface、NameGeneratorInterface见 src/Generator/组装层有UuidBuilderInterface见 src/Builder/UuidBuilderInterface.php编码层有CodecInterface见 src/Codec/CodecInterface.php顶层工厂是UuidFactoryInterface默认实现UuidFactory通过FeatureSet探测当前环境的可用特性并装配全部组件。同时Uuid类src/Uuid.php提供了getFactory()/setFactory()静态方法setFactory()内部会通过$factory ! new UuidFactory()的非严格比较来判断工厂是否被替换从而决定后续静态调用是否还能保持纯净性假设。最终的效果是UUID 自身有严格的规则以保证其实际唯一性ramsey/uuid 在保证其他代码无法破坏这一预期的同时允许你的代码和第三方库改变 UUID 的生成方式并返回 RFC 9562 未规定的其他类型 UUID。3. 面对final如何编写测试final带来的一个实际痛点是无法直接 mock 或继承这些类来编写单元测试。但解决之道不止一条。官方在 docs/testing.rst 中给出了三种经过验证的技术这里逐一展开。3.1 技术一注入指定类型的真实 UUID假设有一个方法使用了UuidV1类型提示public function tellTime(UuidV1 $uuid): string { return $uuid-getDateTime()-format(Y-m-d H:i:s); }由于参数是UuidV1final 类无法 mock最直接的办法是生成一个真实的UuidV1实例传入public function testTellTime(): void { $uuid Uuid::uuid1(); $myObj new MyClass(); $this-assertIsString($myObj-tellTime($uuid)); }如果希望断言返回的是确定的字符串可以提前生成一个已知的版本 1 UUID用Uuid::fromString()传入public function testTellTime(): void { // 我们提前生成了这个版本 1 UUID并已知其包含的准确时间 // 因此可以用它来断言方法的返回值。 $uuid Uuid::fromString(177ef0d8-6630-11ea-b69a-0242ac130003); $myObj new MyClass(); $this-assertSame(2020-03-14 20:12:12, $myObj-tellTime($uuid)); }上述示例基于 PHPUnit但思路适用于任何测试框架。3.2 技术二让静态方法返回指定的 UUID更棘手的情况是被测方法内部直接调用Uuid::uuid1()之类的静态方法public function tellTime(): string { $uuid Uuid::uuid1(); return $uuid-getDateTime()-format(Y-m-d H:i:s); }此时可以通过替换默认工厂来接管静态方法的返回值。参见 docs/customize/factory.rst先创建一个测试用工厂继承UuidFactory并重写uuid1()namespace MyPackage; use Ramsey\Uuid\UuidFactory; use Ramsey\Uuid\UuidInterface; class MyTestUuidFactory extends UuidFactory { public $uuid1; public function uuid1($node null, ?int $clockSeq null): UuidInterface { return $this-uuid1; } }然后在测试中用Uuid::setFactory()替换默认工厂并动态改变返回值/** * runInSeparateProcess * preserveGlobalState disabled */ public function testTellTime(): void { $factory new MyTestUuidFactory(); Uuid::setFactory($factory); $myObj new MyClass(); $factory-uuid1 Uuid::fromString(177ef0d8-6630-11ea-b69a-0242ac130003); $this-assertSame(2020-03-14 20:12:12, $myObj-tellTime()); $factory-uuid1 Uuid::fromString(13814000-1dd2-11b2-9669-00007ffffffe); $this-assertSame(1970-01-01 00:00:00, $myObj-tellTime()); }⚠️ 特别注意工厂是Uuid类上的静态属性见 src/Uuid.php。一旦替换此后的所有Uuid静态调用都会使用新工厂因此测试必须使用runInSeparateProcess独立进程并关闭全局状态保留preserveGlobalState disabled否则会污染其他测试。独立进程会显著拖慢测试速度所以请谨慎使用这一技巧如果可能尽量通过参数把依赖传入对象而不是在对象内部创建或获取依赖——这会让测试更简单。3.3 技术三Mock 接口UuidInterface既然具体类是final那就 mock接口。Ramsey\Uuid\UuidInterfacesrc/UuidInterface.php是库对外暴露的核心契约完全可以被 mock。考虑一个接受UuidInterface的方法public function tellTime(UuidInterface $uuid): string { return $uuid-getDateTime()-format(Y-m-d H:i:s); }使用 Mockery或其他 mock 库PHPUnit 也内置 mock 能力模拟该接口并断言其方法被正确调用public function testTellTime(): void { $dateTime Mockery::mock(DateTime::class); $dateTime-expects()-format(Y-m-d H:i:s)-andReturn(a test date); $uuid Mockery::mock(UuidInterface::class, [ getDateTime $dateTime, ]); $myObj new MyClass(); $this-assertSame(a test date, $myObj-tellTime($uuid)); }在这个示例中我们并不关心返回值是否是真实日期格式只关心UuidInterface上的方法确实被调用了。这是对依赖接口而非具体类这一良好实践的直接运用。4. 小结问题结论rhumsaa/uuid is abandoned警告composer remove rhumsaa/uuidcomposer require ramsey/uuid^2.9无需改代码2.x 命名空间仍为Rhumsaa为什么使用finalUUID 由 RFC 9562/4122 规则定义final保证类实例的创建规则与值内容永不被第三方子类破坏值对象为何也finalType\Integer、Type\Hexadecimal、Type\Time等如同int/float/bool是不可变类型契约final是否影响扩展不影响通过接口、工厂UuidFactoryInterface、依赖注入和Uuid::setFactory()可以高度定制生成行为如何测试注入真实 UUIDUuid::uuid1()/Uuid::fromString()、替换静态工厂、或 mockUuidInterfaceramsey/uuid 的设计哲学可以概括为一句话严格约束 UUID 本身final同时最大限度地开放生成过程接口 工厂 DI。理解了这一层无论是处理依赖迁移、阅读其源码还是为你的业务代码编写可靠的测试都会更加得心应手。如果你需要深入了解本文涉及的自定义工厂与测试细节可直接阅读仓库中的 docs/testing.rst、docs/customize/factory.rst 与 docs/customize.rst并结合 src/FeatureSet.php、src/UuidFactory.php 等源码印证其实现。【免费下载链接】uuid:snowflake: A PHP library for generating universally unique identifiers (UUIDs).项目地址: https://gitcode.com/gh_mirrors/uui/uuid创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考