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

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

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


当前回答

四年后,我又遇到了这个。我目前的方法是这样的,因为它允许在IDE中完成代码以及类型安全:

基类:

abstract class TypedEnum
{
    private static $_instancedValues;

    private $_value;
    private $_name;

    private function __construct($value, $name)
    {
        $this->_value = $value;
        $this->_name = $name;
    }

    private static function _fromGetter($getter, $value)
    {
        $reflectionClass = new ReflectionClass(get_called_class());
        $methods = $reflectionClass->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);    
        $className = get_called_class();

        foreach($methods as $method)
        {
            if ($method->class === $className)
            {
                $enumItem = $method->invoke(null);

                if ($enumItem instanceof $className && $enumItem->$getter() === $value)
                {
                    return $enumItem;
                }
            }
        }

        throw new OutOfRangeException();
    }

    protected static function _create($value)
    {
        if (self::$_instancedValues === null)
        {
            self::$_instancedValues = array();
        }

        $className = get_called_class();

        if (!isset(self::$_instancedValues[$className]))
        {
            self::$_instancedValues[$className] = array();
        }

        if (!isset(self::$_instancedValues[$className][$value]))
        {
            $debugTrace = debug_backtrace();
            $lastCaller = array_shift($debugTrace);

            while ($lastCaller['class'] !== $className && count($debugTrace) > 0)
            {
                $lastCaller = array_shift($debugTrace);
            }

            self::$_instancedValues[$className][$value] = new static($value, $lastCaller['function']);
        }

        return self::$_instancedValues[$className][$value];
    }

    public static function fromValue($value)
    {
        return self::_fromGetter('getValue', $value);
    }

    public static function fromName($value)
    {
        return self::_fromGetter('getName', $value);
    }

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

    public function getName()
    {
        return $this->_name;
    }
}

枚举例子:

final class DaysOfWeek extends TypedEnum
{
    public static function Sunday() { return self::_create(0); }    
    public static function Monday() { return self::_create(1); }
    public static function Tuesday() { return self::_create(2); }   
    public static function Wednesday() { return self::_create(3); }
    public static function Thursday() { return self::_create(4); }  
    public static function Friday() { return self::_create(5); }
    public static function Saturday() { return self::_create(6); }      
}

使用示例:

function saveEvent(DaysOfWeek $weekDay, $comment)
{
    // store week day numeric value and comment:
    $myDatabase->save('myeventtable', 
       array('weekday_id' => $weekDay->getValue()),
       array('comment' => $comment));
}

// call the function, note: DaysOfWeek::Monday() returns an object of type DaysOfWeek
saveEvent(DaysOfWeek::Monday(), 'some comment');

注意,同一个枚举条目的所有实例都是相同的:

$monday1 = DaysOfWeek::Monday();
$monday2 = DaysOfWeek::Monday();
$monday1 === $monday2; // true

你也可以在switch语句中使用它:

function getGermanWeekDayName(DaysOfWeek $weekDay)
{
    switch ($weekDay)
    {
        case DaysOfWeek::Monday(): return 'Montag';
        case DaysOfWeek::Tuesday(): return 'Dienstag';
        // ...
}

你也可以通过名称或值创建枚举项:

$monday = DaysOfWeek::fromValue(2);
$tuesday = DaysOfWeek::fromName('Tuesday');

或者你可以从一个现有的枚举条目中获取名称(即函数名):

$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday

其他回答

我使用接口而不是类:

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

var $today = DaysOfWeek::Sunday;

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

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";

在网上试试:沙盒

最后,PHP 7.1+给出了一个不能被重写的常量。

/**
 * An interface that groups HTTP Accept: header Media Types in one place.
 */
interface MediaTypes
{
    /**
    * Now, if you have to use these same constants with another class, you can
    * without creating funky inheritance / is-a relationships.
    * Also, this gets around the single inheritance limitation.
    */

    public const HTML = 'text/html';
    public const JSON = 'application/json';
    public const XML = 'application/xml';
    public const TEXT = 'text/plain';
}

/**
 * An generic request class.
 */
abstract class Request
{
    // Why not put the constants here?
    // 1) The logical reuse issue.
    // 2) Single Inheritance.
    // 3) Overriding is possible.

    // Why put class constants here?
    // 1) The constant value will not be necessary in other class families.
}

/**
 * An incoming / server-side HTTP request class.
 */
class HttpRequest extends Request implements MediaTypes
{
    // This class can implement groups of constants as necessary.
}

如果您使用的是名称空间,那么代码补全应该可以工作。

但是,这样做将失去在类族(protected)或单独在类(private)中隐藏常量的能力。根据定义,接口中的所有内容都是公共的。

PHP手册:接口

更新:

PHP 8.1现在有了枚举。

我也喜欢java的枚举,因此我这样写我的枚举,我认为这是最类似的行为像在java的枚举,当然,如果有人想使用更多的方法从java应该写在这里,或在抽象类,但核心思想是嵌入在下面的代码


class FruitsEnum {

    static $APPLE = null;
    static $ORANGE = null;

    private $value = null;

    public static $map;

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

    public static function init () {
        self::$APPLE  = new FruitsEnum("Apple");
        self::$ORANGE = new FruitsEnum("Orange");
        //static map to get object by name - example Enum::get("INIT") - returns Enum::$INIT object;
        self::$map = array (
            "Apple" => self::$APPLE,
            "Orange" => self::$ORANGE
        );
    }

    public static function get($element) {
        if($element == null)
            return null;
        return self::$map[$element];
    }

    public function getValue() {
        return $this->value;
    }

    public function equals(FruitsEnum $element) {
        return $element->getValue() == $this->getValue();
    }

    public function __toString () {
        return $this->value;
    }
}
FruitsEnum::init();

var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$APPLE)); //true
var_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$ORANGE)); //false
var_dump(FruitsEnum::$APPLE instanceof FruitsEnum); //true
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::$APPLE)); //true - enum from string
var_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::get("Orange"))); //false

我已经开始使用下面的方法,因为它使我能够对函数参数具有类型安全,在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");