常量可以解决问题,但存在命名空间冲突问题,(或者实际上)因为它们是全局的。数组没有命名空间问题,但它们太模糊了,可以在运行时被覆盖,而IDE很少知道如何在不添加额外的静态分析注释或属性的情况下自动填充它们的键。
你通常会使用哪些解决方案/解决方法? 是否有人记得PHP团队是否对枚举有任何想法或决定?
现在你可以使用SplEnum类来本地构建它。根据官方文档。
SplEnum使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;
}
?>
我知道这是一个旧的线程,然而我看到的所有解决方法中几乎都不像枚举,因为几乎所有的解决方法都要求你手动给枚举项分配值,或者需要你传递一个枚举键的数组给函数。因此,我为此创建了自己的解决方案。
使用我的解决方案创建枚举类很简单,只需扩展下面的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;
}
}
}
}
}
class DayOfWeek {
static $values = array(
self::MONDAY,
self::TUESDAY,
// ...
);
const MONDAY = 0;
const TUESDAY = 1;
// ...
}
$today = DayOfWeek::MONDAY;
// If you want to check if a value is valid
assert( in_array( $today, DayOfWeek::$values ) );
不要使用反射。这会使您的代码难以理解和跟踪,容易导致静态分析工具(例如内置于IDE中的工具)失效。
接受的答案是最好的方式,也是我出于简单起见正在使用的方式。枚举提供了大多数优点(可读性、速度等)。 然而,有一个概念是缺少的:类型安全。 在大多数语言中,枚举也用于限制允许的值。 下面是一个示例,展示了如何使用私有构造函数、静态实例化方法和类型检查来获得类型安全:
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());
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对象转换为其整数表示。
请注意,我们甚至可以通过在实例化方法中实现缓存机制来进一步优化,在枚举广泛使用的情况下可以节省内存...
希望这能有所帮助。
在@Brian Cline的答案基础上,我想发表一下我的意见。
<?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];
}
}
include('enums.php');
]。由于某种原因,它无法看到在子类中声明的枚举函数... - Andrew_STOP_RU_WAR_IN_UA$currentSeason.set("Spring");
。 - Andrew_STOP_RU_WAR_IN_UA这里其他答案缺少的一个方面是如何使用枚举和类型提示。
如果您将枚举定义为抽象类中的一组常量,例如:
abstract class ShirtSize {
public const SMALL = 1;
public const MEDIUM = 2;
public const LARGE = 3;
}
如果一个类不能被实例化,那么你就无法在函数参数中使用类型提示 —— 一方面是因为它不能实例化,另一方面是因为ShirtSize::SMALL
的类型为int
而不是ShirtSize
。
这就是为什么原生的枚举类型在PHP中会比我们设计的任何东西都更好。但是,我们可以通过保持一个私有属性来近似实现枚举类型,该属性表示枚举类型的值,并将其初始化限制为我们预定义的常量。为了防止任意实例化枚举类型(同时避免白名单类型检查的开销),我们将构造函数设为私有。
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
方法,这样用户不必记住使用==
而不是===
来比较两个枚举值。
编辑:一些现有答案非常相似,特别是:https://dev59.com/pnVC5IYBdhLWcg3wjx5d#25526473。
$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};
EnumCategory::$sport = 9;
。欢迎来到体育博物馆。const
是更好的处理方式。 - Dan Lugg指出的解决方案很好。干净、流畅。
然而,如果你想要强类型枚举,可以使用以下方法:
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;
}
}
TestEnum::$TEST1 === TestEnum::parse('TEST1')
语句返回 true。这里有一些不错的解决方案!
以下是我的版本。
我认为主要的缺点是枚举成员必须分别声明和实例化,因为需要包含描述并且PHP无法在静态成员声明时构造对象。我猜想一个解决方法可能是使用反射与解析的文档注释。
抽象枚举如下:
<?php
abstract class AbstractEnum
{
/** @var array cache of all enum instances by class name and integer value */
private static $allEnumMembers = array();
/** @var mixed */
private $code;
/** @var string */
private $description;
/**
* Return an enum instance of the concrete type on which this static method is called, assuming an instance
* exists for the passed in value. Otherwise an exception is thrown.
*
* @param $code
* @return AbstractEnum
* @throws Exception
*/
public static function getByCode($code)
{
$concreteMembers = &self::getConcreteMembers();
if (array_key_exists($code, $concreteMembers)) {
return $concreteMembers[$code];
}
throw new Exception("Value '$code' does not exist for enum '".get_called_class()."'");
}
public static function getAllMembers()
{
return self::getConcreteMembers();
}
/**
* Create, cache and return an instance of the concrete enum type for the supplied primitive value.
*
* @param mixed $code code to uniquely identify this enum
* @param string $description
* @throws Exception
* @return AbstractEnum
*/
protected static function enum($code, $description)
{
$concreteMembers = &self::getConcreteMembers();
if (array_key_exists($code, $concreteMembers)) {
throw new Exception("Value '$code' has already been added to enum '".get_called_class()."'");
}
$concreteMembers[$code] = $concreteEnumInstance = new static($code, $description);
return $concreteEnumInstance;
}
/**
* @return AbstractEnum[]
*/
private static function &getConcreteMembers() {
$thisClassName = get_called_class();
if (!array_key_exists($thisClassName, self::$allEnumMembers)) {
$concreteMembers = array();
self::$allEnumMembers[$thisClassName] = $concreteMembers;
}
return self::$allEnumMembers[$thisClassName];
}
private function __construct($code, $description)
{
$this->code = $code;
$this->description = $description;
}
public function getCode()
{
return $this->code;
}
public function getDescription()
{
return $this->description;
}
}
<?php
require('AbstractEnum.php');
class EMyEnum extends AbstractEnum
{
/** @var EMyEnum */
public static $MY_FIRST_VALUE;
/** @var EMyEnum */
public static $MY_SECOND_VALUE;
/** @var EMyEnum */
public static $MY_THIRD_VALUE;
public static function _init()
{
self::$MY_FIRST_VALUE = self::enum(1, 'My first value');
self::$MY_SECOND_VALUE = self::enum(2, 'My second value');
self::$MY_THIRD_VALUE = self::enum(3, 'My third value');
}
}
EMyEnum::_init();
这可以像这样使用:
<?php
require('EMyEnum.php');
echo EMyEnum::$MY_FIRST_VALUE->getCode().' : '.EMyEnum::$MY_FIRST_VALUE->getDescription().PHP_EOL.PHP_EOL;
var_dump(EMyEnum::getAllMembers());
echo PHP_EOL.EMyEnum::getByCode(2)->getDescription().PHP_EOL;
1 : My first value
array(3) { [1]=> object(EMyEnum)#1 (2) { ["code":"AbstractEnum":private]=> int(1) ["description":"AbstractEnum":private]=> string(14) "My first value" } [2]=> object(EMyEnum)#2 (2) { ["code":"AbstractEnum":private]=> int(2) ["description":"AbstractEnum":private]=> string(15) "My second value" } [3]=> object(EMyEnum)#3 (2) { ["code":"AbstractEnum":private]=> int(3) ["description":"AbstractEnum":private]=> string(14) "My third value" } }
My second value
8.1
中原生支持,预计将于2021年11月发布。其语法如下:enum Status { case started; case stopped; case paused; }
- Abdul Rahman Kayali