PHP上的枚举

我知道PHP还没有原生的枚举。但我已经习惯了Java的世界。我喜欢使用枚举来提供IDE的自动完成功能可以理解的预定义值。

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

有没有你常用的解决方案/变通方法?有人记得PHP的人是否对枚举有过任何想法或决定吗?

641807 次浏览

我在PHP中看到的枚举最常见的解决方案是创建一个通用枚举类,然后对其进行扩展。您可以看看这个

更新:或者,我发现这个从phpclasses.org.

我使用了带有常量的类:

class Enum {const NAME       = 'aaaa';const SOME_VALUE = 'bbbb';}
print Enum::NAME;

类常量呢?

<?php
class YourClass{const SOME_CONSTANT = 1;
public function echoConstant(){echo self::SOME_CONSTANT;}}
echo YourClass::SOME_CONSTANT;
$c = new YourClass;$c->echoConstant();

根据用例,我通常会使用简单,如下所示:

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');                  // falseDaysOfWeek::isValidName('Monday');                   // trueDaysOfWeek::isValidName('monday');                   // trueDaysOfWeek::isValidName('monday', $strict = true);   // falseDaysOfWeek::isValidName(0);                          // false
DaysOfWeek::isValidValue(0);                         // trueDaysOfWeek::isValidValue(5);                         // trueDaysOfWeek::isValidValue(7);                         // falseDaysOfWeek::isValidValue('Friday');                  // false

顺便说一句,每当我使用反射至少一次在静态/const类上,数据不会改变(例如在枚举中)时,我都会缓存这些反射调用的结果,因为每次使用新的反射对象最终都会产生明显的性能影响(存储在多个枚举的组合数组中)。

现在大多数人已经将最后升级到至少5.3,并且SplEnum可用,这当然也是一个可行的选择-只要您不介意在整个代码库中使用实际枚举实例化的传统非直观概念。在上面的例子中,BasicEnumDaysOfWeek根本无法实例化,也不应该实例化。

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

