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

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

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


当前回答

这里的其他一些答案中缺少的一个方面是使用带有类型提示的枚举的方法。

如果您将枚举定义为抽象类中的一组常量,例如。

abstract class ShirtSize {
    public const SMALL = 1;
    public const MEDIUM = 2;
    public const LARGE = 3;
}

那么你就不能在函数参数中输入提示,一方面是因为它是不可实例化的,另一方面是因为ShirtSize::SMALL的类型是int,而不是ShirtSize。

这就是为什么PHP中的本地枚举比我们能想到的任何东西都要好得多。但是,我们可以通过保留一个表示枚举值的私有属性来近似枚举,然后将该属性的初始化限制为预定义的常量。为了防止枚举被任意实例化(没有白名单类型检查的开销),我们将构造函数设为private。

class ShirtSize {
    private $size;
    private function __construct ($size) {
        $this->size = $size;
    }
    public function equals (ShirtSize $s) {
        return $this->size === $s->size;
    }
    public static function SMALL () { return new self(1); }
    public static function MEDIUM () { return new self(2); }
    public static function LARGE () { return new self(3); }
}

然后我们可以像这样使用ShirtSize:

function sizeIsAvailable ($productId, ShirtSize $size) {
    // business magic
}
if(sizeIsAvailable($_GET["id"], ShirtSize::LARGE())) {
    echo "Available";
} else {
    echo "Out of stock.";
}
$s2 = ShirtSize::SMALL();
$s3 = ShirtSize::MEDIUM();
echo $s2->equals($s3) ? "SMALL == MEDIUM" : "SMALL != MEDIUM";

这样,从用户的角度来看,最大的区别是必须在常量的名称上附加一个()。

一个缺点是===(比较对象是否相等)将在==返回true时返回false。出于这个原因,最好提供一个equals方法,这样用户就不必记得使用==和not ===来比较两个enum值。

编辑:现有的几个答案非常相似,特别是:https://stackoverflow.com/a/25526473/2407870。

其他回答

公认的答案是要走的路,实际上这是我所做的简单。枚举提供了大多数优点(可读、快速等)。然而,这里缺少一个概念:类型安全。在大多数语言中,枚举也用于限制允许的值。下面是一个通过使用私有构造函数、静态实例化方法和类型检查来获得类型安全的例子:

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

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

 //static instantiation methods
 public static function MONDAY(){
   return new self(self::Monday);
 }
 //etc.
}

//function using type checking
function printDayOfWeek(DaysOfWeek $d){ //compiler can now use type checking
  // to something with $d...
}

//calling the function is safe!
printDayOfWeek(DaysOfWeek::MONDAY());

我们甚至可以更进一步:在DaysOfWeek类中使用常量可能会导致误用:例如,人们可能会这样错误地使用它:

printDayOfWeek(DaysOfWeek::Monday); //triggers a compiler error.

这是错误的(调用整数常量)。我们可以使用私有静态变量而不是常量来防止这种情况:

class DaysOfWeeks{

  private static $monday = 1;
  //etc.

  private $intVal;
  //private constructor
  private function __construct($intVal){
    $this->intVal = $intVal;
  }

  //public instantiation methods
  public static function MONDAY(){
    return new self(self::$monday);
  }
  //etc.


  //convert an instance to its integer value
  public function intVal(){
    return $this->intVal;
  }

}

当然,不可能访问整数常量(这实际上是目的)。intVal方法允许将DaysOfWeek对象转换为其整数表示形式。

请注意,我们甚至可以进一步在实例化方法中实现缓存机制,以在广泛使用枚举的情况下节省内存…

希望这能有所帮助

类常量呢?

<?php

class YourClass
{
    const SOME_CONSTANT = 1;

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

echo YourClass::SOME_CONSTANT;

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

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

我已经开始使用下面的方法,因为它使我能够对函数参数具有类型安全,在NetBeans中自动完成和良好的性能。我不太喜欢的一件事是你必须调用[扩展类名]::enumerate();在定义类之后。

abstract class Enum {

    private $_value;

    protected function __construct($value) {
        $this->_value = $value;
    }

    public function __toString() {
        return (string) $this->_value;
    }

    public static function enumerate() {
        $class = get_called_class();
        $ref = new ReflectionClass($class);
        $statics = $ref->getStaticProperties();
        foreach ($statics as $name => $value) {
            $ref->setStaticPropertyValue($name, new $class($value));
        }
    }
}

class DaysOfWeek extends Enum {
    public static $MONDAY = 0;
    public static $SUNDAY = 1;
    // etc.
}
DaysOfWeek::enumerate();

function isMonday(DaysOfWeek $d) {
    if ($d == DaysOfWeek::$MONDAY) {
        return true;
    } else {
        return false;
    }
}

$day = DaysOfWeek::$MONDAY;
echo (isMonday($day) ? "bummer it's monday" : "Yay! it's not monday");

现在您可以使用The脾脏类来原生构建它。根据官方文件。

脾提供了模拟和创建枚举对象的能力 原生的PHP。

<?php
class Month extends SplEnum {
    const __default = self::January;

    const January = 1;
    const February = 2;
    const March = 3;
    const April = 4;
    const May = 5;
    const June = 6;
    const July = 7;
    const August = 8;
    const September = 9;
    const October = 10;
    const November = 11;
    const December = 12;
}

echo new Month(Month::June) . PHP_EOL;

try {
    new Month(13);
} catch (UnexpectedValueException $uve) {
    echo $uve->getMessage() . PHP_EOL;
}
?>

请注意,这是一个必须安装的扩展,但默认情况下不可用。在PHP网站上描述的特殊类型下。上面的示例取自PHP站点。