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

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

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


当前回答

这里的其他一些答案中缺少的一个方面是使用带有类型提示的枚举的方法。

如果您将枚举定义为抽象类中的一组常量,例如。

abstract class ShirtSize {
    public const SMALL = 1;
    public const MEDIUM = 2;
    public const LARGE = 3;
}

那么你就不能在函数参数中输入提示,一方面是因为它是不可实例化的,另一方面是因为ShirtSize::SMALL的类型是int,而不是ShirtSize。

这就是为什么PHP中的本地枚举比我们能想到的任何东西都要好得多。但是,我们可以通过保留一个表示枚举值的私有属性来近似枚举,然后将该属性的初始化限制为预定义的常量。为了防止枚举被任意实例化(没有白名单类型检查的开销),我们将构造函数设为private。

class ShirtSize {
    private $size;
    private function __construct ($size) {
        $this->size = $size;
    }
    public function equals (ShirtSize $s) {
        return $this->size === $s->size;
    }
    public static function SMALL () { return new self(1); }
    public static function MEDIUM () { return new self(2); }
    public static function LARGE () { return new self(3); }
}

然后我们可以像这样使用ShirtSize:

function sizeIsAvailable ($productId, ShirtSize $size) {
    // business magic
}
if(sizeIsAvailable($_GET["id"], ShirtSize::LARGE())) {
    echo "Available";
} else {
    echo "Out of stock.";
}
$s2 = ShirtSize::SMALL();
$s3 = ShirtSize::MEDIUM();
echo $s2->equals($s3) ? "SMALL == MEDIUM" : "SMALL != MEDIUM";

这样,从用户的角度来看,最大的区别是必须在常量的名称上附加一个()。

一个缺点是===(比较对象是否相等)将在==返回true时返回false。出于这个原因,最好提供一个equals方法,这样用户就不必记得使用==和not ===来比较两个enum值。

编辑:现有的几个答案非常相似,特别是:https://stackoverflow.com/a/25526473/2407870。

其他回答

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

要使用我的解决方案创建枚举类,可以简单地扩展下面的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; } } } } }

这可能很简单

enum DaysOfWeek {
    Sunday,
    Monday,
    // ...
}

在未来。

枚举类型

如果您需要使用全局唯一的enum(即即使在不同enum之间比较元素时)并且易于使用,请随意使用以下代码。我还添加了一些我认为有用的方法。您可以在代码顶部的注释中找到示例。

<?php

/**
 * Class Enum
 * 
 * @author Christopher Fox <christopher.fox@gmx.de>
 *
 * @version 1.0
 *
 * This class provides the function of an enumeration.
 * The values of Enum elements are unique (even between different Enums)
 * as you would expect them to be.
 *
 * Constructing a new Enum:
 * ========================
 *
 * In the following example we construct an enum called "UserState"
 * with the elements "inactive", "active", "banned" and "deleted".
 * 
 * <code>
 * Enum::Create('UserState', 'inactive', 'active', 'banned', 'deleted');
 * </code>
 *
 * Using Enums:
 * ============
 *
 * The following example demonstrates how to compare two Enum elements
 *
 * <code>
 * var_dump(UserState::inactive == UserState::banned); // result: false
 * var_dump(UserState::active == UserState::active); // result: true
 * </code>
 *
 * Special Enum methods:
 * =====================
 *
 * Get the number of elements in an Enum:
 *
 * <code>
 * echo UserState::CountEntries(); // result: 4
 * </code>
 *
 * Get a list with all elements of the Enum:
 *
 * <code>
 * $allUserStates = UserState::GetEntries();
 * </code>
 *
 * Get a name of an element:
 *
 * <code>
 * echo UserState::GetName(UserState::deleted); // result: deleted
 * </code>
 *
 * Get an integer ID for an element (e.g. to store as a value in a database table):
 * This is simply the index of the element (beginning with 1).
 * Note that this ID is only unique for this Enum but now between different Enums.
 *
 * <code>
 * echo UserState::GetDatabaseID(UserState::active); // result: 2
 * </code>
 */
class Enum
{

    /**
     * @var Enum $instance The only instance of Enum (Singleton)
     */
    private static $instance;

    /**
     * @var array $enums    An array of all enums with Enum names as keys
     *          and arrays of element names as values
     */
    private $enums;

    /**
     * Constructs (the only) Enum instance
     */
    private function __construct()
    {
        $this->enums = array();
    }

    /**
     * Constructs a new enum
     *
     * @param string $name The class name for the enum
     * @param mixed $_ A list of strings to use as names for enum entries
     */
    public static function Create($name, $_)
    {
        // Create (the only) Enum instance if this hasn't happened yet
        if (self::$instance===null)
        {
            self::$instance = new Enum();
        }

        // Fetch the arguments of the function
        $args = func_get_args();
        // Exclude the "name" argument from the array of function arguments,
        // so only the enum element names remain in the array
        array_shift($args);
        self::$instance->add($name, $args);
    }

