我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。

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

你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?


当前回答

类常量呢?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

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

其他回答

在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

支持枚举

你也可以有“支持的”枚举,其中每个枚举案例都由一个int或字符串“支持”。

enum Metal: int {
  case Gold = 1932;
  case Silver = 1049;
  case Lead = 1134;
  case Uranium = 1905;
  case Copper = 894;
}

如果一个案例有一个支持值,所有案例都需要有一个支持值,没有自动生成的值。 注意,受支持值的类型声明在枚举名称之后 备份值为只读 标量值必须是唯一的 值必须是字面量或字面表达式 要读取支持的值,您可以访问value属性:Metal::Gold->value。

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

从字符串(int |):自我 tryFrom (int |字符串):自我?

它们几乎是等效的,重要的区别是,如果没有找到值,第一个将抛出异常,而第二个将简单地返回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

值清单

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

$modes = TransportMode::cases();

现在$modes是:

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

静态方法

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


这涵盖了基本知识。要了解全部内容,请前往相关RFC,直到该特性在PHP文档中发布。

我也喜欢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

踩在布莱恩·克莱恩的回答上,我想我可能会给出我的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 value
        if (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 comparison
            return $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];
    }
}

好吧,对于一个简单的java,比如php中的enum,我使用:

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初学者,语法不好,所以这可能不是最好的方法。我尝试了一些类常量,使用反射从它的值中获得常量名,可能更整洁。

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

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());