<?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 yetif (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 arrayarray_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 enumerationeval($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 IDarray_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 commasforeach ($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 +1return  "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";}
}

?>

这是一个用于处理php中类型安全枚举的github库:

这个库处理类生成、类缓存,它实现了类型安全枚举设计模式,有几个帮助方法来处理枚举,比如检索枚举排序的序数,或者检索枚举组合的二进制值。

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

它是用phpUnit覆盖的完整测试。

GitHub上的php-enum(可以随意分叉)

用法:(@参见usage.php,或单元测试了解更多详细信息)

<?php//require the libraryrequire_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()APPLEORANGERASBERRYBANNANA->getValue()appleorangerasberrybannana->getValue() when values have been specifiedpigdogcatbird->getOrdinal()1234->getBinary()1248

好吧,对于一个简单的java,如PHP中的枚举,我使用:

class SomeTypeName {private static $enum = array(1 => "Read", 2 => "Write");
public function toOrdinal($name) {return array_search($name, self::$enum);}
public function toString($ordinal) {return self::$enum[$ordinal];}}

并称之为:

SomeTypeName::toOrdinal("Read");SomeTypeName::toString(1);

但我是一个PHP初学者,在语法上苦苦挣扎,所以这可能不是最好的方法。我用类常量尝试了一些,使用反射从它的值中获取常量名称,可能会更整洁。

昨天我写了这个类在我的博客。我认为它可能很容易在php脚本中使用:

final class EnumException extends Exception{}
abstract class Enum{/*** @var array ReflectionClass*/protected static $reflectorInstances = array();/*** Массив конфигурированного объекта-константы enum* @var array*/protected static $enumInstances = array();/*** Массив соответствий значение->ключ используется для проверки -* если ли константа с таким значением* @var array*/protected static $foundNameValueLink = array();
protected $constName;protected $constValue;
/*** Реализует паттерн "Одиночка"* Возвращает объект константы, но но как объект его использовать не стоит,* т.к. для него реализован "волшебный метод" __toString()* Это должно использоваться только для типизачии его как параметра* @paradm Node*/final public static function get($value){// Это остается здесь для увеличения производительности (по замерам ~10%)$name = self::getName($value);if ($name === false)throw new EnumException("Неизвестая константа");$className = get_called_class();if (!isset(self::$enumInstances[$className][$name])){$value = constant($className.'::'.$name);self::$enumInstances[$className][$name] = new $className($name, $value);}
return self::$enumInstances[$className][$name];}
/*** Возвращает массив констант пар ключ-значение всего перечисления* @return array*/final public static function toArray(){$classConstantsArray = self::getReflectorInstance()->getConstants();foreach ($classConstantsArray as $k => $v)$classConstantsArray[$k] = (string)$v;return $classConstantsArray;}
/*** Для последующего использования в toArray для получения массива констант ключ->значение* @return ReflectionClass*/final private static function getReflectorInstance(){$className = get_called_class();if (!isset(self::$reflectorInstances[$className])){self::$reflectorInstances[$className] = new ReflectionClass($className);}return self::$reflectorInstances[$className];}
/*** Получает имя константы по её значению* @param string $value*/final public static function getName($value){$className = (string)get_called_class();
$value = (string)$value;if (!isset(self::$foundNameValueLink[$className][$value])){$constantName = array_search($value, self::toArray(), true);self::$foundNameValueLink[$className][$value] = $constantName;}return self::$foundNameValueLink[$className][$value];}
/*** Используется ли такое имя константы в перечислении* @param string $name*/final public static function isExistName($name){$constArray = self::toArray();return isset($constArray[$name]);}
/*** Используется ли такое значение константы в перечислении* @param string $value*/final public static function isExistValue($value){return self::getName($value) === false ? false : true;}

final private function __clone(){}
final private function __construct($name, $value){$this->constName = $name;$this->constValue = $value;}
final public function __toString(){return (string)$this->constValue;}}

用法:

class enumWorkType extends Enum{const FULL = 0;const SHORT = 1;}

还有一个本机扩展。

SplEnum提供了模拟和创建枚举对象的能力原生在PHP中。

http://www.php.net/manual/en/class.splenum.php

注意事项:

https://www.php.net/manual/en/spl-types.installation.php

PECL扩展没有与PHP捆绑在一起。

此PECL扩展的DLL当前不可用。

我也喜欢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)); //truevar_dump(FruitsEnum::$APPLE->equals(FruitsEnum::$ORANGE)); //falsevar_dump(FruitsEnum::$APPLE instanceof FruitsEnum); //truevar_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::$APPLE)); //true - enum from stringvar_dump(FruitsEnum::get("Apple")->equals(FruitsEnum::get("Orange"))); //false

这是我对“动态”枚举的看法……这样我就可以用变量调用它,例如。从表单。

看看这个代码块下面更新的verison…

$value = "concert";$Enumvalue = EnumCategory::enum($value);//$EnumValue = 1
class EnumCategory{const concert = 1;const festival = 2;const sport = 3;const nightlife = 4;const theatre = 5;const musical = 6;const cinema = 7;const charity = 8;const museum = 9;const other = 10;
public function enum($string){return constant('EnumCategory::'.$string);}}

更新:更好的方法…

class EnumCategory {
static $concert = 1;static $festival = 2;static $sport = 3;static $nightlife = 4;static $theatre = 5;static $musical = 6;static $cinema = 7;static $charity = 8;static $museum = 9;static $other = 10;
}

打电话给

EnumCategory::${$category};

我使用interface而不是class

