当前位置:首页 > PHP教程 > php应用 > 列表

如何使用PHP来实现枚举?

发布:smiling 来源: PHP粉丝网  添加日期:2020-02-15 13:57:06 浏览: 评论:0 

本篇文章给大家带来的内容是关于如何使用PHP来实现枚举?有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

枚举

在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。

枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。—— 维基百科

业务场景

在实际开发过程中我们非常容易接触到枚举类型,但是又因为 PHP 原生对枚举的支持不是太好,所以很多时候 开发人员并没有重视枚举的使用,而是使用全局常量或者类常量代替,而这两个数据原则上还是 字符串 并不能用来做类型判断。

业务

订单状态 待支付/待发货/待收货/待评价

会员状态 激活/未激活

....

等等 ,很多时候我们都会用简单的 1/2/3/4 或者0/1 这样的方式去代表,然后在文档或者注释中规定这些东西。

更高级一点儿的就是定义成常量,然后方便统一存取,但是常量的值还是是字符串,无法进行类型判断。

这里就要看一下 PHP 对枚举的支持,虽然 PHP 对枚举没有完美的支持,但是在 SPL 中还是有一个基础的枚举类

SPL 枚举

  1. SplEnum extends SplType {/ Constants / 
  2.  
  3. const NULL __default = NULL ; 
  4.  
  5. / 方法 / 
  6.  
  7. public getConstList ([ bool $include_default = FALSE ] ) : array 
  8.  
  9. / 继承的方法 / 
  10.  
  11. SplType::__construct ( [mixed $initial_value [, bool $strict ]] ) 
  12.  

但是!这个需要额外的安装 PECL 用 PECL 安装 Spl_Types,无意间增加了使用成本,那有没有其他解决方案?答案是肯定的。

直接手写一个。

开始准备

首先定一个枚举

  1. class Enum 
  2.  
  3.  
  4.     // 默认值 
  5.  
  6.     const __default = self::WAIT_PAYMENT; 
  7.  
  8.     // 待付款 
  9.  
  10.     const WAIT_PAYMENT = 0; 
  11.  
  12.     // 待发货 
  13.  
  14.     const WAIT_SHIP = 1; 
  15.  
  16.     // 待收货 
  17.  
  18.     const WAIT_RECEIPT = 2; 
  19.  
  20.     // 待评价 
  21.  
  22.     const WAIT_COMMENT = 3; 
  23.  

这样似乎就完成了,我们直接使用 Enum::WAIT_PAYMENT 就可以拿到里面的值了,但是传参的地方我们并没法校验他。

  1. function setStatus(Enum $status){ 
  2.  
  3.     // TODO 
  4.  
  5.  
  6. setStatus(Enum::WAIT_PAYMENT); 
  7.  
  8. // Error 显然这是不行的 因为上面常量的值时一个 int 并不是 Enum 类型。 

这里我们就需要用到 PHP 面向对象中的一个魔术方法 __toString()

public __toString ( void ) : string

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

现在我们来完善一下这个方法。

  1. class OrderStatus extends Enum 
  2.  
  3.  
  4.     // 默认值 
  5.  
  6.     const __default = self::WAIT_PAYMENT; 
  7.  
  8.     // 待付款 
  9.  
  10.     const WAIT_PAYMENT = 0; 
  11.  
  12.     // 待发货 
  13.  
  14.     const WAIT_SHIP = 1; 
  15.  
  16.     // 待收货 
  17.  
  18.     const WAIT_RECEIPT = 2; 
  19.  
  20.     // 待评价 
  21.  
  22.     const WAIT_COMMENT = 3; 
  23.  
  24.  
  25.  
  26.     public function __toString() 
  27.  
  28.     { 
  29.  
  30.         return '233'
  31.  
  32.     } 
  33.  
  34.  
  35. // object 
  36.  
  37. echo gettype($orderStatus) . PHP_EOL; 
  38.  
  39. // boolean true 
  40.  
  41. var_dump($orderStatus instanceof Enum); 
  42.  
  43. // 233 
  44.  
  45. echo $orderStatus

初具模型

这里似乎实现了一部分,那我们应该怎么样让他做的更好?再来改造一下。

  1. class OrderStatus extends Enum 
  2.  
  3.  
  4.     // 默认值 
  5.  
  6.     const __default = self::WAIT_PAYMENT; 
  7.  
  8.     // 待付款 
  9.  
  10.     const WAIT_PAYMENT = 0; 
  11.  
  12.     // 待发货 
  13.  
  14.     const WAIT_SHIP = 1; 
  15.  
  16.     // 待收货 
  17.  
  18.     const WAIT_RECEIPT = 2; 
  19.  
  20.     // 待评价 
  21.  
  22.     const WAIT_COMMENT = 3; 
  23.  
  24.     /** 
  25.  
  26.      * @var string 
  27.  
  28.      */ 
  29.  
  30.     protected $value
  31.  
  32.  
  33.  
  34.     public function __construct($value = null) 
  35.  
  36.     { 
  37.  
  38.         $this->value = is_null($value) ? self::__default : $value
  39.  
  40.     } 
  41.  
  42.  
  43.  
  44.     public function __toString() 
  45.  
  46.     { 
  47.  
  48.         return (string)$this->value; 
  49.  
  50.     } 
  51.  
  52.  
  53.  
  54.  
  55. // 1️⃣ 
  56.  
  57. $orderStatus = new OrderStatus(OrderStatus::WAIT_SHIP); 
  58.  
  59.  
  60.  
  61. // object 
  62.  
  63. echo gettype($orderStatus) . PHP_EOL; 
  64.  
  65. // boolean true 
  66.  
  67. var_dump($orderStatus instanceof Enum); 
  68.  
  69. // 1 
  70.  
  71. echo $orderStatus . PHP_EOL; 
  72.  
  73. // 2️⃣ 
  74.  
  75. $orderStatus = new OrderStatus(); 
  76.  
  77. // object 
  78.  
  79. echo gettype($orderStatus) . PHP_EOL; 
  80.  
  81. // boolean true 
  82.  
  83. var_dump($orderStatus instanceof Enum); 
  84.  
  85. // 0 
  86.  
  87. echo $orderStatus
  88.  
  89. // 3️⃣ 
  90.  
  91. $orderStatus = new OrderStatus('意外的参数'); 
  92.  
  93. // object 
  94.  
  95. echo gettype($orderStatus) . PHP_EOL; 
  96.  
  97. // boolean true 
  98.  
  99. var_dump($orderStatus instanceof Enum); 
  100.  
  101. // 意外的参数 
  102.  
  103. echo $orderStatus

在这一次,我们加入了 构造函数 并且允许他传入一个可选的值,然后来作为 __toString 方法的输出值,这次看起来不错,功能都已经实现了,如果传入的参数否和我们的预期的话。但是 万一不符合呢?看看,第 3️⃣ 个那里,就已经成了意外了,哪还有没有办法补救?答案当然是 有的 ,在这里我们会用到 PHP 另一个好东西 反射类 ,当然这个不是 PHP 特有的,其他语言也有。

当然,除了反射,我们还会用到另外一个东西 方法重载 里面的 __callStatic 方法。

更进一步

public static __callStatic ( string $name , array $arguments ) : mixed

在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。

$name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。

继续改造。

  1. class Enum 
  2.  
  3.  
  4.     const __default = null; 
  5.  
  6.     /** 
  7.  
  8.      * @var string 
  9.  
  10.      */ 
  11.  
  12.     protected static $value
  13.  
  14.  
  15.  
  16.     // 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new 
  17.  
  18.     protected function __construct($value = null) 
  19.  
  20.     { 
  21.  
  22.         // 很常规 
  23.  
  24.         self::$value = is_null($value) ? static::__default : $value
  25.  
  26.     } 
  27.  
  28.  
  29.  
  30.     /** 
  31.  
  32.      * @param $name 
  33.  
  34.      * @param $arguments 
  35.  
  36.      * @return mixed 
  37.  
  38.      * @throws ReflectionException 
  39.  
  40.      */ 
  41.  
  42.     public static function __callStatic($name$arguments
  43.  
  44.     { 
  45.  
  46.         // 实例化一个反射类 static::class 表示调用者 
  47.  
  48.         $reflectionClass = new ReflectionClass(static::class); 
  49.  
  50.         // 这里我们要有一个约定, 就是类常量成员的名字必须的大写。 
  51.  
  52.         // 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法 
  53.  
  54.         // 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。 
  55.  
  56.         $constant = $reflectionClass->getConstant(strtoupper($name)); 
  57.  
  58.         // 获取调用者的 构造方法 
  59.  
  60.         $construct = $reflectionClass->getConstructor(); 
  61.  
  62.         // 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。 
  63.  
  64.         $construct->setAccessible(true); 
  65.  
  66.         // 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。 
  67.  
  68.         $static = new static($constant); 
  69.  
  70.         return $static
  71.  
  72.     } 
  73.  
  74.  
  75.  
  76.     public function __toString() 
  77.  
  78.     { 
  79.  
  80.         return (string)self::$value
  81.  
  82.     } 
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89. class OrderStatus extends Enum 
  90.  
  91.  
  92.     // 默认值 
  93.  
  94.     const __default = self::WAIT_PAYMENT; 
  95.  
  96.     // 待付款 
  97.  
  98.     const WAIT_PAYMENT = 0; 
  99.  
  100.     // 待发货 
  101.  
  102.     const WAIT_SHIP = 1; 
  103.  
  104.     // 待收货 
  105.  
  106.     const WAIT_RECEIPT = 2; 
  107.  
  108.     // 待评价 
  109.  
  110.     const WAIT_COMMENT = 3; 
  111.  
  112.  
  113.  
  114.  
  115.  
  116.  
  117. $WAIT_SHIP = OrderStatus::WAIT_SHIP(); 
  118.  
  119. var_dump($WAIT_SHIP . ''); 
  120.  
  121. var_dump($WAIT_SHIP instanceof Enum); 

到这里 一个简单的枚举类就完成了。

完结

那如果我们还有其他需求、比如 判断一个值是不是在枚举范围内?获取所有的枚举值?获取所有的枚举键,判断枚举键是否有效?自动格式化「因为 __toString 方法只允许返回字符串 ,但是有的时候我们强制需要整形、bool 等类型」

  1. class Enum 
  2.  
  3.  
  4.     const __default = null; 
  5.  
  6.     /** 
  7.  
  8.      * @var string 
  9.  
  10.      */ 
  11.  
  12.     protected static $value
  13.  
  14.     /** 
  15.  
  16.      * @var ReflectionClass 
  17.  
  18.      */ 
  19.  
  20.     protected static $reflectionClass
  21.  
  22.  
  23.  
  24.     // 注意这里 将构造函数的 修饰符改成了 受保护的 即 外部无法直接 new 
  25.  
  26.     protected function __construct($value = null) 
  27.  
  28.     { 
  29.  
  30.         // 很常规 
  31.  
  32.         self::$value = is_null($value) ? static::__default : $value
  33.  
  34.     } 
  35.  
  36.  
  37.  
  38.     /** 
  39.  
  40.      * @param $name 
  41.  
  42.      * @param $arguments 
  43.  
  44.      * @return mixed 
  45.  
  46.      */ 
  47.  
  48.     public static function __callStatic($name$arguments
  49.  
  50.     { 
  51.  
  52.         // 实例化一个反射类 static::class 表示调用者 
  53.  
  54.         $reflectionClass = self::getReflectionClass(); 
  55.  
  56.         // 这里我们要有一个约定, 就是类常量成员的名字必须的大写。 
  57.  
  58.         // 这里就是取出来调用的静态方法名对应的常量值 虽然这里有个 getValue 方法 
  59.  
  60.         // 但是因为其返回值不可靠 我们就依赖于他原本的隐式的 __toString 方法来帮我们输出字符串即可。 
  61.  
  62.         $constant = $reflectionClass->getConstant(strtoupper($name)); 
  63.  
  64.         // 获取调用者的 构造方法 
  65.  
  66.         $construct = $reflectionClass->getConstructor(); 
  67.  
  68.         // 设置成可访问 因为我们把修饰符设置成了受保护的 这里需要访问到,所以就需要设置成可访问的。 
  69.  
  70.         $construct->setAccessible(true); 
  71.  
  72.         // 因为现在类已经是可以访问的了所以我们直接实例化即可,实例化之后 PHP 会自动调用 __toString 方法 使得返回预期的值。 
  73.  
  74.         $static = new static($constant); 
  75.  
  76.         return $static
  77.  
  78.     } 
  79.  
  80.  
  81.  
  82.     /** 
  83.  
  84.      * 实例化一个反射类 
  85.  
  86.      * @return ReflectionClass 
  87.  
  88.      * @throws ReflectionException 
  89.  
  90.      */ 
  91.  
  92.     protected static function getReflectionClass() 
  93.  
  94.     { 
  95.  
  96.         if (!self::$reflectionClass instanceof ReflectionClass) { 
  97.  
  98.             self::$reflectionClass = new ReflectionClass(static::class); 
  99.  
  100.         } 
  101.  
  102.         return self::$reflectionClass
  103.  
  104.     } 
  105.  
  106.  
  107.  
  108.     /** 
  109.  
  110.      * @return string 
  111.  
  112.      */ 
  113.  
  114.     public function __toString() 
  115.  
  116.     { 
  117.  
  118.         return (string)self::$value
  119.  
  120.     } 
  121.  
  122.  
  123.  
  124.     /** 
  125.  
  126.      * 判断一个值是否有效 即是否为枚举成员的值 
  127.  
  128.      * @param $val 
  129.  
  130.      * @return bool 
  131.  
  132.      * @throws ReflectionException 
  133.  
  134.      */ 
  135.  
  136.     public static function isValid($val
  137.  
  138.     { 
  139.  
  140.         return in_array($val, self::toArray()); 
  141.  
  142.     } 
  143.  
  144.  
  145.  
  146.     /** 
  147.  
  148.      * 转换枚举成员为键值对输出 
  149.  
  150.      * @return array 
  151.  
  152.      * @throws ReflectionException 
  153.  
  154.      */ 
  155.  
  156.     public static function toArray() 
  157.  
  158.     { 
  159.  
  160.         return self::getEnumMembers(); 
  161.  
  162.     } 
  163.  
  164.  
  165.  
  166.     /** 
  167.  
  168.      * 获取枚举的常量成员数组 
  169.  
  170.      * @return array 
  171.  
  172.      * @throws ReflectionException 
  173.  
  174.      */ 
  175.  
  176.     public static function getEnumMembers() 
  177.  
  178.     { 
  179.  
  180.         return self::getReflectionClass() 
  181.  
  182.             ->getConstants(); 
  183.  
  184.     } 
  185.  
  186.  
  187.  
  188.     /** 
  189.  
  190.      * 获取枚举成员值数组 
  191.  
  192.      * @return array 
  193.  
  194.      * @throws ReflectionException 
  195.  
  196.      */ 
  197.  
  198.     public static function values() 
  199.  
  200.     { 
  201.  
  202.         return array_values(self::toArray()); 
  203.  
  204.     } 
  205.  
  206.  
  207.  
  208.     /** 
  209.  
  210.      * 获取枚举成员键数组 
  211.  
  212.      * @return array 
  213.  
  214.      * @throws ReflectionException 
  215.  
  216.      */ 
  217.  
  218.     public static function keys() 
  219.  
  220.     { 
  221.  
  222.         return array_keys(self::getEnumMembers()); 
  223.  
  224.     } 
  225.  
  226.  
  227.  
  228.     /** 
  229.  
  230.      * 判断 Key 是否有效 即存在 
  231.  
  232.      * @param $key 
  233.  
  234.      * @return bool 
  235.  
  236.      * @throws ReflectionException 
  237.  
  238.      */ 
  239.  
  240.     public static function isKey($key
  241.  
  242.     { 
  243.  
  244.         return in_array($keyarray_keys(self::getEnumMembers())); 
  245.  
  246.     } 
  247.  
  248.  
  249.  
  250.     /** 
  251.  
  252.      * 根据 Key 去获取枚举成员值 
  253.  
  254.      * @param $key 
  255.  
  256.      * @return static 
  257.  
  258.      */ 
  259.  
  260.     public static function getKey($key
  261.  
  262.     { 
  263.  
  264.         return self::$key(); 
  265.  
  266.     } 
  267.  
  268.  
  269.  
  270.     /** 
  271.  
  272.      * 格式枚举结果类型 
  273.  
  274.      * @param null|bool|int $type 当此处的值时什么类时 格式化输出的即为此类型 
  275.  
  276.      * @return bool|int|string|null 
  277.  
  278.      */ 
  279.  
  280.     public function format($type = null) 
  281.  
  282.     { 
  283.  
  284.         switch (true) { 
  285.  
  286.             // 当为纯数字 或者类型处传入的为 int 值时 转为 int 
  287.  
  288.             case ctype_digit(self::$value) || is_int($type): 
  289.  
  290.                 return (int)self::$value
  291.  
  292.                 break
  293.  
  294.             // 当 type 传入 true 时 返回 bool 类型 
  295.  
  296.             case $type === true: 
  297.  
  298.                 return (bool)filter_var(self::$value, FILTER_VALIDATE_BOOLEAN); 
  299.  
  300.                 break
  301.  
  302.             default
  303.  
  304.                 return self::$value
  305.  
  306.                 break
  307.  
  308.         } 
  309.  
  310.     } 
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317. class OrderStatus extends Enum 
  318.  
  319.  
  320.     // 默认值 
  321.  
  322.     const __default = self::WAIT_PAYMENT; 
  323.  
  324.     // 待付款 
  325.  
  326.     const WAIT_PAYMENT = 0; 
  327.  
  328.     // 待发货 
  329.  
  330.     const WAIT_SHIP = 1; 
  331.  
  332.     // 待收货 
  333.  
  334.     const WAIT_RECEIPT = 2; 
  335.  
  336.     // 待评价 
  337.  
  338.     const WAIT_COMMENT = 3; 
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345. $WAIT_SHIP = OrderStatus::WAIT_SHIP(); 
  346.  
  347. // 直接输出是字符串 
  348.  
  349. echo $WAIT_SHIP
  350.  
  351. // 判断类型是否存在 
  352.  
  353. var_dump($WAIT_SHIP instanceof OrderStatus); 
  354.  
  355. // 格式化输出一下 是要 字符串 、还是 bool 还是整形 
  356.  
  357. // 自动 
  358.  
  359. var_dump($WAIT_SHIP->format()); 
  360.  
  361. // 整形 
  362.  
  363. var_dump($WAIT_SHIP->format(1)); 
  364.  
  365. // bool 
  366.  
  367. var_dump($WAIT_SHIP->format(true)); 
  368.  
  369. // 判断这个值是否有效的枚举值 
  370.  
  371. var_dump(OrderStatus::isValid(2)); 
  372.  
  373. // 判断这个值是否有效的枚举值 
  374.  
  375. var_dump(OrderStatus::isValid(8)); 
  376.  
  377. // 获取所有枚举成员的 Key 
  378.  
  379. var_dump(OrderStatus::keys()); 
  380.  
  381. // 获取所有枚举成员的值 
  382.  
  383. var_dump(OrderStatus::values()); 
  384.  
  385. // 获取枚举成员的键值对 
  386.  
  387. var_dump(OrderStatus::toArray()); 
  388.  
  389. // 判断枚举 Key 是否有效 
  390.  
  391. var_dump(OrderStatus::isKey('WAIT_PAYMENT')); 
  392.  
  393. // 判断枚举 Key 是否有效 
  394.  
  395. var_dump(OrderStatus::isKey('WAIT_PAYMENT_TMP')); 
  396.  
  397. // 根据 Key 取去 值 注意 这里取出来的已经不带有类型了 
  398.  
  399. // 更加建议直接使用 取类常量的方式去取 或者在高版本的 直接使用类常量修饰符  
  400.  
  401. // 将类常量不可见最佳,但是需要额外处理了 
  402.  
  403. var_dump(OrderStatus::getKey('WAIT_PAYMENT'
  404.  
  405.     ->format(1)); 

截至目前 一个完整的枚举就完成了~

Tags: PHP实现枚举

分享到: