我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
当前回答
基于此要点,所有枚举的基类:
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";
在网上试试:沙盒
其他回答
在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文档中发布。
编辑:从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根本不能被实例化,也不应该被实例化。
基于此要点,所有枚举的基类:
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";
在网上试试:沙盒
这里的其他一些答案中缺少的一个方面是使用带有类型提示的枚举的方法。
如果您将枚举定义为抽象类中的一组常量,例如。
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 TestEnum extends Enum
{
public static $TEST1;
public static $TEST2;
}
TestEnum::init(); // Automatically initializes enum values
枚举类如下所示:
class Enum
{
public static function parse($enum)
{
$class = get_called_class();
$vars = get_class_vars($class);
if (array_key_exists($enum, $vars)) {
return $vars[$enum];
}
return null;
}
public static function init()
{
$className = get_called_class();
$consts = get_class_vars($className);
foreach ($consts as $constant => $value) {
if (is_null($className::$$constant)) {
$constantValue = $constant;
$constantValueName = $className . '::' . $constant . '_VALUE';
if (defined($constantValueName)) {
$constantValue = constant($constantValueName);
}
$className::$$constant = new $className($constantValue);
}
}
}
public function __construct($value)
{
$this->value = $value;
}
}
这样,枚举值是强类型的和
$TEST1 == TEST1::parse('TEST1') // true语句