interface DaysOfWeek{const Sunday = 0;const Monday = 1;// etc.}
var $today = DaysOfWeek::Sunday;

我知道这是一个古老的线程,但是我见过的变通方法中没有一个看起来像枚举,因为几乎所有的变通方法都需要您手动为枚举项赋值,或者需要您将枚举键数组传递给函数。所以我为此创建了自己的解决方案。

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

编辑:这仅适用于php>=5.3,但也可以修改为适用于旧版本

/*** 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;}}}}}

对于简单的枚举,我使用如下结构。通常,您可以将它们用于Switch语句。

<?phpdefine("OPTION_1", "1");define("OPTION_2", OPTION_1 + 1);define("OPTION_3", OPTION_2 + 1);
// Some function...switch($Val){case OPTION_1:{ Perform_1();}break;case OPTION_2:{ Perform_2();}break;...}?>

它不像C++中的原生枚举那样有连通性,但如果您以后想在两者之间添加一个选项,它似乎可以工作并且需要更少的维护。

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

公认的答案是要走的路,实际上也是我为简单起见而做的事情。枚举的大多数优点都提供了(可读、快速等)。然而,缺少一个概念:类型安全。在大多数语言中,枚举也用于限制允许的值。以下是如何通过使用私有构造函数、静态实例化方法和类型检查来获得类型安全的示例:

class DaysOfWeek{const Sunday = 0;const Monday = 1;// etc.
private $intVal;private function __construct($intVal){$this->intVal = $intVal;}
//static instantiation methodspublic static function MONDAY(){return new self(self::Monday);}//etc.}
//function using type checkingfunction printDayOfWeek(DaysOfWeek $d){ //compiler can now use type checking// to something with $d...}
//calling the function is safe!printDayOfWeek(DaysOfWeek::MONDAY());

我们甚至可以更进一步:在DaysOfWeek类中使用常量可能会导致误用:例如,人们可能会错误地这样使用它:

printDayOfWeek(DaysOfWeek::Monday); //triggers a compiler error.

这是错误的(调用整数常量)。我们可以使用私有静态变量而不是常量来防止这种情况:

class DaysOfWeeks{
private static $monday = 1;//etc.
private $intVal;//private constructorprivate function __construct($intVal){$this->intVal = $intVal;}
//public instantiation methodspublic static function MONDAY(){return new self(self::$monday);}//etc.

//convert an instance to its integer valuepublic function intVal(){return $this->intVal;}
}

当然,不可能访问整数常量(这实际上是目的)。intVal方法允许将DaysOfWeek对象转换为其整数表示形式。

请注意,我们甚至可以更进一步,在实例化方法中实现缓存机制,以在广泛使用枚举的情况下节省内存…

希望这能有所帮助

我已经开始使用下面的方法,因为它使我能够为函数参数提供类型安全、在NetBeans中自动完成和良好的性能。我不太喜欢的一件事是,你必须在定义类后调用[extended class name]::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");

指出的解决方案工作良好。清洁和光滑。

但是,如果你想要强类型的枚举,你可以使用:

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

使用Enum类看起来像:

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

这样,枚举值是强类型的,并且

TestEnum::$TEST1 === TestEnum::parse('TEST1') // true statement

一些好的解决方案在这里!

这是我的版本。

  • 是强类型的
  • 它适用于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、我的第一个价值观

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

我的第二价值

abstract class Enumeration{public static function enum(){$reflect = new ReflectionClass( get_called_class() );return $reflect->getConstants();}}

class Test extends Enumeration{const A = 'a';const B = 'b';}

foreach (Test::enum() as $key => $value) {echo "$key -> $value<br>";}

我已经评论了这里的一些其他答案,所以我想我也会权衡。在一天结束的时候,由于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作为所有其他枚举扩展的基类允许使用辅助方法,例如toArrayisValid等。对我来说,类型化枚举(和管理他们的实例)最终太混乱了。


假设

如果,存在__getStatic魔法方法(最好是#1魔法方法),其中大部分可以通过一种多吨模式来缓解。

以下是假设;它不会工作,尽管也许有一天它会

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

我在github上找到了这个图书馆,我认为它为这里的答案提供了一个非常不错的替代方案。

受SplEnum启发的PHP Enum实现

  • 您可以输入提示:function setAction(Action $action) {
  • 您可以使用方法(例如formatparse、…)来丰富枚举
  • 您可以扩展枚举以添加新值(使您的枚举final以防止它)
  • 您可以获得所有可能值的列表(见下文)

宣言

<?phpuse MyCLabs\Enum\Enum;
/*** Action enum*/class Action extends Enum{const VIEW = 'view';const EDIT = 'edit';}

用法

<?php$action = new Action(Action::VIEW);
// or$action = Action::VIEW();

type-hint枚举值:

<?phpfunction setAction(Action $action) {// ...}

上面的答案很棒。但是,如果你以两种不同的方式extend它,那么首先完成的扩展会导致对函数的调用将创建缓存。然后,该缓存将被所有后续调用使用,无论调用是由哪个扩展发起的…

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

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中完成代码以及类型安全:

基类:

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 DaysOfWeeksaveEvent(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
// My Enumeration Classclass Enum{protected $m_actions = array();
public function __construct($actions){$this->init($actions);}
public function init($actions){$this->m_actions = array();for($i = 0; $i < count($actions); ++$i){$this->m_actions[$actions[$i]] = ($i + 1);define($actions[$i], ($i + 1));}}
public function toString($index){$keys = array_keys($this->m_actions);for($i = 0; $i < count($keys); ++$i){if($this->m_actions[$keys[$i]] == $index){return $keys[$i];}}
return "undefined";}
public function fromString($str){return $this->m_actions[$str];}}
// Enumeration creation$actions = new Enum(array("CREATE", "READ", "UPDATE", "DELETE"));
// Examplesprint($action_objects->toString(DELETE));print($action_objects->fromString("DELETE"));
if($action_objects->fromString($_POST["myAction"]) == CREATE){print("CREATE");}

下面我的枚举类定义是强类型,使用和定义非常自然

定义:

class Fruit extends Enum {static public $APPLE = 1;static public $ORANGE = 2;}Fruit::initialize(); //Can also be called in autoloader

切换到枚举

$myFruit = Fruit::$APPLE;
switch ($myFruit) {case Fruit::$APPLE  : echo "I like apples\n";  break;case Fruit::$ORANGE : echo "I hate oranges\n"; break;}
>> I like apples

将Enum作为参数传递(强类型)

/** Function only accepts Fruit enums as input**/function echoFruit(Fruit $fruit) {echo $fruit->getName().": ".$fruit->getValue()."\n";}
/** Call function with each Enum value that Fruit has */foreach (Fruit::getList() as $fruit) {echoFruit($fruit);}
//Call function with Apple enumechoFruit(Fruit::$APPLE)
//Will produce an error. This solution is strongly typedechoFruit(2);
>> APPLE: 1>> ORANGE: 2>> APPLE: 1>> Argument 1 passed to echoFruit() must be an instance of Fruit, integer given

Echo枚举为字符串

echo "I have an $myFruit\n";
>> I have an APPLE

按整数获取枚举

$myFruit = Fruit::getByValue(2);
echo "Now I have an $myFruit\n";
>> Now I have an ORANGE

按名称获取枚举

$myFruit = Fruit::getByName("APPLE");
echo "But I definitely prefer an $myFruit\n\n";
>> But I definitely prefer an APPLE

枚举类:

/*** @author Torge Kummerow*/class Enum {
/*** Holds the values for each type of Enum*/static private $list = array();
/*** Initializes the enum values by replacing the number with an instance of itself* using reflection*/static public function initialize() {$className = get_called_class();$class = new ReflectionClass($className);$staticProperties = $class->getStaticProperties();
self::$list[$className] = array();
foreach ($staticProperties as $propertyName => &$value) {if ($propertyName == 'list')continue;
$enum = new $className($propertyName, $value);$class->setStaticPropertyValue($propertyName, $enum);self::$list[$className][$propertyName] = $enum;} unset($value);}

/*** Gets the enum for the given value** @param integer $value* @throws Exception** @return Enum*/static public function getByValue($value) {$className = get_called_class();foreach (self::$list[$className] as $propertyName=>&$enum) {/* @var $enum Enum */if ($enum->value == $value)return $enum;} unset($enum);
throw new Exception("No such enum with value=$value of type ".get_called_class());}
/*** Gets the enum for the given name** @param string $name* @throws Exception** @return Enum*/static public function getByName($name) {$className = get_called_class();if (array_key_exists($name, static::$list[$className]))return self::$list[$className][$name];
throw new Exception("No such enum ".get_called_class()."::\$$name");}

/*** Returns the list of all enum variants* @return Array of Enum*/static public function getList() {$className = get_called_class();return self::$list[$className];}

private $name;private $value;
public function __construct($name, $value) {$this->name = $name;$this->value = $value;}
public function __toString() {return $this->name;}
public function getValue() {return $this->value;}
public function getName() {return $this->name;}
}

添加

当然你也可以为IDE添加注释

class Fruit extends Enum {
/*** This comment is for autocomplete support in common IDEs* @var Fruit A yummy apple*/static public $APPLE = 1;
/*** This comment is for autocomplete support in common IDEs* @var Fruit A sour orange*/static public $ORANGE = 2;}
//This can also go to the autoloader if available.Fruit::initialize();
class DayOfWeek {static $values = array(self::MONDAY,self::TUESDAY,// ...);
const MONDAY  = 0;const TUESDAY = 1;// ...}
$today = DayOfWeek::MONDAY;
// If you want to check if a value is validassert( in_array( $today, DayOfWeek::$values ) );

不要使用反射。这会让你很难推理你的代码和追踪某些东西在哪里被使用,并且往往会破坏静态分析工具(例如你的IDE中内置的工具)。

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

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

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

那么你就不能在函数参数中键入hint它——首先,因为它不可实例化,还因为ShirtSize::SMALL的类型是int,而不是ShirtSize

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

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方法,这样用户就不必记住使用==而不是===来比较两个枚举值。

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

我知道这是一个非常非常非常古老的线索,但我对此有一个想法,想知道人们的想法。

注意:我正在研究这个,并意识到如果我只是修改__call()函数,你可以更接近实际的enums__call()函数处理所有未知的函数调用。所以假设你想做三个enumsRED_LIGHT、YELLOW_LIGHT和GREEN_LIGHT。你现在可以通过执行以下操作来做到这一点:

$c->RED_LIGHT();$c->YELLOW_LIGHT();$c->GREEN_LIGHT();

定义后,您所要做的就是再次调用它们以获取值:

echo $c->RED_LIGHT();echo $c->YELLOW_LIGHT();echo $c->GREEN_LIGHT();

你应该得到0、1和2。玩得开心!这现在也在GitHub上。

更新:我已经做到了,所以现在使用了__get()__set()函数。这些允许您不必调用函数,除非您愿意。相反,现在您可以说:

$c->RED_LIGHT;$c->YELLOW_LIGHT;$c->GREEN_LIGHT;

用于创建和获取值。因为变量最初没有定义,所以调用__get()函数(因为没有指定值),它会看到数组中的条目尚未创建。因此它创建条目,将最后一个给定的值加上一(+1),增加最后一个值变量,并返回TRUE。如果您设置了值:

$c->RED_LIGHT = 85;

然后调用__set()函数,然后将最后一个值设置为新值加1(+1)。所以现在我们有了一个相当好的方法来执行枚举,并且可以动态创建它们。

<?php#################################################################################   Class ENUMS##       Original code by Mark Manning.#       Copyrighted (c) 2015 by Mark Manning.#       All rights reserved.##       This set of code is hereby placed into the free software universe#       via the GNU greater license thus placing it under the Copyleft#       rules and regulations with the following modifications:##       1. You may use this work in any other work.  Commercial or otherwise.#       2. You may make as much money as you can with it.#       3. You owe me nothing except to give me a small blurb somewhere in#           your program or maybe have pity on me and donate a dollar to#           sim_sales@paypal.com.  :-)##   Blurb:##       PHP Class Enums by Mark Manning (markem-AT-sim1-DOT-us).#       Used with permission.##   Notes:##       VIM formatting.  Set tabs to four(4) spaces.#################################################################################class enums{private $enums;private $clear_flag;private $last_value;
#################################################################################   __construct(). Construction function.  Optionally pass in your enums.################################################################################function __construct(){$this->enums = array();$this->clear_flag = false;$this->last_value = 0;
if( func_num_args() > 0 ){return $this->put( func_get_args() );}
return true;}#################################################################################   put(). Insert one or more enums.################################################################################function put(){$args = func_get_args();##   Did they send us an array of enums?#   Ex: $c->put( array( "a"=>0, "b"=>1,...) );#   OR  $c->put( array( "a", "b", "c",... ) );#if( is_array($args[0]) ){##   Add them all in#foreach( $args[0] as $k=>$v ){##   Don't let them change it once it is set.#   Remove the IF statement if you want to be able to modify the enums.#if( !isset($this->enums[$k]) ){##   If they sent an array of enums like this: "a","b","c",... then we have to#   change that to be "A"=>#. Where "#" is the current count of the enums.#if( is_numeric($k) ){$this->enums[$v] = $this->last_value++;}##   Else - they sent "a"=>"A", "b"=>"B", "c"=>"C"...#else {$this->last_value = $v + 1;$this->enums[$k] = $v;}}}}##   Nope!  Did they just sent us one enum?#else {##   Is this just a default declaration?#   Ex: $c->put( "a" );#if( count($args) < 2 ){##   Again - remove the IF statement if you want to be able to change the enums.#if( !isset($this->enums[$args[0]]) ){$this->enums[$args[0]] = $this->last_value++;}##   No - they sent us a regular enum#   Ex: $c->put( "a", "This is the first enum" );#else {##   Again - remove the IF statement if you want to be able to change the enums.#if( !isset($this->enums[$args[0]]) ){$this->last_value = $args[1] + 1;$this->enums[$args[0]] = $args[1];}}}}
return true;}#################################################################################   get(). Get one or more enums.################################################################################function get(){$num = func_num_args();$args = func_get_args();##   Is this an array of enums request? (ie: $c->get(array("a","b","c"...)) )#if( is_array($args[0]) ){$ary = array();foreach( $args[0] as $k=>$v ){$ary[$v] = $this->enums[$v];}
return $ary;}##   Is it just ONE enum they want? (ie: $c->get("a") )#else if( ($num > 0) && ($num < 2) ){return $this->enums[$args[0]];}##   Is it a list of enums they want? (ie: $c->get( "a", "b", "c"...) )#else if( $num > 1 ){$ary = array();foreach( $args as $k=>$v ){$ary[$v] = $this->enums[$v];}
return $ary;}##   They either sent something funky or nothing at all.#return false;}#################################################################################   clear(). Clear out the enum array.#       Optional.  Set the flag in the __construct function.#       After all, ENUMS are supposed to be constant.################################################################################function clear(){if( $clear_flag ){unset( $this->enums );$this->enums = array();}
return true;}#################################################################################   __call().  In case someone tries to blow up the class.################################################################################function __call( $name, $arguments ){if( isset($this->enums[$name]) ){ return $this->enums[$name]; }else if( !isset($this->enums[$name]) && (count($arguments) > 0) ){$this->last_value = $arguments[0] + 1;$this->enums[$name] = $arguments[0];return true;}else if( !isset($this->enums[$name]) && (count($arguments) < 1) ){$this->enums[$name] = $this->last_value++;return true;}
return false;}#################################################################################   __get(). Gets the value.################################################################################function __get($name){if( isset($this->enums[$name]) ){ return $this->enums[$name]; }else if( !isset($this->enums[$name]) ){$this->enums[$name] = $this->last_value++;return true;}
return false;}#################################################################################   __set().  Sets the value.################################################################################function __set( $name, $value=null ){if( isset($this->enums[$name]) ){ return false; }else if( !isset($this->enums[$name]) && !is_null($value) ){$this->last_value = $value + 1;$this->enums[$name] = $value;return true;}else if( !isset($this->enums[$name]) && is_null($value) ){$this->enums[$name] = $this->last_value++;return true;}
return false;}#################################################################################   __destruct().  Deconstruct the class.  Remove the list of enums.################################################################################function __destruct(){unset( $this->enums );$this->enums = null;
return true;}
}##   Test code##   $c = new enums();#   $c->RED_LIGHT(85);#   $c->YELLOW_LIGHT = 23;#   $c->GREEN_LIGHT;##   echo $c->RED_LIGHT . "\n";#   echo $c->YELLOW_LIGHT . "\n";#   echo $c->GREEN_LIGHT . "\n";
?>

