PHP ENUM 偽實現

Raiven Kao2020-01-15develop/2020php

Enumerations 又可簡稱為 Enum ,在眾多語言中都可以讓程式碼更高效簡潔,例如我們可以在 python 中這麼宣告,並且用 requests 模組打一個 request,即可透過 Enum 來模組化 Response

import enum
import request
class HTTPResponsecode(enum.Enum)
    OK = 200
    BAD_REQUEST = 400
    NOT_FOUND = 404

r = requests.get('https://omegaatt.com/')
print(r.status_code == HTTPResponsecode.OK)
# True

但是在 PHP 中原生是沒有內建 Enum 的,必須安裝 perlSplEnum open in new window 類套件庫,方能直接使用 Enum 的功能。或是可以透過以下的方式達到接近的效果。

極簡易版 Enum

首先,建立一個 Enum 類,一樣舉例為 HTTP 的 response code

abstract class HTTPResponsecodeEnum
{
    const OK = 200;
    const BAD_REQUEST = 400;
    const NOT_FOUND = 404;
}

這樣就完成最基本的 Enum 拉,只要透過簡單的 if 判斷便能輕鬆享受打包的方便

if($status_code == HTTPResponsecodeEnum::OK){
    // foo();
}

具有驗證功能的 Enum

根據防禦性程式寫法,我們知道千萬不要相信使用者傳過來的東西,必須去驗證資料是否正確時,極簡版本已經無法勝任。這時候可以使用 ReflectionClassopen in new window 這個這個類別協助,由這個類來取得 Class 中的常數。

$oClass = new \ReflectionClass(__CLASS__);
$constants = $oClass->getConstants();

如此一來便可以得到該 Class 中的常數清單,試著組合一下。此時的 constants 使用 lazy loading,不像 Singleton 需要注意多線程故可以直接使用。

abstract class HTTPResponsecodeEnum
{
    const OK = 200;
    const BAD_REQUEST = 400;
    const NOT_FOUND = 404;

    public static function getConst(): array
    {
        static $constants;
        if (!isset($constants)) {
            $oClass = new \ReflectionClass(__CLASS__);
            $constants = $oClass->getConstants();
        }
        return $constants;
    }
}

$code = 9527;
if (in_array($code, HTTPResponsecodeEnum::getConst())) {
    // bar();
}

封裝成抽象類 Enum

但若是每個 Enum Class 都寫一個 getConst 方法似乎是太累贅了,PHP 好說歹說也是個可以寫的「很 OOP」的語言,於是乎可以把 getConst 與一些 function 進行整合。

abstract class BasicEnum {
    private static function getConst() {
        static $constantsArray;
        if (!isset($constants)) {
            self::$constantsArray = [];
        }
        $oClass = get_called_class();
        if (!array_key_exists($oClass, self::$constantsArray)) {
            $reflect = new ReflectionClass($oClass);
            self::$constantsArray[$oClass] = $reflect->getConstants();
        }
        return self::$constantsArray[$oClass];
    }

    public static function isValidName($name, $strict = false) {
        $constants = self::getConst();

        if ($strict) {
            return array_key_exists($name, $constants);
        }

        $keys = array_map('strtolower', array_keys($constants));
        return in_array(strtolower($name), $keys);
    }

    public static function isValidValue($value, $strict = true) {
        $values = array_values(self::getConst());
        return in_array($value, $values, $strict);
    }
}

以上改動主要是參照 stackoverflow 上的這篇open in new window,然而針對 getConst 是否要公開或私有這個取決於需要,透過 isValidNameisValidValue 已經能夠辦到了就不用再自造輪子了。未來有需要用到 Enum 類時,僅需繼承這個類,並如極簡版般僅需宣告常數便可以正常使用。

abstract class HTTPResponsecodeEnum extends BasicEnum
{
    const OK = 200;
    const BAD_REQUEST = 400;
    const NOT_FOUND = 404;
}

HTTPResponsecodeEnum::isValidName('OK');    // true
HTTPResponsecodeEnum::isValidName('NO_OK'); // false
HTTPResponsecodeEnum::isValidValue(404);    // true 
HTTPResponsecodeEnum::isValidValue(403);    //false

參考資料

Last Updated 2/3/2023, 3:19:35 PM