我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。

常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。

你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?


当前回答

我使用接口而不是类:

interface DaysOfWeek
{
    const Sunday = 0;
    const Monday = 1;
    // etc.
}

var $today = DaysOfWeek::Sunday;

其他回答

我试图用PHP创建一个枚举…这是非常有限的,因为它不支持对象作为枚举值,但仍然有点有用…

class ProtocolsEnum {

    const HTTP = '1';
    const HTTPS = '2';
    const FTP = '3';

    /**
     * Retrieve an enum value
     * @param string $name
     * @return string
     */
    public static function getValueByName($name) {
        return constant('self::'. $name);
    } 

    /**
     * Retrieve an enum key name
     * @param string $code
     * @return string
     */
    public static function getNameByValue($code) {
        foreach(get_class_constants() as $key => $val) {
            if($val == $code) {
                return $key;
            }
        }
    }

    /**
     * Retrieve associate array of all constants (used for creating droplist options)
     * @return multitype:
     */
    public static function toArray() {      
        return array_flip(self::get_class_constants());
    }

    private static function get_class_constants()
    {
        $reflect = new ReflectionClass(__CLASS__);
        return $reflect->getConstants();
    }
}

我知道这是一个旧线程,然而没有一个我所见过的解决方法真的看起来像枚举,因为几乎所有的解决方法都需要你手动分配值给枚举项,或者它需要你传递一个枚举键数组到一个函数。所以我创造了自己的解决方案。

要使用我的解决方案创建枚举类,可以简单地扩展下面的enum类,创建一堆静态变量(不需要初始化它们),并在枚举类定义的下面调用yourEnumClass::init()。

