您的位置 首页 php

如何使用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';    }}// objectecho gettype($orderStatus) . PHP_EOL;// boolean truevar_dump($orderStatus instanceof Enum);// 233echo $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);// objectecho gettype($orderStatus) . PHP_EOL;// boolean truevar_dump($orderStatus instanceof Enum);// 1echo $orderStatus . PHP_EOL;// 2️⃣$orderStatus = new OrderStatus();// objectecho gettype($orderStatus) . PHP_EOL;// boolean truevar_dump($orderStatus instanceof Enum);// 0echo $orderStatus;// 3️⃣$orderStatus = new OrderStatus('意外的参数');// objectecho gettype($orderStatus) . PHP_EOL;// boolean truevar_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));// boolvar_dump($WAIT_SHIP->format(true));// 判断这个值是否有效的枚举值var_dump(OrderStatus::isValid(2));// 判断这个值是否有效的枚举值var_dump(OrderStatus::isValid(8));// 获取所有枚举成员的 Keyvar_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来实现枚举?

文章地址:https://www.zhihuclub.com/5929.shtml

关于作者: 智云科技

热门文章

网站地图