常量可以解决问题,但存在命名空间冲突问题,(或者实际上)因为它们是全局的。数组没有命名空间问题,但它们太模糊了,可以在运行时被覆盖,而IDE很少知道如何在不添加额外的静态分析注释或属性的情况下自动填充它们的键。
你通常会使用哪些解决方案/解决方法? 是否有人记得PHP团队是否对枚举有任何想法或决定?
https://www.php.net/manual/en/language.types.enumerations.php
enum DaysOfWeek: int
{
case Sunday = 0;
case Monday = 1;
// etc.
}
$today = DaysOfWeek::Sunday;
var_dump($today->value); // 0
var_dump($today->name); // "Sunday"
根据使用情况,我通常会使用类似以下的简单方法:
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);
}
}
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
顺便提一下,每当我在至少一个静态/常量类中使用反射,并且数据不会改变(比如在枚举中),我会缓存这些反射调用的结果,因为每次使用新的反射对象最终会对性能产生明显影响(在关联数组中存储多个枚举)。
现在大多数人终于升级到至少 5.3 版本,并且有了 SplEnum
,那当然也是一种可行的选择,只要你不介意在整个代码库中实际上存在枚举的实例化。在上面的例子中,BasicEnum
和 DaysOfWeek
都不能被实例化,也不应该被实例化。
abstract
和final
吗?我知道在Java中是不允许的。但在PHP中呢? - corsiKaswitch
语句中与null
等值的情况。我曾有过这样的经历。 - yitznewton还有一个本地扩展,它叫做SplEnum
SplEnum允许在PHP中本地模拟和创建枚举对象。
http://www.php.net/manual/zh/class.splenum.php
注意:
https://www.php.net/manual/zh/spl-types.installation.php
PECL扩展不随PHP一起打包。
当前没有针对此PECL扩展的DLL。
基本语法如下:
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
或string
“支持”。
enum Metal: int {
case Gold = 1932;
case Silver = 1049;
case Lead = 1134;
case Uranium = 1905;
case Copper = 894;
}
value
属性:Metal::Gold->value
。最后,支持的枚举在内部实现了BackedEnum
接口,该接口公开了两种方法:
from(int|string): self
tryFrom(int|string): ?self
它们几乎等效,但重要区别在于第一个方法将在未找到值时引发异常,而第二个方法将简单地返回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
纯枚举和支持的枚举都在内部实现了接口UnitEnum
,其中包括(静态)方法UnitEnum :: cases()
,允许检索定义在枚举中的情况数组:
$modes = TransportMode::cases();
现在$modes
的值为:
[
TransportMode::Bicycle,
TransportMode::Car,
TransportMode::Ship,
TransportMode::Plane
TransportMode::Feet
]
枚举可以实现自己的静态
方法,通常用于特殊构造函数。
以上是基础内容。要获取全部信息,请前往相关RFC,直到该功能发布并在PHP文档中公布。
::cases()
返回一个可迭代类型,因此可以使用foreach
循环。例如,像这样:foreach( TransportMode::cases() as $mode ) { echo $mode->value; }
- theking2UnitEnum::cases()
方法,我该如何将其转换为数组,以便我可以在 Select 输入中列出这些 case? - Pathros类常量怎么处理?
<?php
class YourClass
{
const SOME_CONSTANT = 1;
public function echoConstant()
{
echo self::SOME_CONSTANT;
}
}
echo YourClass::SOME_CONSTANT;
$c = new YourClass;
$c->echoConstant();
echoConstant
can be replaced with __toString
. And then simply echo $c
- Justinas我使用带有常量的类:
class Enum {
const NAME = 'aaaa';
const SOME_VALUE = 'bbbb';
}
print Enum::NAME;
上面的最佳答案非常棒。然而,如果您以两种不同的方式进行扩展,那么先进行哪种扩展就会导致对函数的调用将创建缓存。所有后续调用将使用该缓存,无论由哪种扩展发起调用...
为了解决这个问题,请将变量和第一个函数替换为:
private static $constCacheArray = null;
private static function getConstants() {
if (self::$constCacheArray === null) self::$constCacheArray = array();
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new \ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
我已在其他答案上发表评论,所以我觉得我也应该发表自己的看法。归根结底,由于PHP不支持类型枚举,你有两种选择:努力破解类型枚举,或者接受它们极难有效地破解这个事实。
我更喜欢接受这个事实,并使用其他答案中已经使用的const
方法:
abstract class Enum
{
const NONE = null;
final private function __construct()
{
throw new NotSupportedException(); //
}
final private function __clone()
{
throw new NotSupportedException();
}
final public static function toArray()
{
return (new ReflectionClass(static::class))->getConstants();
}
final public static function isValid($value)
{
return in_array($value, static::toArray());
}
}
一个示例枚举:
final class ResponseStatusCode extends Enum
{
const OK = 200;
const CREATED = 201;
const ACCEPTED = 202;
// ...
const SERVICE_UNAVAILABLE = 503;
const GATEWAY_TIME_OUT = 504;
const HTTP_VERSION_NOT_SUPPORTED = 505;
}
将Enum
用作所有其他枚举扩展的基类可以使用帮助方法,例如toArray
,isValid
等。对我来说,有类型的枚举(以及管理它们的实例)变得太凌乱了。
如果存在一个__getStatic
魔术方法(最好再加一个__equals
魔术方法),这将以一种多例模式缓解其中很多问题。
(以下是假设;虽然现在不起作用,但也许有一天会)
final class TestEnum
{
private static $_values = [
'FOO' => 1,
'BAR' => 2,
'QUX' => 3,
];
private static $_instances = [];
public static function __getStatic($name)
{
if (isset(static::$_values[$name]))
{
if (empty(static::$_instances[$name]))
{
static::$_instances[$name] = new static($name);
}
return static::$_instances[$name];
}
throw new Exception(sprintf('Invalid enumeration value, "%s"', $name));
}
private $_value;
public function __construct($name)
{
$this->_value = static::$_values[$name];
}
public function __equals($object)
{
if ($object instanceof static)
{
return $object->_value === $this->_value;
}
return $object === $this->_value;
}
}
$foo = TestEnum::$FOO; // object(TestEnum)#1 (1) {
// ["_value":"TestEnum":private]=>
// int(1)
// }
$zap = TestEnum::$ZAP; // Uncaught exception 'Exception' with message
// 'Invalid enumeration member, "ZAP"'
$qux = TestEnum::$QUX;
TestEnum::$QUX == $qux; // true
'hello world!' == $qux; // false
我使用 interface
而不是 class
:
interface DaysOfWeek
{
const Sunday = 0;
const Monday = 1;
// etc.
}
var $today = DaysOfWeek::Sunday;
class Foo implements DaysOfWeek { }
然后 Foo::Sunday
... 是什么意思?这段代码意味着定义了一个类 Foo
,它实现了 DaysOfWeek
接口。然后 Foo::Sunday
表示访问 Foo
类中实现的 DaysOfWeek
接口中的常量 Sunday
。 - Dan Lugg好的,对于像Java中的简单枚举类型在PHP中的实现,我使用以下代码:
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 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
const Monday = DaysOfWeek('Monday');
。 - Kangurprotected const
并且使用__callStatic
来创建枚举,如果你想要自动补全的话,就需要加上文档注释,但你甚至可以移除const
,只使用文档注释。 - Tofandel
8.1
中原生支持,预计将于2021年11月发布。其语法如下:enum Status { case started; case stopped; case paused; }
- Abdul Rahman Kayali