PHP中的枚举类型

1313
我知道PHP目前还没有本地枚举。但是我已经从Java世界中习惯了它们。我希望使用枚举作为一种提供预定义值的方式,以便IDE的自动完成功能能够理解。
常量可以解决问题,但存在命名空间冲突问题,(或者实际上)因为它们是全局的。数组没有命名空间问题,但它们太模糊了,可以在运行时被覆盖,而IDE很少知道如何在不添加额外的静态分析注释或属性的情况下自动填充它们的键。
你通常会使用哪些解决方案/解决方法? 是否有人记得PHP团队是否对枚举有任何想法或决定?

http://it.toolbox.com/blogs/macsploitation/enums-in-php-a-native-implementation-25228 - pbean
1
我创建了一个绕过函数,将常量枚举为按位或非。之前没注意到你问过这个问题,但我有一个比类变量更好的解决方案,在这里:http://stackoverflow.com/questions/3836385/does-php-have-structs-or-enums - rolling_codes
https://github.com/myclabs/php-enum - Matthieu Napoli
我最近开发了一个简单的PHP Enums库: https://github.com/dnl-blkv/simple-php-enum 在回答这个问题时,它仍处于预发布阶段,但已经完全功能,文档齐全,并发布在Packagist上。如果您正在寻找易于实现类似C/C++的enums的便捷选项,这可能是一个不错的选择。 - dnl-blkv
9
PHP的枚举类型将在版本8.1中原生支持,预计将于2021年11月发布。其语法如下:enum Status { case started; case stopped; case paused; } - Abdul Rahman Kayali
1
随着 PHP 8.1 的发布,原生枚举现在已经成为可能,详见文档 - Niki Romagnoli
40个回答

2

根据这个代码片段,创建了一个所有枚举类的基类:

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 needed
    public function __call($arg1, $arg2) {
        throw new Exception("enum does not have method");
    }

    // not really needed
    static 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";

在线尝试: 沙盒


1
昨天我在我的博客上写了这个类(链接)。我认为它可能很容易在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;
}

2
但是这是一个好的类和函数名称是本地的。而且translate.google.ru可能会有所帮助。 - arturgspb
2
使用Chrome进行翻译,如果您是程序员,那么您会读代码! - markus
8
一般而言,最好将代码包含在答案中,而不是链接到可能在“n”个月/年等之后不存在的外部资源。 - John Parker
我的类太大了,我认为阅读这篇文章会很不方便。 - arturgspb
我认为这里有两个不好的地方:它是用俄语写的(每个程序员都必须懂英语并使用它,即使是在注释中)和它没有被包含在这里。请参考帮助文档了解如何包含大段代码。 - gaRex
@Arturgspb:代码在网站上完美适配,如果有遗漏的地方请告诉我们。 - hakre

1

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

它在许多方面都有限制,现有的答案已经远远超过了它。我认为这并没有真正添加任何有用的东西。 - hakre

0
如果您想要类型安全和一堆与该类型匹配的常量,一种方法是为您的枚举类创建一个抽象类,然后使用锁定构造函数来扩展该类,例如:
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 的实例,并传递 Monday、Tuesday 等实例。唯一的缺点是每次想要使用枚举时都必须创建一个新的实例,但我觉得这很值得。

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

printWeekDay(new Monday());

0
// My Enumeration Class
class 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"));

// Examples
print($action_objects->toString(DELETE));
print($action_objects->fromString("DELETE"));

if($action_objects->fromString($_POST["myAction"]) == CREATE)
{
    print("CREATE");
}

0
我刚刚创建了一个库,希望它能胜任工作。它可以独立于任何PHP项目中使用,并且具有一些Laravel好处,使生活更轻松。我在生产项目中使用它们。

https://github.com/Kwaadpepper/enum

如果您喜欢或不喜欢,请毫不犹豫地提供反馈意见。它可以被打印并序列化为JSON。它的定义非常简单。

使用方法也很简单:

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

1
我建议将该库重命名为比“Enum”更不含糊的名称。 - Peter Mortensen
我非常乐意听取建议。我本可以将其命名为“枚举”,但现在我没有太多灵感。 - Kwaadpepper

0
一个不使用反射的更简单、更轻量级的版本:
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'是否是一个有效值)


0

PHP7.4的类似PHP8的枚举实现

基类:

<?php
declare(strict_types=1);

namespace App\Enum;

abstract class Enum {

    protected const CASES = [];

    public readonly string $value;

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

    /**
     * @return static[]
     */
    public static function from(string $value): Enum {
        $Instance = static::tryFrom($value);
        if (null === $Instance) {
            throw new \ValueError(sprintf('"%s" is not a valid backing value for enum "%s', $value, static::class));
        }

        return $Instance;
    }

    /**
     * @return static[]
     */
    public static function tryFrom(string $value): ?Enum {
        if (!in_array($value, static::CASES, true)) {
            return null;
        }

        static $instances = [];

        $key = static::class . '_' . $value;
        $Instance = &$instances[$key];
        if (null === $Instance) {
            $Instance = new static($value);
        }

        return $Instance;
    }

    /**
     * @return static[]
     */
    public static function cases(): array {
        $result = [];
        foreach (static::CASES as $value) {
            $result[$value] = static::from($value);
        }

        return $result;
    }
}

子类:
<?php
declare(strict_types=1);

namespace App\Enum;

class Level extends Enum {

    public const Easy = 'easy';
    public const Medium = 'medium';
    public const Hard = 'hard';

    protected const CASES = [
        self::Easy,
        self::Medium,
        self::Hard,
    ];

    public function getTitle(): string {
        return match ($this->value) {
            static::Easy => __('Easy'),
            static::Medium => __('Medium'),
            static::Hard => __('Hard'),
        };
    }

    public static function values(): array {
        return array_column(static::cases(), 'value');
    }

    public static function getOptions(): array {
        $result = [];
        foreach (static::cases() as $Case) {
            $result[$Case->value] = $Case->getTitle();
        }

        return $result;
    }
}

使用方法:

$RequestedLevel = Level::tryFrom($_GET['level']);
$Level = Level::from(Level::Medium);
if ($RequestedLevel === $Level) {
    echo $Level->getTitle();
}

-1
另一种方法是使用魔术方法__set并将枚举设为私有。
例如:
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。


-3

我通常使用以下这种方式来创建简单枚举类型。通常可以将它们用于 switch 语句中。

<?php 
  define("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++中的本地枚举那样方便,但它似乎可以工作,并且如果您以后想在选项之间添加一个选项,则需要更少的维护。


3
你完全没有理解重点。Java枚举是面向对象编程的一部分。问题是除了常量之外,PHP是否有替代方案,而你的解决方案既不使用面向对象编程,也无法避免使用常量。 - Sven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接