zlikavac32/php-enum

更好的 PHP 枚举支持

5.0.0 2022-11-15 11:33 UTC

This package is auto-updated.

Last update: 2024-09-15 16:08:28 UTC


README

Build Status Latest Stable Version License

这个库旨在使您在 PHP 中的日常枚举使用更加方便。

目录

  1. 什么是枚举以及何时使用它们?
  2. 安装
  3. 特性
    1. 类型提示
    2. 多态
    3. 身份验证
  4. API
    1. 类方法
    2. 对象方法
    3. 断言函数
  5. 用法
    1. UnhandledEnumException
  6. 限制
    1. 不支持序列化
    2. 不支持克隆
    3. 保留方法
  7. 局限性
  8. 经验法则
  9. 示例
  10. 本库背后的原因

什么是枚举以及何时使用它们?

参考 维基百科

在计算机编程中,枚举类型(也称为枚举、枚举或 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() 方法之外扩展(和构造)您的枚举类。

不要使您的枚举复杂。如果您的枚举需要某种服务才能工作,您可能不需要枚举或该逻辑不属于这里。

这并不是每个问题的解决方案。如果您有一组在语义上属于一起并且具有共同“类型”的项目,例如 GenderWorldSide,请将它们定义为枚举。如果您的项目包含任意值,这些值倾向于变化,如 WORKER_WAIT_TIME,请让它们作为常量。它们意味着不同的事情。

因此,总结一下,枚举具有含义,而常量则没有。

示例

您可以在示例中查看更多使用代码注释的示例。

本库背后的原因

我认为这种方法非常直观,是一个很好的枚举方向。如果您想,您可以滥用任何东西。这并不意味着您不应该使用这个库。这意味着,如果您考虑到这些限制,它会使您的工作更容易。

枚举不仅仅是常量的组,它们是类型本身,可以表现出多态行为。这正是这个库旨在提供的。谨慎使用它,它应该非常有帮助。

最终目标是看到类似原生PHP枚举实现的类似东西,但至少现在,我们只能梦想,对吧?