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

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

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


当前回答

类常量呢?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

    public function echoConstant()
    {
        echo self::SOME_CONSTANT;
    }
}

echo YourClass::SOME_CONSTANT;

$c = new YourClass;
$c->echoConstant();

其他回答

编辑:从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中枚举最常见的解决方案是创建一个通用枚举类,然后扩展它。你可以看看这个。

更新:或者,我从phpclasses.org找到了这个。

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

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

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];
}

下面是一个github库,用于在php中处理类型安全的枚举:

这个库处理类生成、类缓存,并实现了Type Safe Enumeration设计模式,使用几个辅助方法来处理枚举,比如为枚举排序检索序号,或为枚举组合检索二进制值。

生成的代码使用一个普通的旧php模板文件,该文件也是可配置的,因此您可以提供自己的模板。

它是由phpunit覆盖的完整测试。

Php-enums在github (feel free to fork)

用法:(@参见Usage .php或单元测试了解更多细节)

<?php
//require the library
require_once __DIR__ . '/src/Enum.func.php';

//if you don't have a cache directory, create one
@mkdir(__DIR__ . '/cache');
EnumGenerator::setDefaultCachedClassesDir(__DIR__ . '/cache');

//Class definition is evaluated on the fly:
Enum('FruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'));

//Class definition is cached in the cache directory for later usage:
Enum('CachedFruitsEnum', array('apple' , 'orange' , 'rasberry' , 'bannana'), '\my\company\name\space', true);

echo 'FruitsEnum::APPLE() == FruitsEnum::APPLE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::APPLE()) . "\n";

echo 'FruitsEnum::APPLE() == FruitsEnum::ORANGE(): ';
var_dump(FruitsEnum::APPLE() == FruitsEnum::ORANGE()) . "\n";

echo 'FruitsEnum::APPLE() instanceof Enum: ';
var_dump(FruitsEnum::APPLE() instanceof Enum) . "\n";

echo 'FruitsEnum::APPLE() instanceof FruitsEnum: ';
var_dump(FruitsEnum::APPLE() instanceof FruitsEnum) . "\n";

echo "->getName()\n";
foreach (FruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getName() . "\n";
}

echo "->getValue()\n";
foreach (FruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getValue() . "\n";
}

echo "->getOrdinal()\n";
foreach (CachedFruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getOrdinal() . "\n";
}

echo "->getBinary()\n";
foreach (CachedFruitsEnum::iterator() as $enum)
{
  echo "  " . $enum->getBinary() . "\n";
}

输出:

FruitsEnum::APPLE() == FruitsEnum::APPLE(): bool(true)
FruitsEnum::APPLE() == FruitsEnum::ORANGE(): bool(false)
FruitsEnum::APPLE() instanceof Enum: bool(true)
FruitsEnum::APPLE() instanceof FruitsEnum: bool(true)
->getName()
  APPLE
  ORANGE
  RASBERRY
  BANNANA
->getValue()
  apple
  orange
  rasberry
  bannana
->getValue() when values have been specified
  pig
  dog
  cat
  bird
->getOrdinal()
  1
  2
  3
  4
->getBinary()
  1
  2
  4
  8

我试图用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();
    }
}