php - PHP和Enumerations

  显示原文与译文双语对照的内容

我知道PHP没有原生枚举。 但是我已经从Java世界习惯了它们。 我很乐意使用枚举作为一种方法来给预定义的值哪个 ide'自动完成功能可以识别的数据

常量可以实现,但存在名称空间冲突问题,( 或者实际上是 ,因为 ) 是全局的。 数组没有名称空间问题,但是它们太模糊了,可以在运行时覆盖它们,并且ide很少知道如何自动填充它们的键。

你通常使用的解决方案/解决办法是什么? 有人记得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) {
 $values = array_values(self::getConstants());
 return in_array($value, $values, $strict = true);
 }
}

通过创建扩展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

作为一个方面说明,我使用反射至少一次随时都用的是静态/常数,如果静态数据将不会改变 ( 例如在枚举中),我将结果缓存这些对象每次最终一定会出现一个明显的反射调用,因为实践全新的反射的性能影响( 存储在assocciative数组中用于多个枚举) 。

现在,很多人在尝试最后升级到至少 5.3,和 SplEnum 可用,这还是一个可行的选择为well--as只要你不介意概念传统上unintuitive之具有实际枚举实例化你整个代码库 在上面的例子中,BasicEnumDaysOfWeek 根本不能被实例化,也不能是。

类常量如何?


<?php

class YourClass
{
 const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

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

我使用 interface 而不是 class:


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

var $today = DaysOfWeek::Sunday;

好的,对于一个简单的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初学者,在使用语法的时候,这可能不是最好的方式。 我尝试了一些类常量,使用反射来从它的值获取常量名,可能会更整齐。

上面的答案非常棒。 但是,如果你用两种不同的方式进行操作,那么不管哪个扩展首先导致调用函数,都会创建缓存。 这个缓存将被所有后续调用使用,无论调用是由哪个扩展启动的。

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


 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

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

}


?>

我也喜欢源自Java的枚举,因为这个原因,我将用这种方式编写枚举,当然,我认为这是最相似的behawior,当然,如果有人想使用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


我在这里评论了一些其他的答案,所以我想我也应该权衡一下。 最后,由于PHP不支持类型化枚举,你可以采用以下两种方法之一: hack 出类型的枚举,也不必忍受事实,他们都非常难有效 hack 出来。

我更喜欢使用这个事实,而使用了 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 作为基类,所有其他枚举扩展都允许使用 helper 方法,例如 toArrayisValid 等。 对我来说,类型化的枚举( 和管理他们的实例) 只是太混乱了。


假设

用一个有点 Multiton pattern, 如果,两者存在 __getStatic 魔术方法( ,最好是 __equals 魔术法) 其中的大部分可能是 mitigated.

( 以下是假设的;它不会缺乏,但或许有一天它会 )


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

...