zlikavac32 / php-enum
更好的 PHP 枚举支持
Requires
- php: ^8.1
- ext-json: *
Requires (Dev)
- phpspec/phpspec: ^7.2
- phpunit/phpunit: ^9.5
README
这个库旨在使您在 PHP 中的日常枚举使用更加方便。
目录
什么是枚举以及何时使用它们?
参考 维基百科
在计算机编程中,枚举类型(也称为枚举、枚举或 R 编程语言中的因子,以及在统计学中的分类变量)是一种数据类型,由一组称为元素、成员、枚举或枚举器的命名值组成。[...]换句话说,枚举类型具有不同的值,可以进行比较和分配,但程序员没有指定它们在计算机内存中的任何特定具体表示;编译器和解释器可以任意表示它们。
您可以将枚举视为增强版的常量,使用枚举的主要好处是隐藏内部表示,换句话说,枚举本身就是类型。另一方面,常量继承了分配值的类型。在此基础上,此实现还提供了多态行为(受 Java 枚举 启发)。
话虽如此,如果您想逻辑上分组类似的项目,请使用枚举,对于其他事情请使用常量。通过使用枚举,您可以类型提示参数和返回值,并将各种检查委托给 PHP 引擎。
安装
建议通过 Composer 进行安装。
composer require zlikavac32/php-enum
特性
自定义枚举实现(模拟)在 PHP 世界中并不新鲜,因为 PHP 缺乏原生的枚举支持。本库旨在在 PHP 中提供正确的枚举支持。
类型提示
您可以像任何其他类一样类型提示您的参数和返回值(例如 function getNextDay(WeekDay $day): Day
)。这(检查 局限性),基本上保证了您将始终获得有效的枚举对象。
多态
在幕后,每个枚举都是定义的枚举类的实例。这使得您能够定义自己的构造函数,覆盖某些方法,甚至定义在以后的不同枚举变体中实现的具体方法。
身份验证
每次调用枚举对象都保证返回相同的实例。这样,您就可以使用 ===
来检查实例是否相同。
API
主类是 \Zlikavac32\ZEnum\ZEnum
,它充当基枚举(我宁愿有 enum
关键字,但生活并不完美)。您必须扩展它,并在类的 PHPDoc 注释中提供静态方法的列表。请参阅 用法 部分以查看实际示例。
此类还公开了一些公共静态和非静态方法,如下所示。
类方法
final valueOf(string $name): static
- 返回由名称标识的枚举对象或抛出异常(如果未找到)final contains(string $name): bool
- 如果由名称$name
识别的枚举对象存在,则返回true
,否则返回false
final values(): static[]
- 返回按定义顺序排列的所有定义的枚举对象iterator(): Iterator<static>
- 返回包含所有定义的枚举对象的迭代器对象,按定义顺序排列
对象方法
__toString(): string
- 返回默认的字符串表示形式,即枚举名称本身jsonSerialize(): mixed
- 默认为元素名称final name(): string
- 返回枚举对象名称final ordinal(): int
- 返回枚举对象的序号(它在集合中的位置),从 0 开始final isAnyOf(Enum ...$enums): bool
- 如果枚举对象在$enums
列表中,则返回true
,否则返回false
其他方法作为限制不一致行为的一种方式,例如,要有两个具有相同枚举名称的不同的对象。有关更多信息,请查看限制部分。
断言函数
src/functions.php
中有一些辅助函数可以帮助您断言某些输入。每个函数在断言失败时都会抛出 \LogicException
。您可以在源代码中查看文档,这里只是列出了一些
assertFqnIsEnumClass(string $fqn): void
assertEnumClassAdheresConstraints(string $fqn): void
assertNoParentHasEnumerateMethodForClass(string $fqn): void
assertNoParentHasPHPDocMethodForClass(string $fqn): void
assertValidNamePattern(string $name): void
assertEnumClassIsAbstract(string $fqn): void
assertValidEnumCollection(string $class, array $enumCollection, string $enumClassFqn): void
assertElementNameIsString(string $class, $name): void
assertValidEnumElementObjectType(string $class, $object, string $enumClassFqn): void
用法
创建一个抽象类来表示您的枚举,并让它扩展 \Zlikavac32\ZEnum\ZEnum
。
您必须在类的 PHPDoc 注释中列出静态方法。方法名称用作枚举名称。
/** * @method static YesNo YES * @method static YesNo NO */ abstract class YesNo extends \Zlikavac32\ZEnum\ZEnum { }
一旦定义了枚举类,就可以通过调用 YourEnumClass::YOUR_ENUM_NAME()
访问每个定义的枚举对象,例如在示例中,YesNo::YES()
和 YesNo::NO()
。
另一个建议是使用常量约定来定义枚举名称,其中您使用大写字母和下划线作为分隔符。
对同一枚举的每次调用都将返回该相同对象,因此您可以安全地使用身份操作符。
还可以手动实例化枚举对象,并将它们作为映射(而不是名称列表)返回。
/** * @method static YesNo YES * @method static YesNo NO */ abstract class YesNo extends \Zlikavac32\ZEnum\ZEnum { protected static function enumerate(): array { return [ 'YES' => new class extends YesNo {}, 'NO' => new class extends YesNo {} ]; } }
请注意,在 PHPDoc 注释中列出的每个枚举名称都必须在 enumerate 方法中作为键存在,并具有有效的枚举对象作为值。
UnhandledEnumException
如果添加了新的枚举名称时出现未处理的分支,我们会很高兴得到通知。这需要一些手动工作才能实现。
提供了一个方便的 \Zlikavac32\ZEnum\UnhandledEnumException
来使这项手动工作更容易一些。更容易的意思是我们得到了一个友好的错误消息。
它打算在检查每个枚举名称时的默认情况下抛出。
switch ($enum) { case YesNo::YES(): // do something case YesNo::NO(): // do something else default: throw new UnhandledEnumException($enum); }
多个父类
在定义枚举类和 \Zlikavac32\ZEnum\ZEnum
类之间,可以存在多个类,但有一些限制(请参阅关于继承的限制)。
要查看示例,请打开examples/hashable_enum.php。
限制
为了减轻错误使用和不眠之夜的调试,设置了一些限制。它们存在不是因为我想它们存在,而是作为早期警告,表明长期来看可能会出现问题。如果您真的很努力,您仍然可以避免它们,但这对这个库对您有什么用呢?
不支持序列化
为了最大限度地避免误用,您不能序列化/反序列化枚举对象。在正常库使用中,这可以保留身份检查,而这种检查可能会无意中被破坏。
不支持克隆
这个原因与序列化相同。
关于继承的限制
- 中间没有类可以在PHPDoc注释中列出方法
- 中间没有类可以定义
enumerate
方法 - 链中的每个类都必须定义为抽象的
- 定义枚举类必须是具体枚举对象的第一父类
保留方法
\Zlikavac32\ZEnum\ZEnum
的所有公共方法都不能用作枚举名称。有关更多详细信息,请参阅API部分。
局限性
与任何模拟一样,有一些限制。主要限制是,这个库不能保证每个对象都是有效的枚举实例之一。如果您真的想努力寻找边缘情况,您会成功,但这并不是目的,因为这最初是无法修复的。如果一种语言不施加一些限制,用户空间实现几乎不可能做到这一点。
由于您可以随时扩展现有的枚举,因此您可以将它发送到期望有效枚举的地方,我认为目前还没有办法限制这一点。需要非最终类来创建枚举对象,因此,无法实施任何限制。即使可以,也可以用同样的方式避免。
另一方面,您永远不能更改现有的枚举,这是其中一个目标。
另一个限制是,所有操作都是在运行时完成的,因此一些静态分析可能会报告问题,或者一些编译器检查可能不会执行,如果PHP有原生支持,这些检查本应该被执行。为了应对这种情况,您可以在文档块注释中提示方法。
经验法则
正确使用此库可以使您的生活更轻松,代码更易读、更有意义。您应该避免做的事情是在 enumerate()
方法之外扩展(和构造)您的枚举类。
不要使您的枚举复杂。如果您的枚举需要某种服务才能工作,您可能不需要枚举或该逻辑不属于这里。
这并不是每个问题的解决方案。如果您有一组在语义上属于一起并且具有共同“类型”的项目,例如 Gender
或 WorldSide
,请将它们定义为枚举。如果您的项目包含任意值,这些值倾向于变化,如 WORKER_WAIT_TIME
,请让它们作为常量。它们意味着不同的事情。
因此,总结一下,枚举具有含义,而常量则没有。
示例
您可以在示例中查看更多使用代码注释的示例。
本库背后的原因
我认为这种方法非常直观,是一个很好的枚举方向。如果您想,您可以滥用任何东西。这并不意味着您不应该使用这个库。这意味着,如果您考虑到这些限制,它会使您的工作更容易。
枚举不仅仅是常量的组,它们是类型本身,可以表现出多态行为。这正是这个库旨在提供的。谨慎使用它,它应该非常有帮助。
最终目标是看到类似原生PHP枚举实现的类似东西,但至少现在,我们只能梦想,对吧?