如何使用PHP来实现枚举?
本篇文章给大家带来的内容是关于如何使用PHP来实现枚举?有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
枚举
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。—— 维基百科
业务场景
在实际开发过程中我们非常容易接触到枚举类型,但是又因为 PHP 原生对枚举的支持不是太好,所以很多时候 开发人员并没有重视枚举的使用,而是使用全局常量或者类常量代替,而这两个数据原则上还是 字符串
并不能用来做类型判断。
业务
- 订单状态 待支付/待发货/待收货/待评价
- 会员状态 激活/未激活
- ....
等等 ,很多时候我们都会用简单的 1/2/3/4 或者0/1 这样的方式去代表,然后在文档或者注释中规定这些东西。
更高级一点儿的就是定义成常量,然后方便统一存取,但是常量的值还是是字符串,无法进行类型判断。
这里就要看一下 PHP 对枚举的支持,虽然 PHP 对枚举没有完美的支持,但是在 SPL 中还是有一个基础的枚举类
SPL 枚举
SplEnum extends SplType {/ Constants / const NULL __default = NULL ; / 方法 / public getConstList ([ bool $include_default = FALSE ] ) : array / 继承的方法 / SplType::__construct ( [mixed $initial_value [, bool $strict ]] ) }
但是!这个需要额外的安装 PECL 用 PECL 安装 Spl_Types
,无意间增加了使用成本,那有没有其他解决方案?答案是肯定的。
直接手写一个。
开始准备
首先定一个枚举
class Enum { // 默认值 const __default = self::WAIT_PAYMENT; // 待付款 const WAIT_PAYMENT = 0; // 待发货 const WAIT_SHIP = 1; // 待收货 const WAIT_RECEIPT = 2; // 待评价 const WAIT_COMMENT = 3; }
这样似乎就完成了,我们直接使用 Enum::WAIT_PAYMENT
就可以拿到里面的值了,但是传参的地方我们并没法校验他。
function setStatus(Enum $status){ // TODO } setStatus(Enum::WAIT_PAYMENT); // Error 显然这是不行的 因为上面常量的值时一个 int 并不是 Enum 类型。
这里我们就需要用到 PHP 面向对象中的一个魔术方法 __toString()
public __toString ( void ) : string
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
现在我们来完善一下这个方法。
class OrderStatus extends Enum { // 默认值 const __default = self::WAIT_PAYMENT; // 待付款 const WAIT_PAYMENT = 0; // 待发货 const WAIT_SHIP = 1; // 待收货 const WAIT_RECEIPT = 2; // 待评价 const WAIT_COMMENT = 3; public function __toString() { return '233'; } } // object echo gettype($orderStatus) . PHP_EOL; // boolean true var_dump($orderStatus instanceof Enum); // 233 echo $orderStatus;
初具模型
这里似乎实现了一部分,那我们应该怎么样让他做的更好?再来改造一下。
class OrderStatus extends Enum { // 默认值 const __default = self::WAIT_PAYMENT; // 待付款 const WAIT_PAYMENT = 0; // 待发货 const WAIT_SHIP = 1; // 待收货 const WAIT_RECEIPT = 2; // 待评价 const WAIT_COMMENT = 3; /** * @var string */ protected $value; public function __construct($value = null) { $this->value = is_null($value) ? self::__default : $value; } public function __toString() { return (string)$this->value; } } // 1️⃣ $orderStatus = new OrderStatus(OrderStatus::WAIT_SHIP); // object echo gettype($orderStatus) . PHP_EOL; // boolean true var_dump($orderStatus instanceof Enum); // 1 echo $orderStatus . PHP_EOL; // 2️⃣ $orderStatus = new OrderStatus(); // object echo gettype($orderStatus) . PHP_EOL; // boolean true var_dump($orderStatus instanceof Enum); // 0 echo $orderStatus; // 3️⃣ $orderStatus = new OrderStatus('意外的参数'); // object echo gettype($orderStatus) . PHP_EOL; // boolean true var_dump($orderStatus instanceof Enum); // 意外的参数 echo $orderStatus;
在这一次,我们加入了 构造函数
并且允许他传入一个可选的值,然后来作为 __toString
方法的输出值,这次看起来不错,功能都已经实现了,如果传入的参数否和我们的预期的话。但是 万一不符合呢?看看,第 3️⃣ 个那里,就已经成了意外了,哪还有没有办法补救?答案当然是 有的
,在这里我们会用到 PHP 另一个好东西 反射类 ,当然这个不是 PHP 特有的,其他语言也有。
当然,除了反射,我们还会用到另外一个东西 方法重载
里面的 __callStatic
方法。
更进一步
public static __callStatic ( string $name , array $arguments ) : mixed
在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
继续改造。
class Enum { const __default = null; /** * @var string */ protected static $value; // 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new protected function __construct($value = null) { // 很常规 self::$value = is_null($value) ? static::__default : $value; } /** * @param $name * @param $arguments * @return mixed * @throws ReflectionException */ public static function __callStatic($name, $arguments) { // 实例化一个反射类 static::class 表示调用者 $reflectionClass = new ReflectionClass(static::class); // 这里我们要有一个约定, 就是类常量成员的名字必须的大写。 // 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法 // 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。 $constant = $reflectionClass->getConstant(strtoupper($name)); // 获取调用者的 构造方法 $construct = $reflectionClass->getConstructor(); // 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。 $construct->setAccessible(true); // 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。 $static = new static($constant); return $static; } public function __toString() { return (string)self::$value; } } class OrderStatus extends Enum { // 默认值 const __default = self::WAIT_PAYMENT; // 待付款 const WAIT_PAYMENT = 0; // 待发货 const WAIT_SHIP = 1; // 待收货 const WAIT_RECEIPT = 2; // 待评价 const WAIT_COMMENT = 3; } $WAIT_SHIP = OrderStatus::WAIT_SHIP(); var_dump($WAIT_SHIP . ''); var_dump($WAIT_SHIP instanceof Enum);
到这里 一个简单的枚举类就完成了。
完结
那如果我们还有其他需求、比如 判断一个值是不是在枚举范围内?获取所有的枚举值?获取所有的枚举键,判断枚举键是否有效?自动格式化「因为 __toString 方法只允许返回字符串 ,但是有的时候我们强制需要整形、bool 等类型」
class Enum { const __default = null; /** * @var string */ protected static $value; /** * @var ReflectionClass */ protected static $reflectionClass; // 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new protected function __construct($value = null) { // 很常规 self::$value = is_null($value) ? static::__default : $value; } /** * @param $name * @param $arguments * @return mixed */ public static function __callStatic($name, $arguments) { // 实例化一个反射类 static::class 表示调用者 $reflectionClass = self::getReflectionClass(); // 这里我们要有一个约定, 就是类常量成员的名字必须的大写。 // 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法 // 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。 $constant = $reflectionClass->getConstant(strtoupper($name)); // 获取调用者的 构造方法 $construct = $reflectionClass->getConstructor(); // 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。 $construct->setAccessible(true); // 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。 $static = new static($constant); return $static; } /** * 实例化一个反射类 * @return ReflectionClass * @throws ReflectionException */ protected static function getReflectionClass() { if (!self::$reflectionClass instanceof ReflectionClass) { self::$reflectionClass = new ReflectionClass(static::class); } return self::$reflectionClass; } /** * @return string */ public function __toString() { return (string)self::$value; } /** * 判断一个值是否有效 即是否为枚举成员的值 * @param $val * @return bool * @throws ReflectionException */ public static function isValid($val) { return in_array($val, self::toArray()); } /** * 转换枚举成员为键值对输出 * @return array * @throws ReflectionException */ public static function toArray() { return self::getEnumMembers(); } /** * 获取枚举的常量成员数组 * @return array * @throws ReflectionException */ public static function getEnumMembers() { return self::getReflectionClass() ->getConstants(); } /** * 获取枚举成员值数组 * @return array * @throws ReflectionException */ public static function values() { return array_values(self::toArray()); } /** * 获取枚举成员键数组 * @return array * @throws ReflectionException */ public static function keys() { return array_keys(self::getEnumMembers()); } /** * 判断 Key 是否有效 即存在 * @param $key * @return bool * @throws ReflectionException */ public static function isKey($key) { return in_array($key, array_keys(self::getEnumMembers())); } /** * 根据 Key 去获取枚举成员值 * @param $key * @return static */ public static function getKey($key) { return self::$key(); } /** * 格式枚举结果类型 * @param null|bool|int $type 当此处的值时什么类时 格式化输出的即为此类型 * @return bool|int|string|null */ public function format($type = null) { switch (true) { // 当为纯数字 或者类型处传入的为 int 值时 转为 int case ctype_digit(self::$value) || is_int($type): return (int)self::$value; break; // 当 type 传入 true 时 返回 bool 类型 case $type === true: return (bool)filter_var(self::$value, FILTER_VALIDATE_BOOLEAN); break; default: return self::$value; break; } } } class OrderStatus extends Enum { // 默认值 const __default = self::WAIT_PAYMENT; // 待付款 const WAIT_PAYMENT = 0; // 待发货 const WAIT_SHIP = 1; // 待收货 const WAIT_RECEIPT = 2; // 待评价 const WAIT_COMMENT = 3; } $WAIT_SHIP = OrderStatus::WAIT_SHIP(); // 直接输出是字符串 echo $WAIT_SHIP; // 判断类型是否存在 var_dump($WAIT_SHIP instanceof OrderStatus); // 格式化输出一下 是要 字符串 、还是 bool 还是整形 // 自动 var_dump($WAIT_SHIP->format()); // 整形 var_dump($WAIT_SHIP->format(1)); // bool var_dump($WAIT_SHIP->format(true)); // 判断这个值是否有效的枚举值 var_dump(OrderStatus::isValid(2)); // 判断这个值是否有效的枚举值 var_dump(OrderStatus::isValid(8)); // 获取所有枚举成员的 Key var_dump(OrderStatus::keys()); // 获取所有枚举成员的值 var_dump(OrderStatus::values()); // 获取枚举成员的键值对 var_dump(OrderStatus::toArray()); // 判断枚举 Key 是否有效 var_dump(OrderStatus::isKey('WAIT_PAYMENT')); // 判断枚举 Key 是否有效 var_dump(OrderStatus::isKey('WAIT_PAYMENT_TMP')); // 根据 Key 取去 值 注意 这里取出来的已经不带有类型了 // 更加建议直接使用 取类常量的方式去取 或者在高版本的 直接使用类常量修饰符 // 将类常量不可见最佳,但是需要额外处理了 var_dump(OrderStatus::getKey('WAIT_PAYMENT') ->format(1));
截至目前 一个完整的枚举就完成了~
以上是如何使用PHP来实现枚举?的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

PHP 8.4 带来了多项新功能、安全性改进和性能改进,同时弃用和删除了大量功能。 本指南介绍了如何在 Ubuntu、Debian 或其衍生版本上安装 PHP 8.4 或升级到 PHP 8.4

Visual Studio Code,也称为 VS Code,是一个免费的源代码编辑器 - 或集成开发环境 (IDE) - 可用于所有主要操作系统。 VS Code 拥有针对多种编程语言的大量扩展,可以轻松编写

JWT是一种基于JSON的开放标准,用于在各方之间安全地传输信息,主要用于身份验证和信息交换。1.JWT由Header、Payload和Signature三部分组成。2.JWT的工作原理包括生成JWT、验证JWT和解析Payload三个步骤。3.在PHP中使用JWT进行身份验证时,可以生成和验证JWT,并在高级用法中包含用户角色和权限信息。4.常见错误包括签名验证失败、令牌过期和Payload过大,调试技巧包括使用调试工具和日志记录。5.性能优化和最佳实践包括使用合适的签名算法、合理设置有效期、

字符串是由字符组成的序列,包括字母、数字和符号。本教程将学习如何使用不同的方法在PHP中计算给定字符串中元音的数量。英语中的元音是a、e、i、o、u,它们可以是大写或小写。 什么是元音? 元音是代表特定语音的字母字符。英语中共有五个元音,包括大写和小写: a, e, i, o, u 示例 1 输入:字符串 = "Tutorialspoint" 输出:6 解释 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。总共有 6 个元

本教程演示了如何使用PHP有效地处理XML文档。 XML(可扩展的标记语言)是一种用于人类可读性和机器解析的多功能文本标记语言。它通常用于数据存储

如何在系统重启后自动设置unixsocket的权限每次系统重启后,我们都需要执行以下命令来修改unixsocket的权限:sudo...

静态绑定(static::)在PHP中实现晚期静态绑定(LSB),允许在静态上下文中引用调用类而非定义类。1)解析过程在运行时进行,2)在继承关系中向上查找调用类,3)可能带来性能开销。

在Docker环境中使用PECL安装扩展时报错的原因及解决方法在使用Docker环境时,我们常常会遇到一些令人头疼的问�...