这可能很简单

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

在未来。

PHP RFC:枚举类型

踏上@Brian Cline的答案,我想我可能会给我5美分

<?php/*** A class that simulates Enums behaviour* <code>* class Season extends Enum{*    const Spring  = 0;*    const Summer = 1;*    const Autumn = 2;*    const Winter = 3;* }** $currentSeason = new Season(Season::Spring);* $nextYearSeason = new Season(Season::Spring);* $winter = new Season(Season::Winter);* $whatever = new Season(-1);               // Throws InvalidArgumentException* echo $currentSeason.is(Season::Spring);   // True* echo $currentSeason.getName();            // 'Spring'* echo $currentSeason.is($nextYearSeason);  // True* echo $currentSeason.is(Season::Winter);   // False* echo $currentSeason.is(Season::Spring);   // True* echo $currentSeason.is($winter);          // False* </code>** Class Enum** PHP Version 5.5*/abstract class Enum{/*** Will contain all the constants of every enum that gets created to* avoid expensive ReflectionClass usage* @var array*/private static $_constCacheArray = [];/*** The value that separates this instance from the rest of the same class* @var mixed*/private $_value;/*** The label of the Enum instance. Will take the string name of the* constant provided, used for logging and human readable messages* @var string*/private $_name;/*** Creates an enum instance, while makes sure that the value given to the* enum is a valid one** @param mixed $value The value of the current** @throws \InvalidArgumentException*/public final function __construct($value){$constants = self::_getConstants();if (count($constants) !== count(array_unique($constants))) {throw new \InvalidArgumentException('Enums cannot contain duplicate constant values');}if ($name = array_search($value, $constants)) {$this->_value = $value;$this->_name = $name;} else {throw new \InvalidArgumentException('Invalid enum value provided');}}/*** Returns the constant name of the current enum instance** @return string*/public function getName(){return $this->_name;}/*** Returns the value of the current enum instance** @return mixed*/public function getValue(){return $this->_value;}/*** Checks whether this enum instance matches with the provided one.* This function should be used to compare Enums at all times instead* of an identity comparison* <code>* // Assuming EnumObject and EnumObject2 both extend the Enum class* // and constants with such values are defined* $var  = new EnumObject('test');* $var2 = new EnumObject('test');* $var3 = new EnumObject2('test');* $var4 = new EnumObject2('test2');* echo $var->is($var2);  // true* echo $var->is('test'); // true* echo $var->is($var3);  // false* echo $var3->is($var4); // false* </code>** @param mixed|Enum $enum The value we are comparing this enum object against*                         If the value is instance of the Enum class makes*                         sure they are instances of the same class as well,*                         otherwise just ensures they have the same value** @return bool*/public final function is($enum){// If we are comparing enums, just make// sure they have the same toString valueif (is_subclass_of($enum, __CLASS__)) {return get_class($this) === get_class($enum)&& $this->getValue() === $enum->getValue();} else {// Otherwise assume $enum is the value we are comparing against// and do an exact comparisonreturn $this->getValue() === $enum;}}
/*** Returns the constants that are set for the current Enum instance** @return array*/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];}}

