我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得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;
}
其他回答
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>";
}
下面的Enum类定义是强类型的,使用和定义都很自然。
定义:
class Fruit extends Enum {
static public $APPLE = 1;
static public $ORANGE = 2;
}
Fruit::initialize(); //Can also be called in autoloader
切换Enum
$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 enum
echoFruit(Fruit::$APPLE)
//Will produce an error. This solution is strongly typed
echoFruit(2);
>> APPLE: 1
>> ORANGE: 2
>> APPLE: 1
>> Argument 1 passed to echoFruit() must be an instance of Fruit, integer given
以字符串形式返回Enum
echo "I have an $myFruit\n";
>> I have an APPLE
通过整数获取Enum
$myFruit = Fruit::getByValue(2);
echo "Now I have an $myFruit\n";
>> Now I have an ORANGE
按名称获取Enum
$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();
还有一个本地扩展。的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目前不可用。
编辑:从PHP 8.1开始,支持枚举:https://www.php.net/manual/en/language.types.enumerations.php
—
根据用例,我通常会使用如下简单的代码:
abstract class DaysOfWeek
{
const Sunday = 0;
const Monday = 1;
// etc.
}
$today = DaysOfWeek::Sunday;
然而,其他用例可能需要对常量和值进行更多的验证。根据下面关于反射的评论和其他一些注意事项,下面是一个扩展的示例,它可能更好地适用于更广泛的情况:
abstract class BasicEnum {
private static $constCacheArray = NULL;
private static function getConstants() {
if (self::$constCacheArray == NULL) {
self::$constCacheArray = [];
}
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
public static function isValidName($name, $strict = false) {
$constants = self::getConstants();
if ($strict) {
return array_key_exists($name, $constants);
}
$keys = array_map('strtolower', array_keys($constants));
return in_array(strtolower($name), $keys);
}
public static function isValidValue($value, $strict = true) {
$values = array_values(self::getConstants());
return in_array($value, $values, $strict);
}
}
通过创建一个扩展BasicEnum的简单枚举类,你现在可以使用方法进行简单的输入验证:
abstract class DaysOfWeek extends BasicEnum {
const Sunday = 0;
const Monday = 1;
const Tuesday = 2;
const Wednesday = 3;
const Thursday = 4;
const Friday = 5;
const Saturday = 6;
}
DaysOfWeek::isValidName('Humpday'); // false
DaysOfWeek::isValidName('Monday'); // true
DaysOfWeek::isValidName('monday'); // true
DaysOfWeek::isValidName('monday', $strict = true); // false
DaysOfWeek::isValidName(0); // false
DaysOfWeek::isValidValue(0); // true
DaysOfWeek::isValidValue(5); // true
DaysOfWeek::isValidValue(7); // false
DaysOfWeek::isValidValue('Friday'); // false
作为旁注,任何时候我在一个静态/const类上至少使用一次反射,其中数据不会改变(例如在enum中),我都会缓存那些反射调用的结果,因为每次使用新的反射对象最终都会有明显的性能影响(存储在多个enum的关联数组中)。
现在大多数人已经升级到至少5.3版本,而且也有了脾脏,这当然也是一个可行的选择——只要你不介意在你的代码库中使用实际枚举实例化这种传统的不直观的概念。在上面的例子中,BasicEnum和DaysOfWeek根本不能被实例化,也不应该被实例化。
我也喜欢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