我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
当前回答
还有一个本地扩展。的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中枚举最常见的解决方案是创建一个通用枚举类,然后扩展它。你可以看看这个。
更新:或者,我从phpclasses.org找到了这个。
一个不使用反射的更简单、更轻的版本:
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'); // 2
echo requestFormat::has('JSON'); // true
这提供了常量的优势,也允许检查它们的有效性,但它缺乏更复杂的解决方案所提供的其他花哨功能,更明显的是无法检查值的反向(在上面的例子中,你不能检查'2'是否是一个有效值)
编辑:从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根本不能被实例化,也不应该被实例化。
四年后,我又遇到了这个。我目前的方法是这样的,因为它允许在IDE中完成代码以及类型安全:
基类:
abstract class TypedEnum
{
private static $_instancedValues;
private $_value;
private $_name;
private function __construct($value, $name)
{
$this->_value = $value;
$this->_name = $name;
}
private static function _fromGetter($getter, $value)
{
$reflectionClass = new ReflectionClass(get_called_class());
$methods = $reflectionClass->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC);
$className = get_called_class();
foreach($methods as $method)
{
if ($method->class === $className)
{
$enumItem = $method->invoke(null);
if ($enumItem instanceof $className && $enumItem->$getter() === $value)
{
return $enumItem;
}
}
}
throw new OutOfRangeException();
}
protected static function _create($value)
{
if (self::$_instancedValues === null)
{
self::$_instancedValues = array();
}
$className = get_called_class();
if (!isset(self::$_instancedValues[$className]))
{
self::$_instancedValues[$className] = array();
}
if (!isset(self::$_instancedValues[$className][$value]))
{
$debugTrace = debug_backtrace();
$lastCaller = array_shift($debugTrace);
while ($lastCaller['class'] !== $className && count($debugTrace) > 0)
{
$lastCaller = array_shift($debugTrace);
}
self::$_instancedValues[$className][$value] = new static($value, $lastCaller['function']);
}
return self::$_instancedValues[$className][$value];
}
public static function fromValue($value)
{
return self::_fromGetter('getValue', $value);
}
public static function fromName($value)
{
return self::_fromGetter('getName', $value);
}
public function getValue()
{
return $this->_value;
}
public function getName()
{
return $this->_name;
}
}
枚举例子:
final class DaysOfWeek extends TypedEnum
{
public static function Sunday() { return self::_create(0); }
public static function Monday() { return self::_create(1); }
public static function Tuesday() { return self::_create(2); }
public static function Wednesday() { return self::_create(3); }
public static function Thursday() { return self::_create(4); }
public static function Friday() { return self::_create(5); }
public static function Saturday() { return self::_create(6); }
}
使用示例:
function saveEvent(DaysOfWeek $weekDay, $comment)
{
// store week day numeric value and comment:
$myDatabase->save('myeventtable',
array('weekday_id' => $weekDay->getValue()),
array('comment' => $comment));
}
// call the function, note: DaysOfWeek::Monday() returns an object of type DaysOfWeek
saveEvent(DaysOfWeek::Monday(), 'some comment');
注意,同一个枚举条目的所有实例都是相同的:
$monday1 = DaysOfWeek::Monday();
$monday2 = DaysOfWeek::Monday();
$monday1 === $monday2; // true
你也可以在switch语句中使用它:
function getGermanWeekDayName(DaysOfWeek $weekDay)
{
switch ($weekDay)
{
case DaysOfWeek::Monday(): return 'Montag';
case DaysOfWeek::Tuesday(): return 'Dienstag';
// ...
}
你也可以通过名称或值创建枚举项:
$monday = DaysOfWeek::fromValue(2);
$tuesday = DaysOfWeek::fromName('Tuesday');
或者你可以从一个现有的枚举条目中获取名称(即函数名):
$wednesday = DaysOfWeek::Wednesday()
echo $wednesDay->getName(); // Wednesday
我使用带有常量的类:
class Enum {
const NAME = 'aaaa';
const SOME_VALUE = 'bbbb';
}
print Enum::NAME;