一个不使用反射的更简单、更轻的版本:

abstract class enum {private function __construct() {}static function has($const) {$name = get_called_class();return defined("$name::$const");}static function value($const) {$name = get_called_class();return defined("$name::$const")? constant("$name::$const") : false;}}

用法:

class requestFormat  extends enum { const HTML = 1; const JSON = 2; const XML  = 3; const FORM = 4; }
echo requestFormat::value('JSON'); // 2echo requestFormat::has('JSON');   // true

这提供了常量的优点,也允许检查它们的有效性,但它缺乏由更复杂的解决方案提供的其他花哨功能,给出的是这个问题,更明显的是无法检查值的反向(在上面的例子中,你不能检查'2'是否是一个有效的值)

如果您想要类型安全和一堆与该类型匹配的常量,一种方法是为您的枚举创建一个抽象类,然后使用锁定的构造函数扩展该类,如下所示:

abstract class DaysOfWeekEnum{public function __construct(string $value){$this->value = $value;}public function __toString(){return $this->value;}
}class Monday extends DaysOfWeekEnum{public function __construct(){parent::__construct("Monday");}}
class Tuesday extends DaysOfWeekEnum{public function __construct(){parent::__construct("Tuesday");}}

然后你可以让你的方法获取DaysOfWeek的实例,并将其传递给星期一、星期二等的实例……唯一的缺点是每次你想使用枚举时都必须“新建”一个实例,但我觉得这是值得的。

