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

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

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


当前回答

上面的答案太棒了。但是,如果以两种不同的方式进行扩展,那么无论先进行哪种扩展,都会导致对函数的调用,从而创建缓存。这个缓存将被所有后续调用使用,无论调用是由哪个扩展发起的…

要解决这个问题,将变量和第一个函数替换为:

private static $constCacheArray = null;

private static function getConstants() {
    if (self::$constCacheArray === null) self::$constCacheArray = array();

    $calledClass = get_called_class();
    if (!array_key_exists($calledClass, self::$constCacheArray)) {
        $reflect = new \ReflectionClass($calledClass);
        self::$constCacheArray[$calledClass] = $reflect->getConstants();
    }

    return self::$constCacheArray[$calledClass];
}

其他回答

这里有一些很好的解决方案!

这是我的版本。

它是强类型的 它与IDE自动补全一起工作 枚举由代码和描述定义,其中代码可以是整数、二进制值、短字符串或基本上任何您想要的内容。可以很容易地扩展该模式以支持其他属性。 它支持值(==)和引用(===)比较,并在switch语句中工作。

我认为主要的缺点是枚举成员必须分别声明和实例化,这是由于描述和PHP不能在静态成员声明时构造对象。我想绕过这个问题的一种方法可能是使用带有解析过的文档注释的反射。

抽象枚举看起来像这样:

<?php

abstract class AbstractEnum
{
    /** @var array cache of all enum instances by class name and integer value */
    private static $allEnumMembers = array();

    /** @var mixed */
    private $code;

    /** @var string */
    private $description;

    /**
     * Return an enum instance of the concrete type on which this static method is called, assuming an instance
     * exists for the passed in value.  Otherwise an exception is thrown.
     *
     * @param $code
     * @return AbstractEnum
     * @throws Exception
     */
    public static function getByCode($code)
    {
        $concreteMembers = &self::getConcreteMembers();

        if (array_key_exists($code, $concreteMembers)) {
            return $concreteMembers[$code];
        }

        throw new Exception("Value '$code' does not exist for enum '".get_called_class()."'");
    }

    public static function getAllMembers()
    {
        return self::getConcreteMembers();
    }

    /**
     * Create, cache and return an instance of the concrete enum type for the supplied primitive value.
     *
     * @param mixed $code code to uniquely identify this enum
     * @param string $description
     * @throws Exception
     * @return AbstractEnum
     */
    protected static function enum($code, $description)
    {
        $concreteMembers = &self::getConcreteMembers();

        if (array_key_exists($code, $concreteMembers)) {
            throw new Exception("Value '$code' has already been added to enum '".get_called_class()."'");
        }

        $concreteMembers[$code] = $concreteEnumInstance = new static($code, $description);

        return $concreteEnumInstance;
    }

    /**
     * @return AbstractEnum[]
     */
    private static function &getConcreteMembers() {
        $thisClassName = get_called_class();

        if (!array_key_exists($thisClassName, self::$allEnumMembers)) {
            $concreteMembers = array();
            self::$allEnumMembers[$thisClassName] = $concreteMembers;
        }

        return self::$allEnumMembers[$thisClassName];
    }

    private function __construct($code, $description)
    {
        $this->code = $code;
        $this->description = $description;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function getDescription()
    {
        return $this->description;
    }
}

下面是一个具体枚举示例:

<?php

require('AbstractEnum.php');

class EMyEnum extends AbstractEnum
{
    /** @var EMyEnum */
    public static $MY_FIRST_VALUE;
    /** @var EMyEnum */
    public static $MY_SECOND_VALUE;
    /** @var EMyEnum */
    public static $MY_THIRD_VALUE;