edit: This only works in php >= 5.3, but it can probably be modified to work in older versions as well /** * A base class for enums. * * This class can be used as a base class for enums. * It can be used to create regular enums (incremental indices), but it can also be used to create binary flag values. * To create an enum class you can simply extend this class, and make a call to <yourEnumClass>::init() before you use the enum. * Preferably this call is made directly after the class declaration. * Example usages: * DaysOfTheWeek.class.php * abstract class DaysOfTheWeek extends Enum{ * static $MONDAY = 1; * static $TUESDAY; * static $WEDNESDAY; * static $THURSDAY; * static $FRIDAY; * static $SATURDAY; * static $SUNDAY; * } * DaysOfTheWeek::init(); * * example.php * require_once("DaysOfTheWeek.class.php"); * $today = date('N'); * if ($today == DaysOfTheWeek::$SUNDAY || $today == DaysOfTheWeek::$SATURDAY) * echo "It's weekend!"; * * Flags.class.php * abstract class Flags extends Enum{ * static $FLAG_1; * static $FLAG_2; * static $FLAG_3; * } * Flags::init(Enum::$BINARY_FLAG); * * example2.php * require_once("Flags.class.php"); * $flags = Flags::$FLAG_1 | Flags::$FLAG_2; * if ($flags & Flags::$FLAG_1) * echo "Flag_1 is set"; * * @author Tiddo Langerak */ abstract class Enum{ static $BINARY_FLAG = 1; /** * This function must be called to initialize the enumeration! * * @param bool $flags If the USE_BINARY flag is provided, the enum values will be binary flag values. Default: no flags set. */ public static function init($flags = 0){ //First, we want to get a list of all static properties of the enum class. We'll use the ReflectionClass for this. $enum = get_called_class(); $ref = new ReflectionClass($enum); $items = $ref->getStaticProperties(); //Now we can start assigning values to the items. if ($flags & self::$BINARY_FLAG){ //If we want binary flag values, our first value should be 1. $value = 1; //Now we can set the values for all items. foreach ($items as $key=>$item){ if (!isset($item)){ //If no value is set manually, we should set it. $enum::$$key = $value; //And we need to calculate the new value $value *= 2; } else { //If there was already a value set, we will continue starting from that value, but only if that was a valid binary flag value. //Otherwise, we will just skip this item. if ($key != 0 && ($key & ($key - 1) == 0)) $value = 2 * $item; } } } else { //If we want to use regular indices, we'll start with index 0. $value = 0; //Now we can set the values for all items. foreach ($items as $key=>$item){ if (!isset($item)){ //If no value is set manually, we should set it, and increment the value for the next item. $enum::$$key = $value; $value++; } else { //If a value was already set, we'll continue from that value. $value = $item+1; } } } } }

我已经在这里评论了一些其他的答案,所以我想我也会发表意见。 最后,由于PHP不支持类型化枚举,您可以选择以下两种方式之一:删除类型化枚举,或者接受它们极难有效删除的事实。

我更倾向于接受事实,而不是使用其他答案以某种方式使用的const方法:

abstract class Enum
{

    const NONE = null;

    final private function __construct()
    {
        throw new NotSupportedException(); // 
    }

    final private function __clone()
    {
        throw new NotSupportedException();
    }

    final public static function toArray()
    {
        return (new ReflectionClass(static::class))->getConstants();
    }

    final public static function isValid($value)
    {
        return in_array($value, static::toArray());
    }

}

枚举示例:

final class ResponseStatusCode extends Enum
{

    const OK                         = 200;
    const CREATED                    = 201;
    const ACCEPTED                   = 202;
    // ...
    const SERVICE_UNAVAILABLE        = 503;
    const GATEWAY_TIME_OUT           = 504;
    const HTTP_VERSION_NOT_SUPPORTED = 505;

}

使用Enum作为所有其他枚举扩展的基类,允许使用诸如toArray、isValid等辅助方法。对我来说,类型化的枚举(以及管理它们的实例)最终太混乱了。


假设

如果存在一个__getStatic魔法方法(最好还有一个__equals魔法方法),那么大部分问题都可以通过一种多吨模式来缓解。

(以下是假设;它不会起作用,尽管也许有一天它会)

final class TestEnum
{

    private static $_values = [
        'FOO' => 1,
        'BAR' => 2,
        'QUX' => 3,
    ];
    private static $_instances = [];

    public static function __getStatic($name)
    {
        if (isset(static::$_values[$name]))
        {
            if (empty(static::$_instances[$name]))
            {
                static::$_instances[$name] = new static($name);
            }
            return static::$_instances[$name];
        }
        throw new Exception(sprintf('Invalid enumeration value, "%s"', $name));
    }

    private $_value;

    public function __construct($name)
    {
        $this->_value = static::$_values[$name];
    }

    public function __equals($object)
    {
        if ($object instanceof static)
        {
            return $object->_value === $this->_value;
        }
        return $object === $this->_value;
    }

}

$foo = TestEnum::$FOO; // object(TestEnum)#1 (1) {
                       //   ["_value":"TestEnum":private]=>
                       //   int(1)
                       // }

$zap = TestEnum::$ZAP; // Uncaught exception 'Exception' with message
                       // 'Invalid enumeration member, "ZAP"'

$qux = TestEnum::$QUX;
TestEnum::$QUX == $qux; // true
'hello world!' == $qux; // false

基于此要点,所有枚举的基类:

abstract class Enum {
    protected $val;

    protected function __construct($arg) {
        $this->val = $arg;
    }

    public function __toString() {
        return $this->val;
    }

    public function __set($arg1, $arg2) {
        throw new Exception("enum does not have property");
    }

    public function __get($arg1) {
        throw new Exception("enum does not have property");
    }

    // not really needed
    public function __call($arg1, $arg2) {
        throw new Exception("enum does not have method");
    }

    // not really needed
    static public function __callStatic($arg1, $arg2) {
        throw new Exception("enum does not have static method");
    }
}

你的枚举:

final class MyEnum extends Enum {
    static public function val1() {
        return new self("val1");
    }

    static public function val2() {
        return new self("val2");
    }

    static public function val3() {
        return new self("val3");
    }
}

测试:

$a = MyEnum::val1();
echo "1.the enum value is '$a'\n";

function consumeMyEnum(MyEnum $arg) {
    return "2.the return value is '$arg'\n";
}

echo consumeMyEnum($a);
$version = explode(".", PHP_VERSION);
if ($version[0] >= 7) {
    try {
        echo consumeMyEnum("val1");
    } catch (TypeError $e) {
        echo "3.passing argument error happens (PHP 7.0 and above)\n";
    }
}

echo ($a == MyEnum::val1()) ? "4.same\n" : "4.different\n";
echo ($a == MyEnum::val2()) ? "5.same\n" : "5.different\n";

$b = MyEnum::val1();
echo ($a == $b)  ? "6.same\n" : "6.different\n";
echo ($a === $b) ? "7.same\n" : "7.different\n";

$c = MyEnum::val2();
echo ($a == $c)  ? "8.same\n" : "8.different\n";
echo ($a === $c) ? "9.same\n" : "9.different\n";

switch ($c) {
    case MyEnum::val1(): echo "10.case of 1st\n"; break;
    case MyEnum::val2(): echo "11.case of 2nd\n"; break;
    case MyEnum::val3(): echo "12.case of 3rd\n"; break;
}

try {
    $a->prop = 10;
} catch (Exception $e) {
    echo "13.set property error happens\n";
}

try {
    echo $a->prop;
} catch (Exception $e) {
    echo "14.get property error happens\n";
}

try {
    echo $a->meth();
} catch (Exception $e) {
    echo "15.method call error happens\n";
}

try {
    echo MyEnum::meth();
} catch (Exception $e) {
    echo "16.static method call error happens\n";
}

class Ordinary {}
echo $a instanceof MyEnum   ? "17.MyEnum instance\n"   : "17.not MyEnum instance\n";
echo $a instanceof Enum     ? "18.Enum instance\n"     : "18.not Enum instance\n";
echo $a instanceof Ordinary ? "19.Ordinary instance\n" : "19.not Ordinary instance\n";

在网上试试:沙盒

我已经开始使用下面的方法,因为它使我能够对函数参数具有类型安全,在NetBeans中自动完成和良好的性能。我不太喜欢的一件事是你必须调用[扩展类名]::enumerate();在定义类之后。

abstract class Enum {

    private $_value;

    protected function __construct($value) {
        $this->_value = $value;
    }

    public function __toString() {
        return (string) $this->_value;
    }

    public static function enumerate() {
        $class = get_called_class();
        $ref = new ReflectionClass($class);
        $statics = $ref->getStaticProperties();
        foreach ($statics as $name => $value) {
            $ref->setStaticPropertyValue($name, new $class($value));
        }
    }
}

class DaysOfWeek extends Enum {
    public static $MONDAY = 0;
    public static $SUNDAY = 1;
    // etc.
}
DaysOfWeek::enumerate();

function isMonday(DaysOfWeek $d) {
    if ($d == DaysOfWeek::$MONDAY) {
        return true;
    } else {
        return false;
    }
}

$day = DaysOfWeek::$MONDAY;
echo (isMonday($day) ? "bummer it's monday" : "Yay! it's not monday");