function printWeekDay(DaysOfWeek $day){echo "Today is $day.";}
printWeekDay(new Monday());

现在您可以使用类来本地构建它。根据官方留档。

SplEnum提供了模拟和创建枚举对象的能力原生在PHP中。

<?phpclass Month extends SplEnum {const __default = self::January;
const January = 1;const February = 2;const March = 3;const April = 4;const May = 5;const June = 6;const July = 7;const August = 8;const September = 9;const October = 10;const November = 11;const December = 12;}
echo new Month(Month::June) . PHP_EOL;
try {new Month(13);} catch (UnexpectedValueException $uve) {echo $uve->getMessage() . PHP_EOL;}?>

请注意,它是一个必须安装的扩展,但默认情况下不可用。它位于PHP网站本身描述的特殊类型下。上面的示例来自PHP网站。

最后,一个php7.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)中的常量的能力。根据定义,Interface中的所有内容都是public

PHP手册:接口

更新时间:

PHP 8.1现在有枚举

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

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 neededpublic function __call($arg1, $arg2) {throw new Exception("enum does not have method");}
// not really neededstatic 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 8.1中,您可以使用本机枚举

基本语法看起来像这样:

enum TransportMode {case Bicycle;case Car;case Ship;case Plane;case Feet;}
function travelCost(TransportMode $mode, int $distance): int{ /* implementation */ }
$mode = TransportMode::Boat;
$bikeCost = travelCost(TransportMode::Bicycle, 90);$boatCost = travelCost($mode, 90);
// this one would fail: (Enums are singletons, not scalars)$failCost = travelCost('Car', 90);