    public static function _init()
    {
        self::$MY_FIRST_VALUE = self::enum(1, 'My first value');
        self::$MY_SECOND_VALUE = self::enum(2, 'My second value');
        self::$MY_THIRD_VALUE = self::enum(3, 'My third value');
    }
}

EMyEnum::_init();

可以这样使用:

<?php

require('EMyEnum.php');

echo EMyEnum::$MY_FIRST_VALUE->getCode().' : '.EMyEnum::$MY_FIRST_VALUE->getDescription().PHP_EOL.PHP_EOL;

var_dump(EMyEnum::getAllMembers());

echo PHP_EOL.EMyEnum::getByCode(2)->getDescription().PHP_EOL;

并产生如下输出:

1 : My first value array(3) { [1]=> object(EMyEnum)#1 (2) { ["code":"AbstractEnum":private]=> int(1) ["description":"AbstractEnum":private]=> string(14) "My first value" } [2]=> object(EMyEnum)#2 (2) { ["code":"AbstractEnum":private]=> int(2) ["description":"AbstractEnum":private]=> string(15) "My second value" } [3]=> object(EMyEnum)#3 (2) { ["code":"AbstractEnum":private]=> int(3) ["description":"AbstractEnum":private]=> string(14) "My third value" } } My second value

上面的答案太棒了。但是,如果以两种不同的方式进行扩展,那么无论先进行哪种扩展,都会导致对函数的调用,从而创建缓存。这个缓存将被所有后续调用使用,无论调用是由哪个扩展发起的…

要解决这个问题,将变量和第一个函数替换为:

private static $constCacheArray = null;

private static function getConstants() {
    if (self::$constCacheArray === null) self::$constCacheArray = array();

    $calledClass = get_called_class();
    if (!array_key_exists($calledClass, self::$constCacheArray)) {
        $reflect = new \ReflectionClass($calledClass);
        self::$constCacheArray[$calledClass] = $reflect->getConstants();
    }

    return self::$constCacheArray[$calledClass];
}

编辑:从PHP 8.1开始,支持枚举:https://www.php.net/manual/en/language.types.enumerations.php

根据用例,我通常会使用如下简单的代码:

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

$today = DaysOfWeek::Sunday;

然而,其他用例可能需要对常量和值进行更多的验证。根据下面关于反射的评论和其他一些注意事项,下面是一个扩展的示例,它可能更好地适用于更广泛的情况:

abstract class BasicEnum {
    private static $constCacheArray = NULL;

    private static function getConstants() {
        if (self::$constCacheArray == NULL) {
            self::$constCacheArray = [];
        }
        $calledClass = get_called_class();
        if (!array_key_exists($calledClass, self::$constCacheArray)) {
            $reflect = new ReflectionClass($calledClass);
            self::$constCacheArray[$calledClass] = $reflect->getConstants();
        }
        return self::$constCacheArray[$calledClass];
    }

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

        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::getConstants());
        return in_array($value, $values, $strict);
    }
}

通过创建一个扩展BasicEnum的简单枚举类,你现在可以使用方法进行简单的输入验证:

abstract class DaysOfWeek extends BasicEnum {
    const Sunday = 0;
    const Monday = 1;
    const Tuesday = 2;
    const Wednesday = 3;
    const Thursday = 4;
    const Friday = 5;
    const Saturday = 6;
}

DaysOfWeek::isValidName('Humpday');                  // false
DaysOfWeek::isValidName('Monday');                   // true
DaysOfWeek::isValidName('monday');                   // true
DaysOfWeek::isValidName('monday', $strict = true);   // false
DaysOfWeek::isValidName(0);                          // false

DaysOfWeek::isValidValue(0);                         // true
DaysOfWeek::isValidValue(5);                         // true
DaysOfWeek::isValidValue(7);                         // false
DaysOfWeek::isValidValue('Friday');                  // false

作为旁注,任何时候我在一个静态/const类上至少使用一次反射,其中数据不会改变(例如在enum中),我都会缓存那些反射调用的结果,因为每次使用新的反射对象最终都会有明显的性能影响(存储在多个enum的关联数组中)。

现在大多数人已经升级到至少5.3版本,而且也有了脾脏,这当然也是一个可行的选择——只要你不介意在你的代码库中使用实际枚举实例化这种传统的不直观的概念。在上面的例子中,BasicEnum和DaysOfWeek根本不能被实例化,也不应该被实例化。

我只是创建了一个库,希望它能完成这项工作。它可以在任何PHP项目中独立使用,并且有一些Laravel的好东西可以让生活变得更简单。我在生产项目中使用它们。

https://github.com/Kwaadpepper/enum

如果你喜欢或不喜欢,不要犹豫提供反馈。 它可以被打印出来,并序列化为JSON。它的定义很简单。

用法非常简单:

$enum = BasicEnum::someValue();
echo $enum->equals(BasicEnum::someValue()) ? 'true' : 'false'; // true
echo $enum->value; // 'someValue' or the value you have defined
echo $enum->label; // 'someValue' or the label you have defined
echo $enum; // 'someValue' or the value you have defined
echo json_encode($enum); // {"label": "someValue", "value: "someValue" }

枚举定义非常简单(值和标签方法是可选的)

/**
 * @method static self post()
 * @method static self about()
 * @method static self contact()
 */
class PostType extends BaseEnum
{
    protected static function values(): array
    {
        return [
            'post' => 0,
            'about' => 1,
            'contact' => 2
        ];
    }

    protected static function labels(): array
    {
        return [
            'post' => 'Regular posts',
            'about' => 'The about page',
            'contact' => 'The contact page'
        ];
    }
}

另一种方法是使用神奇的__set方法并将枚举设为私有。

e.g.

class Human{
    private $gender;

    public function __set($key, $value){
        if($key == 'day' && !in_array($value, array('Man', 'Woman')){
            new Exception('Wrong value for '.__CLASS__.'->'.$key);
        }
        else{
            $this->$key = $value;
        }
        ...
    }
}

每当类本身之外的代码试图设置类属性时,就会调用这个神奇的方法。 这是从PHP5 - 8工作。