文章转发自专业的Laravel开发者社区,原始连接: https://learnku.com/laravel/t...本文旨在提供一些更好的理解什么是枚举,何时使用它们以及如何在php中使用它们.php
咱们在某些时候使用了常量来定义代码中的一些常数值.他们被用来避免魔法值.用一个象征性的名字代替一些魔法值,咱们能够给它一些意义.而后咱们在代码中引用这个符号名称.由于咱们定义了一次并使用了不少次,因此搜索它并稍后重命名或更改一个值会更容易.html
这就是为何看到相似于下面的代码并不罕见.java
<?php class User { const GENDER_MALE = 0; const GENDER_FEMALE = 1; const STATUS_INACTIVE = 0; const STATUS_ACTIVE = 1; }
以上常量表示了两组属性,GEDNER_* 和 STATUS_*。他们表示一组性别和一组用户状态。每一组都是一个枚举 。枚举是一组元素(也叫作成员)的集合,每个枚举都定义了一种新类型。这个类型,和它的值同样,能够包含任意属于该枚举的元素。laravel
在上面的例子中,枚举借助于常量,每个常量的值都是一个成员。注意,这样作的话,咱们只能在常量包含的类型中取值。所以,咱们在写这些值的时候不会有类型提示,不知道详细的枚举类型。git
来看一个简短的例子, 但咱们假定例子中有更多的代码github
<?php interface UserFactory { public function create( string $email, int $gender, int $status ): User; } $factory->create( $email, User::STATUS_ACTIVE, User::GENDER_FEMALE );
第一眼看上去代码很好,可是他只是碰巧正确运行了!由于两个不一样的枚举成员其实是同一个值,调用create方法成功,是由于这最后两个参数被互换了不影响结果。尽管咱们检查方法接受的值是否有效,运行界面也不会警告咱们,测试也会经过。有人能正确的发现这些bug,可是它也极可能被忽视掉。以后一些状况,好比合并冲突的时候,若是它的值改变了,它可能会引发系统异常。api
若是使用标量类型,咱们会受限于这种类型,没法辨别这两个值是是否是属于两个不一样的枚举。数组
另外一个问题是这个代码描述的的不是很好。想象一下 create
方法没有引用常量。$gender
被别人看做为一个枚举元素将是有多么困难?看这些元素在哪里被定义又有多么困难?咱们以后将会阅读那些代码,所以咱们应该尽量是让代码易于阅读以及和经过。oracle
咱们能够作得更好吗? Sure! 这个方法就是是使用类实例做为枚举元素,类自己定义了一个新的类型。 直到PHP 7,咱们能够安装 SPL类 PECL扩展而且使用SplEnum 。ide
<?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
而且定义用于建立枚举元素的常量。枚举元素是咱们手动构造的对象,在这种状况下是常量值自己。 咱们能够将整型与对象进行比较,这可能很奇怪。 另外,正如文档所述,这是一个仿真的枚举。 PHP自己并不支持枚举类型,因此咱们在这里探讨的全部内容都是仿真的。
咱们用这种方法获得了什么? 咱们能够输入提示咱们的参数,并让PHP引擎在发生错误时提醒咱们。 咱们还能够在枚举类中包含一些逻辑,并使用switch
语句来模拟多态行为。
但也有一些缺点. 例如, 在大多数状况下, 有些你能够用枚举元素而不能用标识检查. 这不是不可能的,咱们不得不很是当心. 因为咱们手动建立枚举成员, 因此许多成员应该是同一个成员, 但这一点手动很难肯定.
利用 SplEnum
咱们解决枚举类型问题, 可是当咱们用标识检查的时候不得不很是当心. 咱们须要一个方法限制能够建立的多个元素, 例如 multiton (multiple singleton objects).
如今咱们将看到由 Java Enum 启发并实现 multiton
的两个不一样的库.
第一个是 eloquent/enumeration. 它为每一个元素建立一个定义类的实例. 请注意, 没有咱们的帮助, 枚举的用户仿真永远不能保证一个枚举实例, 由于咱们限制它的每一步都有一个方法去避免.
这个库可让咱们用错误的方式去尝试, 例如用反射建立一个实例, 在这一点上咱们能够问咱们本身是否作了正确的事. 它也能够在代码的评审过程当中有所帮助,由于这样的实现能够定义几个应该被遵循的规则. 若是这些规则比较简单很容易发现代码中存在的问题.
让咱们看些实例.
<?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
在其中建立自定义对象 .
咱们能够在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
方法中,咱们列出将被用来表示它们的元素名称。
刚刚咱们提到了多态行为,那么为何咱们会使用它呢? 当咱们试图限制同一个枚举元素的多个实例时会发生一件事,那就是咱们不能有循环引用。 让咱们想象一下,咱们想拥有由NORTH
,SOUTH
,EAST
和WEST
组成的WorldSide
枚举。 咱们还想有一个方法opposite():WorldSide
,它返回表明相反的元素。
若是咱们试图经过构造函数注入相反元素,在某一时刻,咱们得到一个循环引用,这意味着,咱们须要相同元素的第二个实例。 为了返回一个有效的相反世界,咱们不得不用一个代理对象 或者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
实现返回元素的名称。
每一个枚举元素返回其相反的元素。
回顾一下,常量不是枚举,枚举不是常量。每一个枚举定义一个类型。若是咱们有一些常数的值对咱们很重要,但名字没有,咱们应该坚持常数。若是咱们有一些常量的价值对咱们可有可无,可是与同一群体中的其余全部人有所不一样则是重要的,请使用枚举
枚举为代码提供了更多的上下文,也能够将某些检查委托给引擎自己。若是PHP有一个本地的枚举支持,这将是很是好的。语法更改可使代码更具可读性。引擎能够为咱们执行检查,并执行一些不能从用户区执行的规则。
你如何使用枚举,你对这个主题有什么想法?请在下方评论