价值观

默认情况下,枚举不受任何类型的标量支持。因此TransportMode::Bicycle不是0,您无法在枚举之间使用><进行比较。

但以下作品:

$foo = TransportMode::Car;$bar = TransportMode::Car;$baz = TransportMode::Bicycle;
$foo === $bar; // true$bar === $baz; // false
$foo instanceof TransportMode; // true
$foo > $bar || $foo <  $bar; // false either way

支持枚举

您还可以拥有“支持”枚举,其中每个枚举案例都由intstring“支持”。

enum Metal: int {case Gold = 1932;case Silver = 1049;case Lead = 1134;case Uranium = 1905;case Copper = 894;}
  • 如果一个案例有一个支持值,所有案例都需要有一个支持值,没有自动生成的值。
  • 请注意,支持值的类型是在枚举名称之后声明的
  • 默认值为只读
  • 标量值必须是独特
  • 值需要是文字或文字表达式
  • 要读取支持的值,您可以访问value属性:Metal::Gold->value

最后,支持的枚举在内部实现了一个BackedEnum接口,它公开了两个方法:

  • from(int|string): self
  • tryFrom(int|string): ?self

它们几乎是等价的,重要的区别是,如果没有找到值,第一个将抛出异常,第二个将简单地返回null

// usage example:
$metal_1 = Metal::tryFrom(1932); // $metal_1 === Metal::Gold;$metal_2 = Metal::tryFrom(1000); // $metal_2 === null;
$metal_3 = Metal::from(9999); // throws Exception

