当前位置:首页 > 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.  
  13. } 

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

直接手写一个。

开始准备

首先定一个枚举

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

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

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

初具模型

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

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

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

完结

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

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

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

Tags: PHP实现枚举

分享到: