你是如何处理 PHP 代码中的枚举类型 Enum 的?

翻译 Summer ⋅ 于 6个月前 ⋅ 2254 阅读 ⋅ 原文地址

站点的翻译文章创建时,您将第一时间收到通知。

这是一篇社区协同翻译的文章,已完成翻译,更多信息请点击 协同翻译介绍

file

本文旨在提供一些更好的理解对于枚举,在PHP 中何时使用,和如何去用的问题。

在某些时候,我们会在代码定义一些常量值。它们能够有效的避免 魔数 。通过用其他的有意义的名称代替这些魔数,并在之后的代码中使用定义好的name。对之后的检索,重命名都会变的更加容易,只因为我们一次定义,并且多次使用这些常量。

这就是为什么类似于下面的代码是比较常见的。


<?php
class User {
    const GENDER_MALE = 0;
    const GENDER_FEMALE = 1;
    const STATUS_INACTIVE = 0;
    const STATUS_ACTIVE = 1;
}
岳池 翻译于 6个月前

查看其他 1 个版本

以上常量表示了两组属性,GEDNER_* 和 STATUS_*。他们表示一组性别和一组用户状态。每一组都是一个枚举 。枚举是一组元素(也叫做成员)的集合,每一个枚举都定义了一种新类型。这个类型,和它的值一样,可以包含任意属于该枚举的元素。

在上面的例子中,枚举借助于常量,每一个常量的值都是一个成员。注意,这样做的话,我们只能在常量包含的类型中取值。因此,我们在写这些值的时候不会有类型提示,不知道详细的枚举类型。

来看一个简短的例子, 但我们假定例子中有更多的代码

<?php
interface UserFactory {
    public function create(
        string $email,
        int $gender,
        int $status
    ): User;
}
$factory->create(
    $email,
    User::STATUS_ACTIVE,
    User::GENDER_FEMALE
);

第一眼看上去代码很好,但是他只是碰巧正确运行了!因为两个不同的枚举成员实际上是同一个值,调用create方法成功,是因为这最后两个参数被互换了不影响结果。尽管我们检查方法接受的值是否有效,运行界面也不会警告我们,测试也会通过。有人能正确的发现这些bug,但是它也很可能被忽视掉。之后一些情况,比如合并冲突的时候,如果它的值改变了,它可能会引起系统异常。

如果使用标量类型,我们会受限于这种类型,无法辨别这两个值是是不是属于两个不同的枚举。

沐雨 翻译于 6个月前

另一个问题是代码的可读性弱。试想当 create 方法没有引用常量时,怎么知道 $gender 参数是枚举类型 ?又在哪里去看已定义的枚举?
我们应该让代码写的易于理解并且功能正常,方便以后查看。

我们可以改进吗?当然可以!思路就是使用类的实例作为枚举元素,该类本身定义了一种新类型。在PHP7我们可以安装 SPL Types (一个PECL扩展),使用其中的 SplEnum 类。


<?php
class YesNo extends \SplEnum
{
    const __default =  self::YES;
    const NO = 0;
    const YES = 1;
}
$no = new YesNo(YesNo::NO);
var_dump($no == YesNo::NO); //true
var_dump(new YesNo(YesNo::NO) == YesNo::NO); //true

我们可以自定义一个类和其中的常量,继承于 SplEnum 类。枚举元素是我们手动实例化的对象,在这种情况下是常量值本身。我们可以将整型与对象进行比较,这看起来可能很奇怪。另外,正如 SplEnum 文档所述,这是一个模拟的枚举。 PHP本身并不支持枚举类型,所以我们这里探讨的所有内容都是模拟的。

我们用这种方法能得到什么? 首先,我们可以在输入参数时提示类型,并让PHP引擎在发生错误时告诉我们。 其次,我们还可以在枚举类中包含一些逻辑,而且可以使用 switch 语句来模拟多态行为。

li-luo-ao 翻译于 5个月前

查看其他 1 个版本

但也有一些缺点. 例如, 在大多数情况下, 有些你可以用枚举元素而不能用标识检查. 这不是不可能的,我们不得不非常小心. 由于我们手动创建枚举成员, 所以许多成员应该是同一个成员, 但这一点手动很难确定.

利用 SplEnum 我们解决枚举类型问题, 但是当我们用标识检查的时候不得不非常小心. 我们需要一个方法限制可以创建的多个元素, 例如  multiton (multiple singleton objects).

现在我们将看到由 Java Enum 启发并实现 multiton 的两个不同的库.

第一个是 eloquent/enumeration. 它为每个元素创建一个定义类的实例. 请注意, 没有我们的帮助, 枚举的用户仿真永远不能保证一个枚举实例, 因为我们限制它的每一步都有一个方法去避免.

sane 翻译于 6个月前

这个库可以让我们用错误的方式去尝试, 例如用反射创建一个实例, 在这一点上我们可以问我们自己是否做了正确的事. 它也可以在代码的评审过程中有所帮助,因为这样的实现可以定义几个应该被遵循的规则. 如果这些规则比较简单很容易发现代码中存在的问题.

让我们看些实例.


<?php
final class YesNo extends \Eloquent\Enumeration\AbstractEnumeration {
    const NO = 0;
    const YES = 1;
}
var_dump(YesNo::YES()->key()); // YES

我们定义了一个继承  \Eloquent\Enumeration\AbstractEnumeration 的新类 YesNo . 接下来我们定义一个定义元素名和创建表现这些元素的对象的库的常量.

