我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
我知道PHP还没有本地枚举。但是我已经习惯了来自Java世界的它们。我喜欢使用枚举来提供ide的自动补全功能能够理解的预定义值。
常量可以解决这个问题,但是存在名称空间冲突问题,而且(实际上是因为)它们是全局的。数组没有名称空间的问题,但是它们太模糊了,它们可以在运行时被覆盖,而且ide很少知道如何在没有额外的静态分析注释或属性的情况下自动填充它们的键。
你有什么常用的解决方案/变通办法吗?有人记得PHP的人对枚举有什么想法或决定吗?
当前回答
昨天我在博客上写了这门课。我认为在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;
}
其他回答
我知道这是一个旧线程,然而没有一个我所见过的解决方法真的看起来像枚举,因为几乎所有的解决方法都需要你手动分配值给枚举项,或者它需要你传递一个枚举键数组到一个函数。所以我创造了自己的解决方案。
要使用我的解决方案创建枚举类,可以简单地扩展下面的enum类,创建一堆静态变量(不需要初始化它们),并在枚举类定义的下面调用yourEnumClass::init()。
edit: This only works in php >= 5.3, but it can probably be modified to work in older versions as well /** * A base class for enums. * * This class can be used as a base class for enums. * It can be used to create regular enums (incremental indices), but it can also be used to create binary flag values. * To create an enum class you can simply extend this class, and make a call to <yourEnumClass>::init() before you use the enum. * Preferably this call is made directly after the class declaration. * Example usages: * DaysOfTheWeek.class.php * abstract class DaysOfTheWeek extends Enum{ * static $MONDAY = 1; * static $TUESDAY; * static $WEDNESDAY; * static $THURSDAY; * static $FRIDAY; * static $SATURDAY; * static $SUNDAY; * } * DaysOfTheWeek::init(); * * example.php * require_once("DaysOfTheWeek.class.php"); * $today = date('N'); * if ($today == DaysOfTheWeek::$SUNDAY || $today == DaysOfTheWeek::$SATURDAY) * echo "It's weekend!"; * * Flags.class.php * abstract class Flags extends Enum{ * static $FLAG_1; * static $FLAG_2; * static $FLAG_3; * } * Flags::init(Enum::$BINARY_FLAG); * * example2.php * require_once("Flags.class.php"); * $flags = Flags::$FLAG_1 | Flags::$FLAG_2; * if ($flags & Flags::$FLAG_1) * echo "Flag_1 is set"; * * @author Tiddo Langerak */ abstract class Enum{ static $BINARY_FLAG = 1; /** * This function must be called to initialize the enumeration! * * @param bool $flags If the USE_BINARY flag is provided, the enum values will be binary flag values. Default: no flags set. */ public static function init($flags = 0){ //First, we want to get a list of all static properties of the enum class. We'll use the ReflectionClass for this. $enum = get_called_class(); $ref = new ReflectionClass($enum); $items = $ref->getStaticProperties(); //Now we can start assigning values to the items. if ($flags & self::$BINARY_FLAG){ //If we want binary flag values, our first value should be 1. $value = 1; //Now we can set the values for all items. foreach ($items as $key=>$item){ if (!isset($item)){ //If no value is set manually, we should set it. $enum::$$key = $value; //And we need to calculate the new value $value *= 2; } else { //If there was already a value set, we will continue starting from that value, but only if that was a valid binary flag value. //Otherwise, we will just skip this item. if ($key != 0 && ($key & ($key - 1) == 0)) $value = 2 * $item; } } } else { //If we want to use regular indices, we'll start with index 0. $value = 0; //Now we can set the values for all items. foreach ($items as $key=>$item){ if (!isset($item)){ //If no value is set manually, we should set it, and increment the value for the next item. $enum::$$key = $value; $value++; } else { //If a value was already set, we'll continue from that value. $value = $item+1; } } } } }
这里的其他一些答案中缺少的一个方面是使用带有类型提示的枚举的方法。
如果您将枚举定义为抽象类中的一组常量,例如。
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。
还有一个本地扩展。的SplEnum
脾提供了模拟和创建枚举对象的能力 原生的PHP。
http://www.php.net/manual/en/class.splenum.php
注意:
https://www.php.net/manual/en/spl-types.installation.php
PECL扩展没有与PHP绑定。 此PECL扩展的DLL目前不可用。
这是我对“动态”enum的看法…这样我就可以用变量来调用它,比如从表单中。
看看这个代码块下面的更新版本…
$value = "concert";
$Enumvalue = EnumCategory::enum($value);
//$EnumValue = 1
class EnumCategory{
const concert = 1;
const festival = 2;
const sport = 3;
const nightlife = 4;
const theatre = 5;
const musical = 6;
const cinema = 7;
const charity = 8;
const museum = 9;
const other = 10;
public function enum($string){
return constant('EnumCategory::'.$string);
}
}
更新:更好的方式做…
class EnumCategory {
static $concert = 1;
static $festival = 2;
static $sport = 3;
static $nightlife = 4;
static $theatre = 5;
static $musical = 6;
static $cinema = 7;
static $charity = 8;
static $museum = 9;
static $other = 10;
}
电话
EnumCategory::${$category};
在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文档中发布。