    /**
     * Creates an enumeration if this hasn't happened yet
     * 
     * @param string $name The class name for the enum
     * @param array $fields The names of the enum elements
     */
    private function add($name, $fields)
    {
        if (!array_key_exists($name, $this->enums))
        {
            $this->enums[$name] = array();

            // Generate the code of the class for this enumeration
            $classDeclaration =     "class " . $name . " {\n"
                        . "private static \$name = '" . $name . "';\n"
                        . $this->getClassConstants($name, $fields)
                        . $this->getFunctionGetEntries($name)
                        . $this->getFunctionCountEntries($name)
                        . $this->getFunctionGetDatabaseID()
                        . $this->getFunctionGetName()
                        . "}";

            // Create the class for this enumeration
            eval($classDeclaration);
        }
    }

    /**
     * Returns the code of the class constants
     * for an enumeration. These are the representations
     * of the elements.
     * 
     * @param string $name The class name for the enum
     * @param array $fields The names of the enum elements
     *
     * @return string The code of the class constants
     */
    private function getClassConstants($name, $fields)
    {
        $constants = '';

        foreach ($fields as $field)
        {
            // Create a unique ID for the Enum element
            // This ID is unique because class and variables
            // names can't contain a semicolon. Therefore we
            // can use the semicolon as a separator here.
            $uniqueID = $name . ";" . $field;
            $constants .=   "const " . $field . " = '". $uniqueID . "';\n";
            // Store the unique ID
            array_push($this->enums[$name], $uniqueID);
        }

        return $constants;
    }

    /**
     * Returns the code of the function "GetEntries()"
     * for an enumeration
     * 
     * @param string $name The class name for the enum
     *
     * @return string The code of the function "GetEntries()"
     */
    private function getFunctionGetEntries($name) 
    {
        $entryList = '';        

        // Put the unique element IDs in single quotes and
        // separate them with commas
        foreach ($this->enums[$name] as $key => $entry)
        {
            if ($key > 0) $entryList .= ',';
            $entryList .= "'" . $entry . "'";
        }

        return  "public static function GetEntries() { \n"
            . " return array(" . $entryList . ");\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "CountEntries()"
     * for an enumeration
     * 
     * @param string $name The class name for the enum
     *
     * @return string The code of the function "CountEntries()"
     */
    private function getFunctionCountEntries($name) 
    {
        // This function will simply return a constant number (e.g. return 5;)
        return  "public static function CountEntries() { \n"
            . " return " . count($this->enums[$name]) . ";\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "GetDatabaseID()"
     * for an enumeration
     * 
     * @return string The code of the function "GetDatabaseID()"
     */
    private function getFunctionGetDatabaseID()
    {
        // Check for the index of this element inside of the array
        // of elements and add +1
        return  "public static function GetDatabaseID(\$entry) { \n"
            . "\$key = array_search(\$entry, self::GetEntries());\n"
            . " return \$key + 1;\n"
            . "}\n";
    }

    /**
     * Returns the code of the function "GetName()"
     * for an enumeration
     *
     * @return string The code of the function "GetName()"
     */
    private function getFunctionGetName()
    {
        // Remove the class name from the unique ID 
        // and return this value (which is the element name)
        return  "public static function GetName(\$entry) { \n"
            . "return substr(\$entry, strlen(self::\$name) + 1 , strlen(\$entry));\n"
            . "}\n";
    }

}


?>

提出的解决方案效果很好。干净光滑。

然而,如果你想要强类型的枚举,你可以使用这个:

class TestEnum extends Enum
{
    public static $TEST1;
    public static $TEST2;
}
TestEnum::init(); // Automatically initializes enum values

枚举类如下所示:

class Enum
{
    public static function parse($enum)
    {
        $class = get_called_class();
        $vars = get_class_vars($class);
        if (array_key_exists($enum, $vars)) {
            return $vars[$enum];
        }
        return null;
    }

    public static function init()
    {
        $className = get_called_class();
        $consts = get_class_vars($className);
        foreach ($consts as $constant => $value) {
            if (is_null($className::$$constant)) {
                $constantValue = $constant;
                $constantValueName = $className . '::' . $constant . '_VALUE';
                if (defined($constantValueName)) {
                    $constantValue = constant($constantValueName);
                }
                $className::$$constant = new $className($constantValue);
            }
        }
    }

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

这样,枚举值是强类型的和

$TEST1 == TEST1::parse('TEST1') // true语句

我已经在这里评论了一些其他的答案,所以我想我也会发表意见。 最后,由于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