文章轉發自專業的Laravel開發者社區,原始連接: https://learnku.com/laravel/t...本文旨在提供一些更好的理解什麼是枚舉,何時使用它們以及如何在php中使用它們.php
咱們在某些時候使用了常量來定義代碼中的一些常數值.他們被用來避免魔法值.用一個象徵性的名字代替一些魔法值,咱們能夠給它一些意義.而後咱們在代碼中引用這個符號名稱.由於咱們定義了一次並使用了不少次,因此搜索它並稍後重命名或更改一個值會更容易.html
這就是爲何看到相似於下面的代碼並不罕見.java
<?php class User { const GENDER_MALE = 0; const GENDER_FEMALE = 1; const STATUS_INACTIVE = 0; const STATUS_ACTIVE = 1; }
以上常量表示了兩組屬性,GEDNER_* 和 STATUS_*。他們表示一組性別和一組用戶狀態。每一組都是一個枚舉 。枚舉是一組元素(也叫作成員)的集合,每個枚舉都定義了一種新類型。這個類型,和它的值同樣,能夠包含任意屬於該枚舉的元素。laravel
在上面的例子中,枚舉藉助於常量,每個常量的值都是一個成員。注意,這樣作的話,咱們只能在常量包含的類型中取值。所以,咱們在寫這些值的時候不會有類型提示,不知道詳細的枚舉類型。git
來看一個簡短的例子, 但咱們假定例子中有更多的代碼github
<?php interface UserFactory { public function create( string $email, int $gender, int $status ): User; } $factory->create( $email, User::STATUS_ACTIVE, User::GENDER_FEMALE );
第一眼看上去代碼很好,可是他只是碰巧正確運行了!由於兩個不一樣的枚舉成員其實是同一個值,調用create方法成功,是由於這最後兩個參數被互換了不影響結果。儘管咱們檢查方法接受的值是否有效,運行界面也不會警告咱們,測試也會經過。有人能正確的發現這些bug,可是它也極可能被忽視掉。以後一些狀況,好比合並衝突的時候,若是它的值改變了,它可能會引發系統異常。api
若是使用標量類型,咱們會受限於這種類型,沒法辨別這兩個值是是否是屬於兩個不一樣的枚舉。數組
另外一個問題是這個代碼描述的的不是很好。想象一下 create
方法沒有引用常量。$gender
被別人看做爲一個枚舉元素將是有多麼困難?看這些元素在哪裏被定義又有多麼困難?咱們以後將會閱讀那些代碼,所以咱們應該儘量是讓代碼易於閱讀以及和經過。oracle
咱們能夠作得更好嗎? Sure! 這個方法就是是使用類實例做爲枚舉元素,類自己定義了一個新的類型。 直到PHP 7,咱們能夠安裝 SPL類 PECL擴展而且使用SplEnum 。ide
<?php class YesNo extends \SplEnum { const __default = self::YES; const NO = 0; const YES = 1; } $no = new YesNo(YesNo::NO); var_dump($no == YesNo::NO); //true var_dump(new YesNo(YesNo::NO) == YesNo::NO); //true
咱們擴展 SplEnum
而且定義用於建立枚舉元素的常量。枚舉元素是咱們手動構造的對象,在這種狀況下是常量值自己。 咱們能夠將整型與對象進行比較,這可能很奇怪。 另外,正如文檔所述,這是一個仿真的枚舉。 PHP自己並不支持枚舉類型,因此咱們在這裏探討的全部內容都是仿真的。
咱們用這種方法獲得了什麼? 咱們能夠輸入提示咱們的參數,並讓PHP引擎在發生錯誤時提醒咱們。 咱們還能夠在枚舉類中包含一些邏輯,並使用switch
語句來模擬多態行爲。
但也有一些缺點. 例如, 在大多數狀況下, 有些你能夠用枚舉元素而不能用標識檢查. 這不是不可能的,咱們不得不很是當心. 因爲咱們手動建立枚舉成員, 因此許多成員應該是同一個成員, 但這一點手動很難肯定.
利用 SplEnum
咱們解決枚舉類型問題, 可是當咱們用標識檢查的時候不得不很是當心. 咱們須要一個方法限制能夠建立的多個元素, 例如 multiton (multiple singleton objects).
如今咱們將看到由 Java Enum 啓發並實現 multiton
的兩個不一樣的庫.
第一個是 eloquent/enumeration. 它爲每一個元素建立一個定義類的實例. 請注意, 沒有咱們的幫助, 枚舉的用戶仿真永遠不能保證一個枚舉實例, 由於咱們限制它的每一步都有一個方法去避免.
這個庫可讓咱們用錯誤的方式去嘗試, 例如用反射建立一個實例, 在這一點上咱們能夠問咱們本身是否作了正確的事. 它也能夠在代碼的評審過程當中有所幫助,由於這樣的實現能夠定義幾個應該被遵循的規則. 若是這些規則比較簡單很容易發現代碼中存在的問題.
讓咱們看些實例.
<?php final class YesNo extends \Eloquent\Enumeration\AbstractEnumeration { const NO = 0; const YES = 1; } var_dump(YesNo::YES()->key()); // YES
咱們定義了一個繼承 \Eloquent\Enumeration\AbstractEnumeration
的新類 YesNo
. 接下來咱們定義一個定義元素名和建立表現這些元素的對象的庫的常量.
還有一些狀況咱們須要謹記,用 serialize
/deserialize
在其中建立自定義對象 .
咱們能夠在GitHub頁面上找到更多的例子和很完善的文檔。
咱們要展現的第二個庫是 zlikavac32/php-enum. 與 eloquent/enumeration
不一樣,這個庫面向容許真正的多態行爲的抽象類。 因此,咱們能夠用每一個方法都定義一個枚舉元素來實現,而不是使用switch
的方法。 經過嚴格的規則來定義枚舉,也能夠至關可靠地確保每一個元素只有一個實例。
這個庫面向抽象類,以便將每一個成員的許多實例限制爲一個。 這個想法是,每一個枚舉必須被定義爲抽象的,並枚舉它的元素。 請注意,你能夠經過擴展類,而後構造一個元素來濫用,可是若是你這麼用了,這些是會在代碼審查過程當中標紅的。
對於抽象類,咱們知道咱們不會意外地有一個枚舉的新元素,由於它須要具體的實現。 經過遵循在enum自己中保持這些具體實現的規則,咱們能夠很容易地發現濫用。 匿名類 在這裏頗有用。
庫強制抽象枚舉類,但不能強制建立有效的元素。 這是這個庫的用戶的責任。 圖書館照顧其他的。
讓咱們看一個簡單的例子。
<?php /** * @method static YesNo YES * @method static YesNo NO */ abstract class YesNo extends \Zlikavac32\Enum\Enum { protected static function enumerate(): array { return [ 'YES', 'NO' ]; } } var_dump(YesNo::YES()->name()); // YES
PHPDoc註釋定義了返回枚舉元素的現有靜態方法。 這有助於搜索和重構代碼。 接下來,咱們將枚舉YesNo
定義爲抽象,並擴展\Zlikavac32\Enum\Enum
並定義一個靜態方法enumerate
。 而後,在enumerate
方法中,咱們列出將被用來表示它們的元素名稱。
剛剛咱們提到了多態行爲,那麼爲何咱們會使用它呢? 當咱們試圖限制同一個枚舉元素的多個實例時會發生一件事,那就是咱們不能有循環引用。 讓咱們想象一下,咱們想擁有由NORTH
,SOUTH
,EAST
和WEST
組成的WorldSide
枚舉。 咱們還想有一個方法opposite():WorldSide
,它返回表明相反的元素。
若是咱們試圖經過構造函數注入相反元素,在某一時刻,咱們得到一個循環引用,這意味着,咱們須要相同元素的第二個實例。 爲了返回一個有效的相反世界,咱們不得不用一個代理對象 或者switch
語句破解。
隨着多態行爲,咱們能作的就是讓咱們看到咱們可定義咱們須要的WorldSide
枚舉。
<?php /** * @method static WorldSide NORTH * @method static WorldSide SOUTH * @method static WorldSide EAST * @method static WorldSide WEST */ abstract class WorldSide extends \Zlikavac32\Enum\Enum { protected static function enumerate(): array { return [ 'NORTH' => new class extends WorldSide { public function opposite(): WorldSide { return WorldSide::SOUTH(); } }, 'SOUTH' => new class extends WorldSide { public function opposite(): WorldSide { return WorldSide::NORTH(); } }, 'EAST' => new class extends WorldSide { public function opposite(): WorldSide { return WorldSide::WEST(); } }, 'WEST' => new class extends WorldSide { public function opposite(): WorldSide { return WorldSide::EAST(); } } ]; } abstract public function opposite(): WorldSide; } foreach (WorldSide::iterator() as $worldSide) { var_dump(sprintf( 'Opposite of %s is %s', (string) $worldSide, (string) $worldSide->opposite() )); }
在enumerate
方法,咱們提供了每個枚舉元素的實現。數組是用枚舉元素名稱來索引的。當手動的建立元素,咱們定義咱們元素名稱做爲數據的鍵。
咱們能夠用 WorldSide::iterator()
獲取枚舉元素的順序迭代器,來定義和遍歷他們。 每個枚舉元素都有一個默認的 __toString(): string
實現返回元素的名稱。
每一個枚舉元素返回其相反的元素。
回顧一下,常量不是枚舉,枚舉不是常量。每一個枚舉定義一個類型。若是咱們有一些常數的值對咱們很重要,但名字沒有,咱們應該堅持常數。若是咱們有一些常量的價值對咱們可有可無,可是與同一羣體中的其餘全部人有所不一樣則是重要的,請使用枚舉
枚舉爲代碼提供了更多的上下文,也能夠將某些檢查委託給引擎自己。若是PHP有一個本地的枚舉支持,這將是很是好的。語法更改可使代碼更具可讀性。引擎能夠爲咱們執行檢查,並執行一些不能從用戶區執行的規則。
你如何使用枚舉,你對這個主題有什麼想法?請在下方評論