还有一些情况我们需要谨记,用 serialize/deserialize 在其中创建自定义对象 .

sane 翻译于 6个月前

我们可以在GitHub页面上找到更多的例子和很完善的文档。

我们要展示的第二个库是 zlikavac32/php-enum. 与 eloquent/enumeration不同,这个库面向允许真正的多态行为的抽象类。 所以,我们可以用每个方法都定义一个枚举元素来实现,而不是使用switch的方法。 通过严格的规则来定义枚举,也可以相当可靠地确保每个元素只有一个实例。

这个库面向抽象类,以便将每个成员的许多实例限制为一个。 这个想法是,每个枚举必须被定义为抽象的,并枚举它的元素。 请注意,你可以通过扩展类,然后构造一个元素来滥用,但是如果你这么用了,这些是会在代码审查过程中标红的。

对于抽象类,我们知道我们不会意外地有一个枚举的新元素,因为它需要具体的实现。 通过遵循在enum本身中保持这些具体实现的规则,我们可以很容易地发现滥用。  匿名类 在这里很有用。

库强制抽象枚举类,但不能强制创建有效的元素。 这是这个库的用户的责任。 图书馆照顾其余的。

让我们看一个简单的例子。


<?php
/**
 * @method static YesNo YES
 * @method static YesNo NO
 */
abstract class YesNo extends \Zlikavac32\Enum\Enum
{
    protected static function enumerate(): array
    {
        return [
            'YES', 'NO'
        ];
    }
}
var_dump(YesNo::YES()->name()); // YES

PHPDoc注释定义了返回枚举元素的现有静态方法。 这有助于搜索和重构代码。 接下来,我们将枚举YesNo定义为抽象,并扩展\Zlikavac32\Enum\Enum并定义一个静态方法enumerate。 然后,在enumerate方法中,我们列出将被用来表示它们的元素名称。

刚刚我们提到了多态行为,那么为什么我们会使用它呢? 当我们试图限制同一个枚举元素的多个实例时会发生一件事,那就是我们不能有循环引用。 让我们想象一下,我们想拥有由NORTHSOUTHEASTWEST组成的WorldSide枚举。 我们还想有一个方法opposite():WorldSide,它返回代表相反的元素。

RyanFeng 翻译于 6个月前

如果我们试图通过构造函数注入相反元素,在某一时刻,我们获得一个循环引用,这意味着,我们需要相同元素的第二个实例。 为了返回一个有效的相反世界,我们不得不用一个代理对象 或者switch语句破解。

随着多态行为,我们能做的就是让我们看到我们可定义我们需要的WorldSide枚举。

<?php
/**
 * @method static WorldSide NORTH
 * @method static WorldSide SOUTH
 * @method static WorldSide EAST
 * @method static WorldSide WEST
 */
abstract class WorldSide extends \Zlikavac32\Enum\Enum
{
    protected static function enumerate(): array
    {
        return [
            'NORTH' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::SOUTH();
                }
            },
            'SOUTH' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::NORTH();
                }
            },
            'EAST' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::WEST();
                }
            },
            'WEST' => new class extends WorldSide {
                public function opposite(): WorldSide {
                    return WorldSide::EAST();
                }
            }
        ];
    }
    abstract public function opposite(): WorldSide;
}
foreach (WorldSide::iterator() as $worldSide) {
    var_dump(sprintf(
        'Opposite of %s is %s', 
        (string) $worldSide, 
        (string) $worldSide->opposite()
    ));
}

enumerate 方法,我们提供了每一个枚举元素的实现。数组是用枚举元素名称来索引的。当手动的创建元素,我们定义我们元素名称作为数据的键。

我们可以用 WorldSide::iterator() 获取枚举元素的顺序迭代器,来定义和遍历他们。 每一个枚举元素都有一个默认的 __toString(): string实现返回元素的名称。

每个枚举元素返回其相反的元素。

rayle 翻译于 6个月前

回顾一下,常量不是枚举,枚举不是常量。每个枚举定义一个类型。如果我们有一些常数的值对我们很重要,但名字没有,我们应该坚持常数。如果我们有一些常量的价值对我们无关紧要,但是与同一群体中的其他所有人有所不同则是重要的,请使用枚举

枚举为代码提供了更多的上下文,也可以将某些检查委托给引擎本身。如果PHP有一个本地的枚举支持,这将是非常好的。语法更改可以使代码更具可读性。引擎可以为我们执行检查,并执行一些不能从用户区执行的规则。

你如何使用枚举,你对这个主题有什么想法?请在下方评论

liugu01 翻译于 6个月前

原文地址:https://blog.trikoder.net/enumerations-a...

译文地址:https://laravel-china.org/topics/7479/ho...


本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

本帖已被设为精华帖!
回复数量: 0
    暂无评论~~
    您需要登陆以后才能留下评论!

    Composer 中国全量镜像

    Top 100 扩展包

    Lumen 中文文档

    Laravel 速查表

    Laravel 中文文档

    Laravel 项目开发规范

    Laravel 开发环境部署

    Elasticsearch-PHP 中文文档

    Lumen 中文文档

    GraphQL PHP 中文文档

    社区文档撰写指南

    TDD 构建 Laravel 论坛笔记

    PHP PSR 标准规范

    PHP 设计模式全集

    Dingo API 中文文档