在 數學和 計算機科學理論中,一個集的 枚舉是列出某些有窮序列集的全部成員的程序,或者是一種特定類型對象的計數。這兩種類型常常(但不老是)重疊。枚舉是一個被命名的整型常數的集合,枚舉在平常生活中很常見,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一個枚舉。—— 維基百科php
在上一篇文章中,我談到了 PHP 中的類型約束,此次咱們來談實際應用。git
在實際開發過程當中咱們很是容易接觸到枚舉類型,可是又由於 PHP 原生對枚舉的支持不是太好,因此不少時候 開發人員並無重視枚舉的使用,而是使用全局常量或者類常量代替,而這兩個數據原則上仍是 字符串
並不能用來作類型判斷。segmentfault
等等 ,不少時候咱們都會用簡單的 1/2/3/4 或者0/1 這樣的方式去表明,而後在文檔或者註釋中規定這些東西。數組
更高級一點兒的就是定義成常量,而後方便統一存取,可是常量的值仍是是字符串,沒法進行類型判斷。函數
這裏就要看一下 PHP 對枚舉的支持,雖然 PHP 對枚舉沒有完美的支持,可是在 SPL 中仍是有一個基礎的枚舉類oop
SplEnum extends SplType {/ Constants /this
const NULL __default = NULL ;.net
/ 方法 /code
public getConstList ([ bool
$include_default
= FALSE ] ) : arrayorm/ 繼承的方法 /
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()
__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
方法。
在靜態上下文中調用一個不可訪問方法時,__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));
截至目前 一個完整的枚舉就完成了~
如下內容,編輯於 2019-10-21
本文只是一個實現的思路,若是生產須要使用,建議使用更加成熟的方案,推薦以下。