方法

枚举可能有方法,因此实现接口。

interface TravelCapable{public function travelCost(int $distance): int;public function requiresFuel(): bool;}
enum TransportMode: int implements TravelCapable{case Bicycle = 10;case Car = 1000 ;case Ship = 800 ;case Plane = 2000;case Feet = 5;  
public function travelCost(int $distance): int{return $this->value * $distance;}  
public function requiresFuel(): bool {return match($this) {TransportMode::Car, TransportMode::Ship, TransportMode::Plane => true,TransportMode::Bicycle, TransportMode::Feet => false}}}
$mode = TransportMode::Car;
$carConsumesFuel = $mode->requiresFuel();   // true$carTravelCost   = $mode->travelCost(800);  // 800000

值列表

纯枚举和备份枚举都在内部实现接口UnitEnum,其中包括(静态)方法UnitEnum::cases(),并允许检索枚举中定义的案例数组:

$modes = TransportMode::cases();

现在$modes是:

[TransportMode::Bicycle,TransportMode::Car,TransportMode::Ship,TransportMode::PlaneTransportMode::Feet]

静态方法

枚举可以实现它们自己的static方法,这些方法通常用于专门的构造函数。


这涵盖了基础知识。要了解全部内容,请前往相关rfc,直到该功能发布并发布在PHP的留档中。

另一种方法是使用魔术方法__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。

我刚刚创建了一个图书馆,我希望它能完成这项工作。它可以在任何PHP项目上独立使用,并有一些Laravel好东西让生活更轻松。我在生产项目中使用它们。

https://github.com/Kwaadpepper/enum

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

用法很简单:

$enum = BasicEnum::someValue();echo $enum->equals(BasicEnum::someValue()) ? 'true' : 'false'; // trueecho $enum->value; // 'someValue' or the value you have definedecho $enum->label; // 'someValue' or the label you have definedecho $enum; // 'someValue' or the value you have definedecho 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'];}}